diff --git a/composeApp/src/commonMain/kotlin/de/kitshn/ui/component/input/recipe/RecipeSearchField.kt b/composeApp/src/commonMain/kotlin/de/kitshn/ui/component/input/recipe/RecipeSearchField.kt index f32176cc..cb68cbcd 100644 --- a/composeApp/src/commonMain/kotlin/de/kitshn/ui/component/input/recipe/RecipeSearchField.kt +++ b/composeApp/src/commonMain/kotlin/de/kitshn/ui/component/input/recipe/RecipeSearchField.kt @@ -1,28 +1,26 @@ package de.kitshn.ui.component.input.recipe +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.UnfoldMore import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.ExposedDropdownMenuBox -import androidx.compose.material3.ExposedDropdownMenuBoxScope +import androidx.compose.material3.Icon import androidx.compose.material3.LocalTextStyle -import androidx.compose.material3.MenuAnchorType import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextFieldDefaults -import androidx.compose.material3.Text import androidx.compose.material3.TextField import androidx.compose.material3.TextFieldColors import androidx.compose.material3.TextFieldDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -37,13 +35,11 @@ import coil3.compose.LocalPlatformContext import de.kitshn.api.tandoor.TandoorClient import de.kitshn.api.tandoor.TandoorRequestState import de.kitshn.api.tandoor.model.recipe.TandoorRecipeOverview -import de.kitshn.api.tandoor.rememberTandoorRequestState -import de.kitshn.api.tandoor.route.TandoorRecipeQueryParameters -import de.kitshn.ui.TandoorRequestErrorHandler +import de.kitshn.ui.dialog.select.SelectRecipeDialog +import de.kitshn.ui.dialog.select.rememberSelectRecipeDialogState import kitshn.composeapp.generated.resources.Res import kitshn.composeapp.generated.resources.common_title_image import kitshn.composeapp.generated.resources.common_unknown_recipe -import kotlinx.coroutines.delay import org.jetbrains.compose.resources.getString import org.jetbrains.compose.resources.stringResource @@ -53,10 +49,10 @@ fun BaseRecipeSearchField( client: TandoorClient, value: Int?, onValueChange: (Int?) -> Unit, - content: @Composable ExposedDropdownMenuBoxScope.( + content: @Composable ( thumbnail: @Composable (() -> Unit)?, value: String, - onValueChange: (value: String) -> Unit + onClick: () -> Unit ) -> Unit ) { val context = LocalPlatformContext.current @@ -64,43 +60,34 @@ fun BaseRecipeSearchField( var selectedRecipe by remember { mutableStateOf(null) } LaunchedEffect(selectedRecipe) { onValueChange(selectedRecipe?.id) } - var isExpanded by remember { mutableStateOf(false) } + val selectRecipeDialogState = rememberSelectRecipeDialogState() - var searchText by rememberSaveable { mutableStateOf("") } + var searchText by remember { mutableStateOf("") } LaunchedEffect(value) { - if(value == null) return@LaunchedEffect - if(selectedRecipe?.id != value) selectedRecipe = client.container.recipeOverview[value] - - searchText = selectedRecipe?.name ?: getString(Res.string.common_unknown_recipe) - } - - val recipeOverviewList = remember { mutableStateListOf() } - - val searchRequestState = rememberTandoorRequestState() - LaunchedEffect(searchText) { - delay(300) + if(value == null) { + searchText = "" + selectedRecipe = null + return@LaunchedEffect + } + if(selectedRecipe?.id != value) { + selectedRecipe = client.container.recipeOverview[value] - searchRequestState.wrapRequest { - TandoorRequestState().wrapRequest { - client.recipe.list( - parameters = TandoorRecipeQueryParameters(query = searchText), - pageSize = 5 - ).results.let { - recipeOverviewList.clear() - recipeOverviewList.addAll(it) + if(selectedRecipe == null) { + TandoorRequestState().wrapRequest { + client.recipe.get(value).let { + client.container.recipeOverview[value] = it.toOverview() + selectedRecipe = it.toOverview() + } } } } + + searchText = selectedRecipe?.name ?: getString(Res.string.common_unknown_recipe) } - ExposedDropdownMenuBox( - expanded = isExpanded, - onExpandedChange = { - isExpanded = it - } - ) { - content( - if(selectedRecipe != null) { + content( + when(selectedRecipe != null) { + true -> { { AsyncImage( model = selectedRecipe?.loadThumbnail(), @@ -112,32 +99,21 @@ fun BaseRecipeSearchField( .clip(RoundedCornerShape(8.dp)) ) } - } else null, - searchText - ) { - searchText = it - selectedRecipe = null - } - - ExposedDropdownMenu( - expanded = isExpanded, - onDismissRequest = { - isExpanded = false } - ) { - recipeOverviewList.forEach { - DropdownMenuItem( - text = { Text(it.name) }, - onClick = { - selectedRecipe = it - isExpanded = false - } - ) - } - } + + else -> null + }, + searchText, + ) { + selectRecipeDialogState.open(initialSelectedId = value) } - TandoorRequestErrorHandler(state = searchRequestState) + SelectRecipeDialog( + client = client, + state = selectRecipeDialogState, + ) { + selectedRecipe = it + } } @OptIn(ExperimentalMaterial3Api::class) @@ -165,29 +141,40 @@ fun OutlinedRecipeSearchField( client = client, value = value, onValueChange = onValueChange -) { t, v, vc -> - OutlinedTextField( - value = v, - modifier = modifier.menuAnchor(MenuAnchorType.PrimaryEditable, true), - enabled = true, - readOnly = false, - singleLine = true, - textStyle = textStyle, - label = label, - placeholder = placeholder, - leadingIcon = t ?: leadingIcon, - trailingIcon = trailingIcon, - prefix = prefix, - suffix = suffix, - supportingText = supportingText, - visualTransformation = visualTransformation, - keyboardOptions = keyboardOptions, - keyboardActions = keyboardActions, - isError = isError, - shape = shape, - colors = colors, - onValueChange = vc - ) +) { l, v, onClick -> + Box { + OutlinedTextField( + value = v, + modifier = modifier, + enabled = true, + readOnly = true, + singleLine = true, + textStyle = textStyle, + label = label, + placeholder = placeholder, + leadingIcon = l ?: leadingIcon, + trailingIcon = trailingIcon ?: { + Icon( + imageVector = Icons.Default.UnfoldMore, + contentDescription = null + ) + }, + prefix = prefix, + suffix = suffix, + supportingText = supportingText, + visualTransformation = visualTransformation, + keyboardOptions = keyboardOptions, + keyboardActions = keyboardActions, + isError = isError, + shape = shape, + colors = colors, + onValueChange = { } + ) + + Box( + modifier = Modifier.matchParentSize().clickable { onClick() } + ) + } } @OptIn(ExperimentalMaterial3Api::class) @@ -215,27 +202,38 @@ fun RecipeSearchField( client = client, value = value, onValueChange = onValueChange -) { t, v, vc -> - TextField( - value = v, - modifier = modifier.menuAnchor(MenuAnchorType.PrimaryEditable, true), - enabled = true, - readOnly = false, - singleLine = true, - textStyle = textStyle, - label = label, - placeholder = placeholder, - leadingIcon = t ?: leadingIcon, - trailingIcon = trailingIcon, - prefix = prefix, - suffix = suffix, - supportingText = supportingText, - visualTransformation = visualTransformation, - keyboardOptions = keyboardOptions, - keyboardActions = keyboardActions, - isError = isError, - shape = shape, - colors = colors, - onValueChange = vc - ) -} \ No newline at end of file +) { l, v, onClick -> + Box { + TextField( + value = v, + modifier = modifier, + enabled = true, + readOnly = true, + singleLine = true, + textStyle = textStyle, + label = label, + placeholder = placeholder, + leadingIcon = l ?: leadingIcon, + trailingIcon = trailingIcon ?: { + Icon( + imageVector = Icons.Default.UnfoldMore, + contentDescription = null + ) + }, + prefix = prefix, + suffix = suffix, + supportingText = supportingText, + visualTransformation = visualTransformation, + keyboardOptions = keyboardOptions, + keyboardActions = keyboardActions, + isError = isError, + shape = shape, + colors = colors, + onValueChange = { } + ) + + Box( + modifier = Modifier.matchParentSize().clickable { onClick() } + ) + } +} diff --git a/composeApp/src/commonMain/kotlin/de/kitshn/ui/view/home/search/HomeSearchContent.kt b/composeApp/src/commonMain/kotlin/de/kitshn/ui/component/search/RecipeSearchContent.kt similarity index 70% rename from composeApp/src/commonMain/kotlin/de/kitshn/ui/view/home/search/HomeSearchContent.kt rename to composeApp/src/commonMain/kotlin/de/kitshn/ui/component/search/RecipeSearchContent.kt index 0a6fdbc6..2b29ef99 100644 --- a/composeApp/src/commonMain/kotlin/de/kitshn/ui/view/home/search/HomeSearchContent.kt +++ b/composeApp/src/commonMain/kotlin/de/kitshn/ui/component/search/RecipeSearchContent.kt @@ -1,4 +1,4 @@ -package de.kitshn.ui.view.home.search +package de.kitshn.ui.component.search import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -11,6 +11,9 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.DinnerDining import androidx.compose.material.icons.rounded.NoMeals import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.ListItemColors +import androidx.compose.material3.ListItemDefaults +import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf @@ -36,7 +39,6 @@ import de.kitshn.ui.component.alert.FullSizeAlertPane import de.kitshn.ui.component.loading.AnimatedContainedLoadingIndicator import de.kitshn.ui.component.loading.LazyListAnimatedContainedLoadingIndicator import de.kitshn.ui.component.model.recipe.HorizontalRecipeCardLink -import de.kitshn.ui.component.search.AdditionalSearchSettingsChipRow import de.kitshn.ui.selectionMode.SelectionModeState import de.kitshn.ui.state.foreverRememberNotSavable import kitshn.composeapp.generated.resources.Res @@ -45,13 +47,21 @@ import kitshn.composeapp.generated.resources.home_search_empty_query import kotlinx.coroutines.delay import org.jetbrains.compose.resources.stringResource -const val HOME_SEARCH_PAGING_SIZE = 36 +const val RECIPE_SEARCH_PAGING_SIZE = 36 @Composable -fun ViewHomeSearchContent( +fun RecipeSearchContent( client: TandoorClient, - state: HomeSearchState, + state: RecipeSearchState, selectionModeState: SelectionModeState? = null, + listItemColors: ListItemColors = ListItemDefaults.colors( + containerColor = MaterialTheme.colorScheme.surfaceContainerHigh + ), + selectedListItemColors: ListItemColors = ListItemDefaults.colors( + containerColor = MaterialTheme.colorScheme.primaryContainer, + headlineColor = MaterialTheme.colorScheme.onPrimaryContainer, + supportingColor = MaterialTheme.colorScheme.onPrimaryContainer + ), onClick: (recipe: TandoorRecipeOverview) -> Unit ) { val hapticFeedback = LocalHapticFeedback.current @@ -60,6 +70,11 @@ fun ViewHomeSearchContent( val additionalSearchSettingsChipRowState = state.additionalSearchSettingsChipRowState + val searchLazyListState by foreverRememberNotSavable( + key = "RecipeSearchContent/lazyList/${state.scrollSessionId}", + initialValue = LazyListState() + ) + // overwrite to defaults LaunchedEffect(state.defaultValuesApplied) { if(state.defaultValuesApplied) return@LaunchedEffect @@ -99,17 +114,21 @@ fun ViewHomeSearchContent( } LaunchedEffect(state.query, additionalSearchSettingsChipRowState.updateState) { - if(firstUpdate) { + if(firstUpdate && !state.triggerInitialSearch) { firstUpdate = false return@LaunchedEffect } + firstUpdate = false + state.triggerInitialSearch = false + + delay(300) state.currentPage = 1 val response = state.searchRequestState.wrapRequest { client.recipe.list( parameters = collectQueryParameters(), - pageSize = HOME_SEARCH_PAGING_SIZE, + pageSize = RECIPE_SEARCH_PAGING_SIZE, page = state.currentPage ) } @@ -126,18 +145,9 @@ fun ViewHomeSearchContent( state.searchResultIds.clear() response.results.forEach { recipe -> state.searchResultIds.add(recipe.id) } - hapticFeedback.performHapticFeedback( - when(response.results.size) { - 0 -> HapticFeedbackType.Reject - else -> HapticFeedbackType.Confirm - } - ) + searchLazyListState.scrollToItem(0) } - val searchLazyListState by foreverRememberNotSavable( - key = "ViewHomeSearchContent/lazyList/${state.query}/${additionalSearchSettingsChipRowState.updateState}", - initialValue = LazyListState() - ) val reachedBottom by remember { derivedStateOf { searchLazyListState.reachedBottom() } } var fetchNewItems by remember { mutableStateOf(false) } @@ -157,7 +167,7 @@ fun ViewHomeSearchContent( state.extendedSearchRequestState.wrapRequest { client.recipe.list( parameters = collectQueryParameters(), - pageSize = HOME_SEARCH_PAGING_SIZE, + pageSize = RECIPE_SEARCH_PAGING_SIZE, page = state.currentPage ) }?.let { @@ -186,44 +196,52 @@ fun ViewHomeSearchContent( HorizontalDivider() Box { - when(state.searchRequestState.state) { - TandoorRequestStateState.SUCCESS -> if(state.searchResultIds.size > 0) { - LazyColumn( - modifier = Modifier.fillMaxSize(), - state = searchLazyListState, - contentPadding = PaddingValues(16.dp), - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { - items(state.searchResultIds.size, key = { state.searchResultIds[it] }) { - val recipeOverview = - client.container.recipeOverview[state.searchResultIds[it]] - - if(recipeOverview != null) HorizontalRecipeCardLink( - recipeOverview = recipeOverview, - selectionState = selectionModeState - ) { ro -> onClick(ro) } - } - - LazyListAnimatedContainedLoadingIndicator( - nextPageExists = state.nextPageExists, - extendedRequestState = state.extendedSearchRequestState - ) + if (state.searchResultIds.isNotEmpty()) { + LazyColumn( + modifier = Modifier.fillMaxSize(), + state = searchLazyListState, + contentPadding = PaddingValues(16.dp), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + items(state.searchResultIds.size, key = { state.searchResultIds[it] }) { + val recipeOverview = + client.container.recipeOverview[state.searchResultIds[it]] + + val isSelected = state.initialSelectedId == recipeOverview?.id + + if (recipeOverview != null) HorizontalRecipeCardLink( + recipeOverview = recipeOverview, + selectionState = selectionModeState, + defaultColors = when(isSelected) { + true -> selectedListItemColors + false -> listItemColors + } + ) { ro -> onClick(ro) } } - } else { - FullSizeAlertPane( - imageVector = Icons.Rounded.NoMeals, - contentDescription = stringResource(Res.string.home_search_empty), - text = stringResource(Res.string.home_search_empty) + + LazyListAnimatedContainedLoadingIndicator( + nextPageExists = state.nextPageExists, + extendedRequestState = state.extendedSearchRequestState ) } + } else { + when (state.searchRequestState.state) { + TandoorRequestStateState.SUCCESS -> { + FullSizeAlertPane( + imageVector = Icons.Rounded.NoMeals, + contentDescription = stringResource(Res.string.home_search_empty), + text = stringResource(Res.string.home_search_empty) + ) + } - TandoorRequestStateState.IDLE -> FullSizeAlertPane( - imageVector = Icons.Rounded.DinnerDining, - contentDescription = stringResource(Res.string.home_search_empty_query), - text = stringResource(Res.string.home_search_empty_query) - ) + TandoorRequestStateState.IDLE -> FullSizeAlertPane( + imageVector = Icons.Rounded.DinnerDining, + contentDescription = stringResource(Res.string.home_search_empty_query), + text = stringResource(Res.string.home_search_empty_query) + ) - else -> {} + else -> {} + } } Box( @@ -239,4 +257,4 @@ fun ViewHomeSearchContent( TandoorRequestErrorHandler(state = state.searchRequestState) TandoorRequestErrorHandler(state = state.extendedSearchRequestState) -} \ No newline at end of file +} diff --git a/composeApp/src/commonMain/kotlin/de/kitshn/ui/view/home/search/HomeSearchState.kt b/composeApp/src/commonMain/kotlin/de/kitshn/ui/component/search/RecipeSearchState.kt similarity index 78% rename from composeApp/src/commonMain/kotlin/de/kitshn/ui/view/home/search/HomeSearchState.kt rename to composeApp/src/commonMain/kotlin/de/kitshn/ui/component/search/RecipeSearchState.kt index 27320634..bd02199e 100644 --- a/composeApp/src/commonMain/kotlin/de/kitshn/ui/view/home/search/HomeSearchState.kt +++ b/composeApp/src/commonMain/kotlin/de/kitshn/ui/component/search/RecipeSearchState.kt @@ -1,4 +1,4 @@ -package de.kitshn.ui.view.home.search +package de.kitshn.ui.component.search import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState @@ -14,21 +14,20 @@ import de.kitshn.api.tandoor.model.TandoorKeyword import de.kitshn.api.tandoor.model.TandoorKeywordOverview import de.kitshn.api.tandoor.route.TandoorRecipeQueryParametersSortOrder import de.kitshn.api.tandoor.route.TandoorUser -import de.kitshn.ui.component.search.AdditionalSearchSettingsChipRowState import de.kitshn.ui.state.foreverRememberNotSavable import kotlinx.coroutines.delay @Composable -fun rememberHomeSearchState( +fun rememberGlobalRecipeSearchState( key: String -): MutableState { +): MutableState { return foreverRememberNotSavable( key = key, - initialValue = HomeSearchState() + initialValue = RecipeSearchState() ) } -data class HomeSearchStateDefaultValues( +data class GlobalRecipeSearchStateDefaultValues( val query: String = "", val new: Boolean = false, val random: Boolean = false, @@ -39,7 +38,7 @@ data class HomeSearchStateDefaultValues( val sortOrder: TandoorRecipeQueryParametersSortOrder? = null ) -class HomeSearchState( +class RecipeSearchState( val shown: MutableState = mutableStateOf(false) ) { @@ -54,18 +53,24 @@ class HomeSearchState( var nextPageExists by mutableStateOf(false) var defaultValuesApplied: Boolean = true - var defaultValues = HomeSearchStateDefaultValues() + var defaultValues = GlobalRecipeSearchStateDefaultValues() var appliedAutoFocusSearchField: Boolean = false + var triggerInitialSearch: Boolean = true + var initialSelectedId: Int? = null + var scrollSessionId: Int by mutableIntStateOf(0) - fun open() { + fun open(initialSelectedId: Int? = null) { this.appliedAutoFocusSearchField = false + this.triggerInitialSearch = true + this.initialSelectedId = initialSelectedId + this.scrollSessionId++ - this.defaultValues = HomeSearchStateDefaultValues() + this.defaultValues = GlobalRecipeSearchStateDefaultValues() this.shown.value = true } - fun open(values: HomeSearchStateDefaultValues) { + fun open(values: GlobalRecipeSearchStateDefaultValues) { this.appliedAutoFocusSearchField = false this.defaultValuesApplied = false @@ -83,7 +88,7 @@ class HomeSearchState( ?: client.keyword.retrieve(keywordId) open( - HomeSearchStateDefaultValues( + GlobalRecipeSearchStateDefaultValues( keywords = listOf(keyword) ) ) @@ -92,7 +97,7 @@ class HomeSearchState( fun openWithCreatedBy(user: TandoorUser) { open( - HomeSearchStateDefaultValues( + GlobalRecipeSearchStateDefaultValues( createdBy = user ) ) @@ -103,7 +108,7 @@ class HomeSearchState( val user = client.user.getUsers().find { it.id == userId } open( - HomeSearchStateDefaultValues( + GlobalRecipeSearchStateDefaultValues( createdBy = user ) ) diff --git a/composeApp/src/commonMain/kotlin/de/kitshn/ui/dialog/AdaptiveFullscreenDialog.kt b/composeApp/src/commonMain/kotlin/de/kitshn/ui/dialog/AdaptiveFullscreenDialog.kt index 5827da57..6de4d5bf 100644 --- a/composeApp/src/commonMain/kotlin/de/kitshn/ui/dialog/AdaptiveFullscreenDialog.kt +++ b/composeApp/src/commonMain/kotlin/de/kitshn/ui/dialog/AdaptiveFullscreenDialog.kt @@ -191,7 +191,12 @@ fun CommonAdaptiveFullscreenDialogContent( Column { Box( - Modifier.heightIn(max = maxHeight - 80.dp) + Modifier.heightIn( + max = when(bottomBar != null || actions != null) { + true -> maxHeight - 80.dp + false -> maxHeight + } + ) ) { content( scrollBehavior.nestedScrollConnection, diff --git a/composeApp/src/commonMain/kotlin/de/kitshn/ui/dialog/RecipeSearchDialog.kt b/composeApp/src/commonMain/kotlin/de/kitshn/ui/dialog/RecipeSearchDialog.kt new file mode 100644 index 00000000..c5bc8f6d --- /dev/null +++ b/composeApp/src/commonMain/kotlin/de/kitshn/ui/dialog/RecipeSearchDialog.kt @@ -0,0 +1,148 @@ +package de.kitshn.ui.dialog + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.text.input.delete +import androidx.compose.foundation.text.input.rememberTextFieldState +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Close +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.ListItemDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.SearchBarDefaults +import androidx.compose.material3.SearchBarValue +import androidx.compose.material3.Text +import androidx.compose.material3.rememberSearchBarState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusProperties +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.unit.dp +import de.kitshn.api.tandoor.TandoorClient +import de.kitshn.api.tandoor.model.recipe.TandoorRecipeOverview +import de.kitshn.ui.component.input.iosKeyboardWorkaround.InputFieldWithIOSKeyboardWorkaround +import de.kitshn.ui.component.search.RecipeSearchContent +import de.kitshn.ui.component.search.RecipeSearchState +import kitshn.composeapp.generated.resources.Res +import kitshn.composeapp.generated.resources.action_close +import kitshn.composeapp.generated.resources.home_search_tandoor +import kotlinx.coroutines.delay +import org.jetbrains.compose.resources.stringResource + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun RecipeSearchDialog( + client: TandoorClient, + onDismissRequest: () -> Unit, + initialSelectedId: Int? = null, + title: String? = null, + placeholder: String? = null, + onSelect: (TandoorRecipeOverview) -> Unit +) { + val state = remember { RecipeSearchState() } + LaunchedEffect(Unit) { + state.open(initialSelectedId = initialSelectedId) + } + + val keyboardController = LocalSoftwareKeyboardController.current + val focusRequester = remember { FocusRequester() } + + val textFieldState = rememberTextFieldState(initialText = state.query) + val searchBarState = rememberSearchBarState( + initialValue = SearchBarValue.Expanded + ) + + // query debounce + LaunchedEffect(textFieldState.text) { + // the content debounces this + state.query = textFieldState.text.toString() + } + + var canFocusWorkaround by remember { mutableStateOf(false) } + LaunchedEffect(Unit) { + delay(600) + canFocusWorkaround = true + focusRequester.requestFocus() + } + + val inputField = @Composable { + SearchBarDefaults.InputFieldWithIOSKeyboardWorkaround( + modifier = Modifier + .fillMaxWidth() + .focusRequester(focusRequester) + .focusProperties { + canFocus = canFocusWorkaround + }, + textFieldState = textFieldState, + searchBarState = searchBarState, + onSearch = { + keyboardController?.hide() + }, + placeholder = { Text(placeholder ?: stringResource(Res.string.home_search_tandoor)) }, + trailingIcon = { + AnimatedVisibility( + visible = textFieldState.text.isNotEmpty(), + enter = fadeIn(), + exit = fadeOut() + ) { + IconButton(onClick = { + textFieldState.edit { delete(0, originalText.length) } + state.additionalSearchSettingsChipRowState.reset() + }) { + Icon( + Icons.Rounded.Close, + contentDescription = stringResource(Res.string.action_close) + ) + } + } + } + ) + } + + AdaptiveFullscreenDialog( + onDismiss = { + onDismissRequest() + }, + title = { + title?.let { Text(it) } + } + ) { _, isFullscreen, _ -> + Column( + Modifier + .fillMaxSize() + ) { + if(title != null) { + Box(Modifier.padding(horizontal = 16.dp, vertical = 8.dp)) { + inputField() + } + } + + RecipeSearchContent( + client = client, + state = state, + listItemColors = ListItemDefaults.colors( + containerColor = when(isFullscreen) { + true -> MaterialTheme.colorScheme.surfaceContainerLow + false -> MaterialTheme.colorScheme.surfaceContainerHighest + } + ), + onClick = onSelect + ) + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/de/kitshn/ui/dialog/recipe/RecipeActivitiesBottomSheet.kt b/composeApp/src/commonMain/kotlin/de/kitshn/ui/dialog/recipe/RecipeActivitiesBottomSheet.kt index 1afcafdb..25729c2a 100644 --- a/composeApp/src/commonMain/kotlin/de/kitshn/ui/dialog/recipe/RecipeActivitiesBottomSheet.kt +++ b/composeApp/src/commonMain/kotlin/de/kitshn/ui/dialog/recipe/RecipeActivitiesBottomSheet.kt @@ -44,7 +44,7 @@ import de.kitshn.ui.component.loading.AnimatedContainedLoadingIndicator import de.kitshn.ui.component.loading.LazyListAnimatedContainedLoadingIndicator import de.kitshn.ui.component.model.recipe.activity.RecipeActivityListItem import de.kitshn.ui.theme.Typography -import de.kitshn.ui.view.home.search.HOME_SEARCH_PAGING_SIZE +import de.kitshn.ui.component.search.RECIPE_SEARCH_PAGING_SIZE import kitshn.composeapp.generated.resources.Res import kitshn.composeapp.generated.resources.common_activity import kotlinx.coroutines.delay @@ -108,7 +108,7 @@ fun RecipeActivitiesBottomSheet( listRequestState.wrapRequest { client.cookLog.list( recipeId = recipe.id, - pageSize = HOME_SEARCH_PAGING_SIZE, + pageSize = RECIPE_SEARCH_PAGING_SIZE, ) }?.let { currentPage++ @@ -140,7 +140,7 @@ fun RecipeActivitiesBottomSheet( extendedListRequestState.wrapRequest { client.cookLog.list( recipeId = recipe.id, - pageSize = HOME_SEARCH_PAGING_SIZE, + pageSize = RECIPE_SEARCH_PAGING_SIZE, page = currentPage ) }?.let { diff --git a/composeApp/src/commonMain/kotlin/de/kitshn/ui/dialog/recipeBook/ManageRecipeInRecipeBooksDialog.kt b/composeApp/src/commonMain/kotlin/de/kitshn/ui/dialog/recipeBook/ManageRecipeInRecipeBooksDialog.kt index bb79531d..6de88541 100644 --- a/composeApp/src/commonMain/kotlin/de/kitshn/ui/dialog/recipeBook/ManageRecipeInRecipeBooksDialog.kt +++ b/composeApp/src/commonMain/kotlin/de/kitshn/ui/dialog/recipeBook/ManageRecipeInRecipeBooksDialog.kt @@ -49,7 +49,7 @@ import de.kitshn.ui.component.input.AlwaysDockedSearchBar import de.kitshn.ui.component.input.iosKeyboardWorkaround.InputFieldWithIOSKeyboardWorkaround import de.kitshn.ui.component.model.recipebook.HorizontalRecipeBookCard import de.kitshn.ui.modifier.fullWidthAlertDialogPadding -import de.kitshn.ui.view.home.search.HOME_SEARCH_PAGING_SIZE +import de.kitshn.ui.component.search.RECIPE_SEARCH_PAGING_SIZE import kitshn.composeapp.generated.resources.Res import kitshn.composeapp.generated.resources.action_apply import kitshn.composeapp.generated.resources.action_manage_recipe_books @@ -244,7 +244,7 @@ fun RecipeBookSearchBar( searchRequestState.wrapRequest { client.recipeBook.list( query = search, - pageSize = HOME_SEARCH_PAGING_SIZE, + pageSize = RECIPE_SEARCH_PAGING_SIZE, ) }?.let { searchResults.clear() diff --git a/composeApp/src/commonMain/kotlin/de/kitshn/ui/dialog/select/SelectMultipleFoodsDialog.kt b/composeApp/src/commonMain/kotlin/de/kitshn/ui/dialog/select/SelectMultipleFoodsDialog.kt index c136fdae..31926c79 100644 --- a/composeApp/src/commonMain/kotlin/de/kitshn/ui/dialog/select/SelectMultipleFoodsDialog.kt +++ b/composeApp/src/commonMain/kotlin/de/kitshn/ui/dialog/select/SelectMultipleFoodsDialog.kt @@ -44,7 +44,7 @@ import de.kitshn.ui.component.input.AlwaysDockedSearchBar import de.kitshn.ui.component.input.iosKeyboardWorkaround.InputFieldWithIOSKeyboardWorkaround import de.kitshn.ui.layout.ResponsiveSideBySideLayout import de.kitshn.ui.modifier.fullWidthAlertDialogPadding -import de.kitshn.ui.view.home.search.HOME_SEARCH_PAGING_SIZE +import de.kitshn.ui.component.search.RECIPE_SEARCH_PAGING_SIZE import kitshn.composeapp.generated.resources.Res import kitshn.composeapp.generated.resources.action_apply import kitshn.composeapp.generated.resources.search_ingredients @@ -234,7 +234,7 @@ fun FoodSearchBar( searchRequestState.wrapRequest { client.food.list( query = search, - pageSize = HOME_SEARCH_PAGING_SIZE, + pageSize = RECIPE_SEARCH_PAGING_SIZE, ) }?.let { searchResults.clear() diff --git a/composeApp/src/commonMain/kotlin/de/kitshn/ui/dialog/select/SelectMultipleKeywordsDialog.kt b/composeApp/src/commonMain/kotlin/de/kitshn/ui/dialog/select/SelectMultipleKeywordsDialog.kt index 9ee31ead..51c45cc9 100644 --- a/composeApp/src/commonMain/kotlin/de/kitshn/ui/dialog/select/SelectMultipleKeywordsDialog.kt +++ b/composeApp/src/commonMain/kotlin/de/kitshn/ui/dialog/select/SelectMultipleKeywordsDialog.kt @@ -60,7 +60,7 @@ import de.kitshn.ui.component.input.iosKeyboardWorkaround.InputFieldWithIOSKeybo import de.kitshn.ui.component.loading.LazyListAnimatedContainedLoadingIndicator import de.kitshn.ui.layout.ResponsiveSideBySideLayout import de.kitshn.ui.modifier.fullWidthAlertDialogPadding -import de.kitshn.ui.view.home.search.HOME_SEARCH_PAGING_SIZE +import de.kitshn.ui.component.search.RECIPE_SEARCH_PAGING_SIZE import kitshn.composeapp.generated.resources.Res import kitshn.composeapp.generated.resources.action_add import kitshn.composeapp.generated.resources.action_apply @@ -275,7 +275,7 @@ fun KeywordSearchBar( searchRequestState.wrapRequest { client.keyword.list( query = search, - pageSize = HOME_SEARCH_PAGING_SIZE, + pageSize = RECIPE_SEARCH_PAGING_SIZE, ) }?.let { currentPage++ @@ -307,7 +307,7 @@ fun KeywordSearchBar( extendedSearchRequestState.wrapRequest { client.keyword.list( query = search, - pageSize = HOME_SEARCH_PAGING_SIZE, + pageSize = RECIPE_SEARCH_PAGING_SIZE, page = currentPage ) }?.let { diff --git a/composeApp/src/commonMain/kotlin/de/kitshn/ui/dialog/select/SelectRecipeDialog.kt b/composeApp/src/commonMain/kotlin/de/kitshn/ui/dialog/select/SelectRecipeDialog.kt index 982ce06e..cd2eb08a 100644 --- a/composeApp/src/commonMain/kotlin/de/kitshn/ui/dialog/select/SelectRecipeDialog.kt +++ b/composeApp/src/commonMain/kotlin/de/kitshn/ui/dialog/select/SelectRecipeDialog.kt @@ -1,48 +1,15 @@ package de.kitshn.ui.dialog.select -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.rounded.Receipt -import androidx.compose.material.icons.rounded.Search -import androidx.compose.material3.AlertDialog -import androidx.compose.material3.CardDefaults -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.FilledTonalButton -import androidx.compose.material3.Icon -import androidx.compose.material3.ListItemDefaults -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.SearchBarDefaults -import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalSoftwareKeyboardController -import androidx.compose.ui.unit.dp -import androidx.compose.ui.window.DialogProperties import de.kitshn.api.tandoor.TandoorClient import de.kitshn.api.tandoor.model.recipe.TandoorRecipeOverview -import de.kitshn.api.tandoor.rememberTandoorRequestState -import de.kitshn.api.tandoor.route.TandoorRecipeQueryParameters -import de.kitshn.ui.component.input.AlwaysDockedSearchBar -import de.kitshn.ui.component.input.iosKeyboardWorkaround.InputFieldWithIOSKeyboardWorkaround -import de.kitshn.ui.component.model.recipe.HorizontalRecipeCardLink -import de.kitshn.ui.modifier.fullWidthAlertDialogPadding -import de.kitshn.ui.view.home.search.HOME_SEARCH_PAGING_SIZE +import de.kitshn.ui.dialog.RecipeSearchDialog import kitshn.composeapp.generated.resources.Res -import kitshn.composeapp.generated.resources.action_abort import kitshn.composeapp.generated.resources.search_recipes import kitshn.composeapp.generated.resources.select_recipe -import kotlinx.coroutines.delay import org.jetbrains.compose.resources.stringResource @Composable @@ -55,7 +22,10 @@ fun rememberSelectRecipeDialogState(): SelectRecipeDialogState { class SelectRecipeDialogState( val shown: MutableState = mutableStateOf(false) ) { - fun open() { + var initialSelectedId: Int? = null + + fun open(initialSelectedId: Int? = null) { + this.initialSelectedId = initialSelectedId this.shown.value = true } @@ -72,115 +42,17 @@ fun SelectRecipeDialog( ) { if(!state.shown.value) return - val submit: (recipeOverview: TandoorRecipeOverview?) -> Unit = { - it?.let { it1 -> onSubmit(it1) } - state.dismiss() - } - - AlertDialog( - modifier = Modifier.fullWidthAlertDialogPadding(), + RecipeSearchDialog( + client = client, onDismissRequest = { state.dismiss() }, - icon = { - Icon(Icons.Rounded.Receipt, stringResource(Res.string.select_recipe)) - }, - title = { - Text(stringResource(Res.string.select_recipe)) - }, - text = { - RecipeSearchBar( - client = client, - onSelect = submit - ) - }, - confirmButton = { - FilledTonalButton(onClick = { - submit(null) - }) { - Text(stringResource(Res.string.action_abort)) - } - }, - properties = DialogProperties( - usePlatformDefaultWidth = false - ) + initialSelectedId = state.initialSelectedId, + title = stringResource(Res.string.select_recipe), + placeholder = stringResource(Res.string.search_recipes), + onSelect = { + onSubmit(it) + state.dismiss() + } ) } - - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun RecipeSearchBar( - client: TandoorClient, - onSelect: (recipeOverview: TandoorRecipeOverview) -> Unit -) { - val keyboardController = LocalSoftwareKeyboardController.current - - var query by rememberSaveable { mutableStateOf("") } - var search by rememberSaveable { mutableStateOf("") } - - LaunchedEffect(query) { - delay(250) - search = query - } - - val searchRequestState = rememberTandoorRequestState() - - val searchResults = remember { mutableStateListOf() } - LaunchedEffect(search) { - searchRequestState.wrapRequest { - client.recipe.list( - parameters = TandoorRecipeQueryParameters( - query = search - ), - pageSize = HOME_SEARCH_PAGING_SIZE, - ) - }?.let { - searchResults.clear() - searchResults.addAll(it.results) - } - } - - AlwaysDockedSearchBar( - colors = SearchBarDefaults.colors( - containerColor = MaterialTheme.colorScheme.surface, - dividerColor = Color.Transparent - ), - inputField = { - SearchBarDefaults.InputFieldWithIOSKeyboardWorkaround( - query = query, - onQueryChange = { query = it }, - onSearch = { - keyboardController?.hide() - search = it - }, - leadingIcon = { - Icon( - Icons.Rounded.Search, - stringResource(Res.string.search_recipes) - ) - }, - placeholder = { Text(stringResource(Res.string.search_recipes)) }, - expanded = true, - onExpandedChange = { } - ) - } - ) { - LazyColumn( - contentPadding = PaddingValues(16.dp), - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { - items(searchResults.size, key = { searchResults[it].id }) { - val recipeOverview = searchResults[it] - - HorizontalRecipeCardLink( - recipeOverview = recipeOverview, - onClick = onSelect, - defaultColors = ListItemDefaults.colors( - containerColor = CardDefaults.cardColors().containerColor - ) - ) - } - } - } -} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/de/kitshn/ui/route/main/subroute/home/Home.kt b/composeApp/src/commonMain/kotlin/de/kitshn/ui/route/main/subroute/home/Home.kt index c75d6317..815d227d 100644 --- a/composeApp/src/commonMain/kotlin/de/kitshn/ui/route/main/subroute/home/Home.kt +++ b/composeApp/src/commonMain/kotlin/de/kitshn/ui/route/main/subroute/home/Home.kt @@ -66,7 +66,7 @@ import de.kitshn.ui.route.RouteParameters import de.kitshn.ui.selectionMode.model.RecipeSelectionModeTopAppBar import de.kitshn.ui.selectionMode.rememberSelectionModeState import de.kitshn.ui.view.home.search.HomeSearchTopBar -import de.kitshn.ui.view.home.search.rememberHomeSearchState +import de.kitshn.ui.component.search.rememberGlobalRecipeSearchState import kitshn.composeapp.generated.resources.Res import kitshn.composeapp.generated.resources.action_add import kitshn.composeapp.generated.resources.action_import @@ -87,7 +87,7 @@ fun RouteMainSubrouteHome( ) { val coroutineScope = rememberCoroutineScope() - val homeSearchState by rememberHomeSearchState(key = "RouteMainSubrouteHome/homeSearch") + val homeSearchState by rememberGlobalRecipeSearchState(key = "RouteMainSubrouteHome/homeSearch") // handle keyword passing p.vm.uiState.searchKeyword.WatchAndConsume { diff --git a/composeApp/src/commonMain/kotlin/de/kitshn/ui/route/main/subroute/home/HomeDynamicLayout.kt b/composeApp/src/commonMain/kotlin/de/kitshn/ui/route/main/subroute/home/HomeDynamicLayout.kt index e9a632c8..8775874b 100644 --- a/composeApp/src/commonMain/kotlin/de/kitshn/ui/route/main/subroute/home/HomeDynamicLayout.kt +++ b/composeApp/src/commonMain/kotlin/de/kitshn/ui/route/main/subroute/home/HomeDynamicLayout.kt @@ -44,7 +44,7 @@ import de.kitshn.ui.state.ErrorLoadingSuccessState import de.kitshn.ui.state.foreverRememberNotSavable import de.kitshn.ui.state.rememberErrorLoadingSuccessState import de.kitshn.ui.state.rememberForeverScrollState -import de.kitshn.ui.view.home.search.HomeSearchState +import de.kitshn.ui.component.search.RecipeSearchState import de.kitshn.ui.view.recipe.details.RecipeServingsAmountSaveMap import kitshn.composeapp.generated.resources.Res import kitshn.composeapp.generated.resources.action_show_all_recipes @@ -58,7 +58,7 @@ fun HomeDynamicLayout( searchBarScrollBehavior: SearchBarScrollBehavior, selectionModeState: SelectionModeState, - homeSearchState: HomeSearchState, + homeSearchState: RecipeSearchState, onIsScrollingUpChanged: (isScrollingUp: Boolean) -> Unit, diff --git a/composeApp/src/commonMain/kotlin/de/kitshn/ui/route/main/subroute/home/HomeTraditionalLayout.kt b/composeApp/src/commonMain/kotlin/de/kitshn/ui/route/main/subroute/home/HomeTraditionalLayout.kt index be7d61bd..3bc51f9a 100644 --- a/composeApp/src/commonMain/kotlin/de/kitshn/ui/route/main/subroute/home/HomeTraditionalLayout.kt +++ b/composeApp/src/commonMain/kotlin/de/kitshn/ui/route/main/subroute/home/HomeTraditionalLayout.kt @@ -58,8 +58,8 @@ import de.kitshn.ui.state.foreverRememberNotSavable import de.kitshn.ui.state.rememberErrorLoadingSuccessState import de.kitshn.ui.theme.Typography import de.kitshn.ui.theme.playfairDisplay -import de.kitshn.ui.view.home.search.HOME_SEARCH_PAGING_SIZE -import de.kitshn.ui.view.home.search.HomeSearchState +import de.kitshn.ui.component.search.RECIPE_SEARCH_PAGING_SIZE +import de.kitshn.ui.component.search.RecipeSearchState import de.kitshn.ui.view.recipe.details.RecipeServingsAmountSaveMap import kitshn.composeapp.generated.resources.Res import kitshn.composeapp.generated.resources.lorem_ipsum_medium @@ -75,7 +75,7 @@ fun HomeTraditionalLayout( searchBarScrollBehavior: SearchBarScrollBehavior, selectionModeState: SelectionModeState, - homeSearchState: HomeSearchState, + homeSearchState: RecipeSearchState, onIsScrollingUpChanged: (isScrollingUp: Boolean) -> Unit, @@ -139,7 +139,7 @@ fun HomeTraditionalLayout( listRequestState.wrapRequest { p.vm.tandoorClient!!.recipe.list( parameters = queryParameters, - pageSize = HOME_SEARCH_PAGING_SIZE, + pageSize = RECIPE_SEARCH_PAGING_SIZE, page = currentPage ) }?.let { @@ -177,7 +177,7 @@ fun HomeTraditionalLayout( extendedListRequestState.wrapRequest { p.vm.tandoorClient!!.recipe.list( parameters = queryParameters, - pageSize = HOME_SEARCH_PAGING_SIZE, + pageSize = RECIPE_SEARCH_PAGING_SIZE, page = currentPage ) }?.let { diff --git a/composeApp/src/commonMain/kotlin/de/kitshn/ui/route/main/subroute/list/List.kt b/composeApp/src/commonMain/kotlin/de/kitshn/ui/route/main/subroute/list/List.kt index bf88bcd0..c5a5c31b 100644 --- a/composeApp/src/commonMain/kotlin/de/kitshn/ui/route/main/subroute/list/List.kt +++ b/composeApp/src/commonMain/kotlin/de/kitshn/ui/route/main/subroute/list/List.kt @@ -43,7 +43,7 @@ import de.kitshn.ui.state.ErrorLoadingSuccessState import de.kitshn.ui.state.foreverRememberMutableStateList import de.kitshn.ui.state.foreverRememberNotSavable import de.kitshn.ui.state.rememberErrorLoadingSuccessState -import de.kitshn.ui.view.home.search.HOME_SEARCH_PAGING_SIZE +import de.kitshn.ui.component.search.RECIPE_SEARCH_PAGING_SIZE import kitshn.composeapp.generated.resources.Res import kitshn.composeapp.generated.resources.navigation_list import kotlinx.coroutines.delay @@ -86,7 +86,7 @@ fun RouteMainSubrouteList( listRequestState.wrapRequest { client.recipe.list( parameters = TandoorRecipeQueryParameters(), - pageSize = HOME_SEARCH_PAGING_SIZE, + pageSize = RECIPE_SEARCH_PAGING_SIZE, page = currentPage ) }?.let { @@ -125,7 +125,7 @@ fun RouteMainSubrouteList( extendedListRequestState.wrapRequest { client.recipe.list( parameters = TandoorRecipeQueryParameters(), - pageSize = HOME_SEARCH_PAGING_SIZE, + pageSize = RECIPE_SEARCH_PAGING_SIZE, page = currentPage ) }?.let { diff --git a/composeApp/src/commonMain/kotlin/de/kitshn/ui/view/home/search/HomeSearch.kt b/composeApp/src/commonMain/kotlin/de/kitshn/ui/view/home/search/HomeSearchTopBar.kt similarity index 87% rename from composeApp/src/commonMain/kotlin/de/kitshn/ui/view/home/search/HomeSearch.kt rename to composeApp/src/commonMain/kotlin/de/kitshn/ui/view/home/search/HomeSearchTopBar.kt index 8632b816..161c4ac1 100644 --- a/composeApp/src/commonMain/kotlin/de/kitshn/ui/view/home/search/HomeSearch.kt +++ b/composeApp/src/commonMain/kotlin/de/kitshn/ui/view/home/search/HomeSearchTopBar.kt @@ -16,6 +16,7 @@ import androidx.compose.material3.ExpandedFullScreenSearchBar import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.SearchBarDefaults import androidx.compose.material3.SearchBarScrollBehavior import androidx.compose.material3.SearchBarValue @@ -33,7 +34,10 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.focus.focusProperties import androidx.compose.ui.platform.LocalSoftwareKeyboardController import de.kitshn.KitshnViewModel +import de.kitshn.api.tandoor.model.recipe.TandoorRecipeOverview import de.kitshn.ui.component.input.iosKeyboardWorkaround.InputFieldWithIOSKeyboardWorkaround +import de.kitshn.ui.component.search.RecipeSearchContent +import de.kitshn.ui.component.search.RecipeSearchState import de.kitshn.ui.dialog.recipe.RecipeLinkDialog import de.kitshn.ui.dialog.recipe.rememberRecipeLinkDialogState import de.kitshn.ui.selectionMode.model.RecipeSelectionModeTopAppBar @@ -51,9 +55,10 @@ import org.jetbrains.compose.resources.stringResource @Composable fun HomeSearchTopBar( vm: KitshnViewModel, - state: HomeSearchState, + state: RecipeSearchState, colors: TopAppBarColors, - scrollBehavior: SearchBarScrollBehavior + scrollBehavior: SearchBarScrollBehavior, + onSelect: ((TandoorRecipeOverview) -> Unit)? = null ) { val coroutineScope = rememberCoroutineScope() val keyboardController = LocalSoftwareKeyboardController.current @@ -70,13 +75,14 @@ fun HomeSearchTopBar( val client = vm.tandoorClient ?: return - val textFieldState = rememberTextFieldState() + val textFieldState = rememberTextFieldState(initialText = state.query) val searchBarState = rememberSearchBarState( initialValue = SearchBarValue.Collapsed ) + // query debounce LaunchedEffect(textFieldState.text) { - delay(200) + // the content debounces this state.query = textFieldState.text.toString() } @@ -105,7 +111,6 @@ fun HomeSearchTopBar( searchBarState = searchBarState, onSearch = { keyboardController?.hide() - state.query = it }, placeholder = { Text(stringResource(Res.string.home_search_tandoor)) }, leadingIcon = { @@ -168,17 +173,24 @@ fun HomeSearchTopBar( if(searchBarState.currentValue == SearchBarValue.Expanded) ExpandedFullScreenSearchBar( state = searchBarState, - inputField = inputField + inputField = inputField, + colors = SearchBarDefaults.colors( + containerColor = MaterialTheme.colorScheme.surfaceContainer + ) ) { Box { - ViewHomeSearchContent( + RecipeSearchContent( client = client, state = state, selectionModeState = selectionModeState, onClick = { - // disable text field to hide keyboard on iOS - disableTextField = true - recipeLinkDialogState.open(it) + if (onSelect != null) { + onSelect(it) + } else { + // disable text field to hide keyboard on iOS + disableTextField = true + recipeLinkDialogState.open(it) + } } )