From 4a9cba02d69fc75d84cee470a5ca46f34efdfdc3 Mon Sep 17 00:00:00 2001 From: ulfrxdev Date: Tue, 12 May 2026 23:09:39 +0200 Subject: [PATCH] Add search for every screen --- .../composeResources/values/strings.xml | 2 + .../kotlin/dev/ulfrx/recipe/di/ShellModule.kt | 8 +++- .../ulfrx/recipe/navigation/RootNavDisplay.kt | 8 +++- .../ui/screens/planner/PlannerScreen.kt | 16 +++++++- .../screens/planner/PlannerSearchViewModel.kt | 41 +++++++++++++++++++ .../ui/screens/shopping/ShoppingScreen.kt | 16 +++++++- .../shopping/ShoppingSearchViewModel.kt | 41 +++++++++++++++++++ 7 files changed, 126 insertions(+), 6 deletions(-) create mode 100644 composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/ui/screens/planner/PlannerSearchViewModel.kt create mode 100644 composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/ui/screens/shopping/ShoppingSearchViewModel.kt diff --git a/composeApp/src/commonMain/composeResources/values/strings.xml b/composeApp/src/commonMain/composeResources/values/strings.xml index 7063660..676b9f2 100644 --- a/composeApp/src/commonMain/composeResources/values/strings.xml +++ b/composeApp/src/commonMain/composeResources/values/strings.xml @@ -22,6 +22,8 @@ Szukaj przepisów… Szukaj w spiżarni… + Szukaj w planie… + Szukaj na liście… Otwórz wyszukiwanie diff --git a/composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/di/ShellModule.kt b/composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/di/ShellModule.kt index 604bcb6..834c1d9 100644 --- a/composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/di/ShellModule.kt +++ b/composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/di/ShellModule.kt @@ -2,9 +2,11 @@ package dev.ulfrx.recipe.di import dev.ulfrx.recipe.ui.screens.pantry.PantrySearchViewModel import dev.ulfrx.recipe.ui.screens.pantry.PantryViewModel +import dev.ulfrx.recipe.ui.screens.planner.PlannerSearchViewModel import dev.ulfrx.recipe.ui.screens.planner.PlannerViewModel import dev.ulfrx.recipe.ui.screens.recipes.RecipesSearchViewModel import dev.ulfrx.recipe.ui.screens.recipes.RecipesViewModel +import dev.ulfrx.recipe.ui.screens.shopping.ShoppingSearchViewModel import dev.ulfrx.recipe.ui.screens.shopping.ShoppingViewModel import org.koin.dsl.module import org.koin.plugin.module.dsl.viewModel @@ -20,9 +22,11 @@ val shellModule = viewModel() viewModel() - // Per-tab Search ViewModels — pure echo this phase; Phase 5 / 8 inject - // their respective SearchSource implementations. Both implement + // Per-tab Search ViewModels — pure echo this phase; Phase 5 / 6 / 8 / 9 + // inject their respective SearchSource implementations. All implement // SearchControls so the shared ProvideSearchChrome composable drives them. viewModel() viewModel() + viewModel() + viewModel() } diff --git a/composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/navigation/RootNavDisplay.kt b/composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/navigation/RootNavDisplay.kt index 1853814..8ffdf4d 100644 --- a/composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/navigation/RootNavDisplay.kt +++ b/composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/navigation/RootNavDisplay.kt @@ -14,11 +14,13 @@ import dev.ulfrx.recipe.ui.screens.pantry.PantryScreen import dev.ulfrx.recipe.ui.screens.pantry.PantrySearchViewModel import dev.ulfrx.recipe.ui.screens.pantry.PantryViewModel import dev.ulfrx.recipe.ui.screens.planner.PlannerScreen +import dev.ulfrx.recipe.ui.screens.planner.PlannerSearchViewModel import dev.ulfrx.recipe.ui.screens.planner.PlannerViewModel import dev.ulfrx.recipe.ui.screens.recipes.RecipesScreen import dev.ulfrx.recipe.ui.screens.recipes.RecipesSearchViewModel import dev.ulfrx.recipe.ui.screens.recipes.RecipesViewModel import dev.ulfrx.recipe.ui.screens.shopping.ShoppingScreen +import dev.ulfrx.recipe.ui.screens.shopping.ShoppingSearchViewModel import dev.ulfrx.recipe.ui.screens.shopping.ShoppingViewModel import org.koin.compose.viewmodel.koinViewModel @@ -72,7 +74,8 @@ fun RootNavDisplay( entryProvider = entryProvider { entry { val vm: PlannerViewModel = koinViewModel() - PlannerScreen(viewModel = vm) + val searchVm: PlannerSearchViewModel = koinViewModel() + PlannerScreen(viewModel = vm, searchViewModel = searchVm) } entry { val vm: RecipesViewModel = koinViewModel() @@ -86,7 +89,8 @@ fun RootNavDisplay( } entry { val vm: ShoppingViewModel = koinViewModel() - ShoppingScreen(viewModel = vm) + val searchVm: ShoppingSearchViewModel = koinViewModel() + ShoppingScreen(viewModel = vm, searchViewModel = searchVm) } }, ) diff --git a/composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/ui/screens/planner/PlannerScreen.kt b/composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/ui/screens/planner/PlannerScreen.kt index a19daf1..9676cc5 100644 --- a/composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/ui/screens/planner/PlannerScreen.kt +++ b/composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/ui/screens/planner/PlannerScreen.kt @@ -16,22 +16,36 @@ import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle import dev.ulfrx.recipe.navigation.BottomBarDestination import dev.ulfrx.recipe.ui.components.empty.EmptyState +import dev.ulfrx.recipe.ui.components.search.ProvideSearchChrome import dev.ulfrx.recipe.ui.theme.RecipeTheme import org.jetbrains.compose.resources.stringResource import recipe.composeapp.generated.resources.Res import recipe.composeapp.generated.resources.empty_planner_subtitle import recipe.composeapp.generated.resources.empty_planner_title +import recipe.composeapp.generated.resources.search_placeholder_planner import recipe.composeapp.generated.resources.shell_tab_planner /** * Phase 2.1 — empty-state screen for the Planner tab. Phase 6 replaces the * empty body with the calendar grid. + * + * Owns its own bottom-bar chrome via [ProvideSearchChrome] — search affordance + * is shell-wide for visual consistency across tabs. */ @Composable -fun PlannerScreen(viewModel: PlannerViewModel) { +fun PlannerScreen( + viewModel: PlannerViewModel, + searchViewModel: PlannerSearchViewModel, +) { @Suppress("UNUSED_VARIABLE") val state by viewModel.state.collectAsStateWithLifecycle() + ProvideSearchChrome( + controls = searchViewModel, + placeholder = Res.string.search_placeholder_planner, + activeTab = BottomBarDestination.Planner, + ) + Box( modifier = Modifier diff --git a/composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/ui/screens/planner/PlannerSearchViewModel.kt b/composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/ui/screens/planner/PlannerSearchViewModel.kt new file mode 100644 index 0000000..827f6cc --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/ui/screens/planner/PlannerSearchViewModel.kt @@ -0,0 +1,41 @@ +package dev.ulfrx.recipe.ui.screens.planner + +import androidx.lifecycle.ViewModel +import dev.ulfrx.recipe.ui.components.search.SearchControls +import dev.ulfrx.recipe.ui.components.search.SearchSource +import dev.ulfrx.recipe.ui.components.search.SearchState +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update + +/** + * PlannerSearchViewModel — semantic parity with the Recipes / Pantry search VMs. + * Pure echo this phase; Phase 6/7 injects a Planner-specific SearchSource. + */ +class PlannerSearchViewModel( + @Suppress("UNUSED_PARAMETER") + private val searchSource: SearchSource? = null, +) : ViewModel(), + SearchControls { + private val _state = MutableStateFlow(SearchState()) + override val state: StateFlow = _state.asStateFlow() + + override fun open() { + _state.update { it.copy(isOpen = true) } + } + + /** D-08: closing clears the query. */ + override fun close() { + _state.value = SearchState(isOpen = false, query = "") + } + + override fun onQueryChange(q: String) { + _state.update { it.copy(query = q) } + } + + /** D-07: clear() resets only the query, preserves isOpen. */ + override fun clear() { + _state.update { it.copy(query = "") } + } +} diff --git a/composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/ui/screens/shopping/ShoppingScreen.kt b/composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/ui/screens/shopping/ShoppingScreen.kt index 869fe37..71825ac 100644 --- a/composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/ui/screens/shopping/ShoppingScreen.kt +++ b/composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/ui/screens/shopping/ShoppingScreen.kt @@ -16,22 +16,36 @@ import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle import dev.ulfrx.recipe.navigation.BottomBarDestination import dev.ulfrx.recipe.ui.components.empty.EmptyState +import dev.ulfrx.recipe.ui.components.search.ProvideSearchChrome import dev.ulfrx.recipe.ui.theme.RecipeTheme import org.jetbrains.compose.resources.stringResource import recipe.composeapp.generated.resources.Res import recipe.composeapp.generated.resources.empty_shopping_subtitle import recipe.composeapp.generated.resources.empty_shopping_title +import recipe.composeapp.generated.resources.search_placeholder_shopping import recipe.composeapp.generated.resources.shell_tab_shopping /** * Phase 2.1 — empty-state screen for the Shopping tab. Phase 9 replaces the * empty body with the shopping list + session UI. + * + * Owns its own bottom-bar chrome via [ProvideSearchChrome] — search affordance + * is shell-wide for visual consistency across tabs. */ @Composable -fun ShoppingScreen(viewModel: ShoppingViewModel) { +fun ShoppingScreen( + viewModel: ShoppingViewModel, + searchViewModel: ShoppingSearchViewModel, +) { @Suppress("UNUSED_VARIABLE") val state by viewModel.state.collectAsStateWithLifecycle() + ProvideSearchChrome( + controls = searchViewModel, + placeholder = Res.string.search_placeholder_shopping, + activeTab = BottomBarDestination.Shopping, + ) + Box( modifier = Modifier.fillMaxSize().background(RecipeTheme.colors.background), ) { diff --git a/composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/ui/screens/shopping/ShoppingSearchViewModel.kt b/composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/ui/screens/shopping/ShoppingSearchViewModel.kt new file mode 100644 index 0000000..e2b65e6 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/ulfrx/recipe/ui/screens/shopping/ShoppingSearchViewModel.kt @@ -0,0 +1,41 @@ +package dev.ulfrx.recipe.ui.screens.shopping + +import androidx.lifecycle.ViewModel +import dev.ulfrx.recipe.ui.components.search.SearchControls +import dev.ulfrx.recipe.ui.components.search.SearchSource +import dev.ulfrx.recipe.ui.components.search.SearchState +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update + +/** + * ShoppingSearchViewModel — semantic parity with the Recipes / Pantry search VMs. + * Pure echo this phase; Phase 9 injects a Shopping-specific SearchSource. + */ +class ShoppingSearchViewModel( + @Suppress("UNUSED_PARAMETER") + private val searchSource: SearchSource? = null, +) : ViewModel(), + SearchControls { + private val _state = MutableStateFlow(SearchState()) + override val state: StateFlow = _state.asStateFlow() + + override fun open() { + _state.update { it.copy(isOpen = true) } + } + + /** D-08: closing clears the query. */ + override fun close() { + _state.value = SearchState(isOpen = false, query = "") + } + + override fun onQueryChange(q: String) { + _state.update { it.copy(query = q) } + } + + /** D-07: clear() resets only the query, preserves isOpen. */ + override fun clear() { + _state.update { it.copy(query = "") } + } +}