Add search for every screen
This commit is contained in:
@@ -22,6 +22,8 @@
|
|||||||
<!-- Phase 2.1 — Search affordance placeholders (UI-10, CONTEXT D-06) -->
|
<!-- Phase 2.1 — Search affordance placeholders (UI-10, CONTEXT D-06) -->
|
||||||
<string name="search_placeholder_recipes">Szukaj przepisów…</string>
|
<string name="search_placeholder_recipes">Szukaj przepisów…</string>
|
||||||
<string name="search_placeholder_pantry">Szukaj w spiżarni…</string>
|
<string name="search_placeholder_pantry">Szukaj w spiżarni…</string>
|
||||||
|
<string name="search_placeholder_planner">Szukaj w planie…</string>
|
||||||
|
<string name="search_placeholder_shopping">Szukaj na liście…</string>
|
||||||
|
|
||||||
<!-- Phase 2.1 — Search affordance a11y (UI-10, CONTEXT D-06/D-08) -->
|
<!-- Phase 2.1 — Search affordance a11y (UI-10, CONTEXT D-06/D-08) -->
|
||||||
<string name="search_open_a11y">Otwórz wyszukiwanie</string>
|
<string name="search_open_a11y">Otwórz wyszukiwanie</string>
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ package dev.ulfrx.recipe.di
|
|||||||
|
|
||||||
import dev.ulfrx.recipe.ui.screens.pantry.PantrySearchViewModel
|
import dev.ulfrx.recipe.ui.screens.pantry.PantrySearchViewModel
|
||||||
import dev.ulfrx.recipe.ui.screens.pantry.PantryViewModel
|
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.planner.PlannerViewModel
|
||||||
import dev.ulfrx.recipe.ui.screens.recipes.RecipesSearchViewModel
|
import dev.ulfrx.recipe.ui.screens.recipes.RecipesSearchViewModel
|
||||||
import dev.ulfrx.recipe.ui.screens.recipes.RecipesViewModel
|
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 dev.ulfrx.recipe.ui.screens.shopping.ShoppingViewModel
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
import org.koin.plugin.module.dsl.viewModel
|
import org.koin.plugin.module.dsl.viewModel
|
||||||
@@ -20,9 +22,11 @@ val shellModule =
|
|||||||
viewModel<PantryViewModel>()
|
viewModel<PantryViewModel>()
|
||||||
viewModel<ShoppingViewModel>()
|
viewModel<ShoppingViewModel>()
|
||||||
|
|
||||||
// Per-tab Search ViewModels — pure echo this phase; Phase 5 / 8 inject
|
// Per-tab Search ViewModels — pure echo this phase; Phase 5 / 6 / 8 / 9
|
||||||
// their respective SearchSource implementations. Both implement
|
// inject their respective SearchSource implementations. All implement
|
||||||
// SearchControls so the shared ProvideSearchChrome composable drives them.
|
// SearchControls so the shared ProvideSearchChrome composable drives them.
|
||||||
viewModel<RecipesSearchViewModel>()
|
viewModel<RecipesSearchViewModel>()
|
||||||
viewModel<PantrySearchViewModel>()
|
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.PantrySearchViewModel
|
||||||
import dev.ulfrx.recipe.ui.screens.pantry.PantryViewModel
|
import dev.ulfrx.recipe.ui.screens.pantry.PantryViewModel
|
||||||
import dev.ulfrx.recipe.ui.screens.planner.PlannerScreen
|
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.planner.PlannerViewModel
|
||||||
import dev.ulfrx.recipe.ui.screens.recipes.RecipesScreen
|
import dev.ulfrx.recipe.ui.screens.recipes.RecipesScreen
|
||||||
import dev.ulfrx.recipe.ui.screens.recipes.RecipesSearchViewModel
|
import dev.ulfrx.recipe.ui.screens.recipes.RecipesSearchViewModel
|
||||||
import dev.ulfrx.recipe.ui.screens.recipes.RecipesViewModel
|
import dev.ulfrx.recipe.ui.screens.recipes.RecipesViewModel
|
||||||
import dev.ulfrx.recipe.ui.screens.shopping.ShoppingScreen
|
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 dev.ulfrx.recipe.ui.screens.shopping.ShoppingViewModel
|
||||||
import org.koin.compose.viewmodel.koinViewModel
|
import org.koin.compose.viewmodel.koinViewModel
|
||||||
|
|
||||||
@@ -72,7 +74,8 @@ fun RootNavDisplay(
|
|||||||
entryProvider = entryProvider {
|
entryProvider = entryProvider {
|
||||||
entry<Screen.Planner.Home> {
|
entry<Screen.Planner.Home> {
|
||||||
val vm: PlannerViewModel = koinViewModel()
|
val vm: PlannerViewModel = koinViewModel()
|
||||||
PlannerScreen(viewModel = vm)
|
val searchVm: PlannerSearchViewModel = koinViewModel()
|
||||||
|
PlannerScreen(viewModel = vm, searchViewModel = searchVm)
|
||||||
}
|
}
|
||||||
entry<Screen.Recipes.Home> {
|
entry<Screen.Recipes.Home> {
|
||||||
val vm: RecipesViewModel = koinViewModel()
|
val vm: RecipesViewModel = koinViewModel()
|
||||||
@@ -86,7 +89,8 @@ fun RootNavDisplay(
|
|||||||
}
|
}
|
||||||
entry<Screen.Shopping.Home> {
|
entry<Screen.Shopping.Home> {
|
||||||
val vm: ShoppingViewModel = koinViewModel()
|
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 androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import dev.ulfrx.recipe.navigation.BottomBarDestination
|
import dev.ulfrx.recipe.navigation.BottomBarDestination
|
||||||
import dev.ulfrx.recipe.ui.components.empty.EmptyState
|
import dev.ulfrx.recipe.ui.components.empty.EmptyState
|
||||||
|
import dev.ulfrx.recipe.ui.components.search.ProvideSearchChrome
|
||||||
import dev.ulfrx.recipe.ui.theme.RecipeTheme
|
import dev.ulfrx.recipe.ui.theme.RecipeTheme
|
||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
import recipe.composeapp.generated.resources.Res
|
import recipe.composeapp.generated.resources.Res
|
||||||
import recipe.composeapp.generated.resources.empty_planner_subtitle
|
import recipe.composeapp.generated.resources.empty_planner_subtitle
|
||||||
import recipe.composeapp.generated.resources.empty_planner_title
|
import recipe.composeapp.generated.resources.empty_planner_title
|
||||||
|
import recipe.composeapp.generated.resources.search_placeholder_planner
|
||||||
import recipe.composeapp.generated.resources.shell_tab_planner
|
import recipe.composeapp.generated.resources.shell_tab_planner
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Phase 2.1 — empty-state screen for the Planner tab. Phase 6 replaces the
|
* Phase 2.1 — empty-state screen for the Planner tab. Phase 6 replaces the
|
||||||
* empty body with the calendar grid.
|
* 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
|
@Composable
|
||||||
fun PlannerScreen(viewModel: PlannerViewModel) {
|
fun PlannerScreen(
|
||||||
|
viewModel: PlannerViewModel,
|
||||||
|
searchViewModel: PlannerSearchViewModel,
|
||||||
|
) {
|
||||||
@Suppress("UNUSED_VARIABLE")
|
@Suppress("UNUSED_VARIABLE")
|
||||||
val state by viewModel.state.collectAsStateWithLifecycle()
|
val state by viewModel.state.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
|
ProvideSearchChrome(
|
||||||
|
controls = searchViewModel,
|
||||||
|
placeholder = Res.string.search_placeholder_planner,
|
||||||
|
activeTab = BottomBarDestination.Planner,
|
||||||
|
)
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier =
|
modifier =
|
||||||
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 androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import dev.ulfrx.recipe.navigation.BottomBarDestination
|
import dev.ulfrx.recipe.navigation.BottomBarDestination
|
||||||
import dev.ulfrx.recipe.ui.components.empty.EmptyState
|
import dev.ulfrx.recipe.ui.components.empty.EmptyState
|
||||||
|
import dev.ulfrx.recipe.ui.components.search.ProvideSearchChrome
|
||||||
import dev.ulfrx.recipe.ui.theme.RecipeTheme
|
import dev.ulfrx.recipe.ui.theme.RecipeTheme
|
||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
import recipe.composeapp.generated.resources.Res
|
import recipe.composeapp.generated.resources.Res
|
||||||
import recipe.composeapp.generated.resources.empty_shopping_subtitle
|
import recipe.composeapp.generated.resources.empty_shopping_subtitle
|
||||||
import recipe.composeapp.generated.resources.empty_shopping_title
|
import recipe.composeapp.generated.resources.empty_shopping_title
|
||||||
|
import recipe.composeapp.generated.resources.search_placeholder_shopping
|
||||||
import recipe.composeapp.generated.resources.shell_tab_shopping
|
import recipe.composeapp.generated.resources.shell_tab_shopping
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Phase 2.1 — empty-state screen for the Shopping tab. Phase 9 replaces the
|
* Phase 2.1 — empty-state screen for the Shopping tab. Phase 9 replaces the
|
||||||
* empty body with the shopping list + session UI.
|
* 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
|
@Composable
|
||||||
fun ShoppingScreen(viewModel: ShoppingViewModel) {
|
fun ShoppingScreen(
|
||||||
|
viewModel: ShoppingViewModel,
|
||||||
|
searchViewModel: ShoppingSearchViewModel,
|
||||||
|
) {
|
||||||
@Suppress("UNUSED_VARIABLE")
|
@Suppress("UNUSED_VARIABLE")
|
||||||
val state by viewModel.state.collectAsStateWithLifecycle()
|
val state by viewModel.state.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
|
ProvideSearchChrome(
|
||||||
|
controls = searchViewModel,
|
||||||
|
placeholder = Res.string.search_placeholder_shopping,
|
||||||
|
activeTab = BottomBarDestination.Shopping,
|
||||||
|
)
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier.fillMaxSize().background(RecipeTheme.colors.background),
|
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