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 = "") }
+ }
+}