Add search for every screen
This commit is contained in:
@@ -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<PantryViewModel>()
|
||||
viewModel<ShoppingViewModel>()
|
||||
|
||||
// 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<RecipesSearchViewModel>()
|
||||
viewModel<PantrySearchViewModel>()
|
||||
viewModel<PlannerSearchViewModel>()
|
||||
viewModel<ShoppingSearchViewModel>()
|
||||
}
|
||||
|
||||
@@ -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<Screen.Planner.Home> {
|
||||
val vm: PlannerViewModel = koinViewModel()
|
||||
PlannerScreen(viewModel = vm)
|
||||
val searchVm: PlannerSearchViewModel = koinViewModel()
|
||||
PlannerScreen(viewModel = vm, searchViewModel = searchVm)
|
||||
}
|
||||
entry<Screen.Recipes.Home> {
|
||||
val vm: RecipesViewModel = koinViewModel()
|
||||
@@ -86,7 +89,8 @@ fun RootNavDisplay(
|
||||
}
|
||||
entry<Screen.Shopping.Home> {
|
||||
val vm: ShoppingViewModel = koinViewModel()
|
||||
ShoppingScreen(viewModel = vm)
|
||||
val searchVm: ShoppingSearchViewModel = koinViewModel()
|
||||
ShoppingScreen(viewModel = vm, searchViewModel = searchVm)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<SearchState> = _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 = "") }
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
) {
|
||||
|
||||
@@ -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<SearchState> = _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 = "") }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user