Add home screen

This commit is contained in:
2026-05-17 20:44:25 +02:00
parent 8700d197f0
commit 8eda4b04ee
9 changed files with 56 additions and 64 deletions

View File

@@ -14,8 +14,8 @@
<string name="auth_error_unknown">Coś poszło nie tak. Spróbuj ponownie.</string>
<!-- Phase 2.1 — App shell navigation tab labels (UI-03, CONTEXT D-03) -->
<string name="shell_tab_home">Start</string>
<string name="shell_tab_planner">Planer</string>
<string name="shell_tab_recipes">Przepisy</string>
<string name="shell_tab_pantry">Spiżarnia</string>
<string name="shell_tab_shopping">Zakupy</string>
@@ -37,10 +37,10 @@
<string name="dock_expand_a11y">Rozwiń pasek nawigacji</string>
<!-- Phase 2.1 — Empty-state copy (UI-09, CONTEXT D-10/D-11/D-12) -->
<string name="empty_home_title">Tu pojawi się Twój dzień</string>
<string name="empty_home_subtitle">Wkrótce zobaczysz tu podsumowania i propozycje.</string>
<string name="empty_planner_title">Twój plan tygodnia czeka</string>
<string name="empty_planner_subtitle">Wkrótce zobaczysz tu zaplanowane posiłki.</string>
<string name="empty_recipes_title">Tu pojawi się Twoja książka kucharska</string>
<string name="empty_recipes_subtitle">Po dodaniu pierwszych przepisów zobaczysz je w tym miejscu.</string>
<string name="empty_pantry_title">Spiżarnia jest jeszcze pusta</string>
<string name="empty_pantry_subtitle">Wkrótce zobaczysz tu wszystko, co masz pod ręką.</string>
<string name="empty_shopping_title">Lista zakupów czeka na Twój plan</string>

View File

@@ -1,8 +1,8 @@
package dev.ulfrx.recipe.di
import dev.ulfrx.recipe.ui.screens.home.HomeViewModel
import dev.ulfrx.recipe.ui.screens.pantry.PantryViewModel
import dev.ulfrx.recipe.ui.screens.planner.PlannerViewModel
import dev.ulfrx.recipe.ui.screens.recipes.RecipesViewModel
import dev.ulfrx.recipe.ui.screens.search.ShellSearchViewModel
import dev.ulfrx.recipe.ui.screens.shopping.ShoppingViewModel
import org.koin.dsl.module
@@ -14,8 +14,8 @@ val shellModule =
// Active-tab tracking lives in TabNavigator (a `remember`-scoped state holder
// owned by AppShell), not in a shell-level VM, so there is no ShellViewModel
// to register.
viewModel<HomeViewModel>()
viewModel<PlannerViewModel>()
viewModel<RecipesViewModel>()
viewModel<PantryViewModel>()
viewModel<ShoppingViewModel>()

View File

@@ -1,16 +1,16 @@
package dev.ulfrx.recipe.navigation
import androidx.compose.ui.graphics.vector.ImageVector
import com.composables.icons.lucide.BookOpenText
import com.composables.icons.lucide.CalendarDays
import com.composables.icons.lucide.House
import com.composables.icons.lucide.Lucide
import com.composables.icons.lucide.Package
import com.composables.icons.lucide.ShoppingCart
import org.jetbrains.compose.resources.StringResource
import recipe.composeapp.generated.resources.Res
import recipe.composeapp.generated.resources.shell_tab_home
import recipe.composeapp.generated.resources.shell_tab_pantry
import recipe.composeapp.generated.resources.shell_tab_planner
import recipe.composeapp.generated.resources.shell_tab_recipes
import recipe.composeapp.generated.resources.shell_tab_shopping
enum class DockDestination(
@@ -18,16 +18,16 @@ enum class DockDestination(
val labelRes: StringResource,
val icon: ImageVector,
) {
Home(
startDestination = Screen.Home.Root,
labelRes = Res.string.shell_tab_home,
icon = Lucide.House,
),
Planner(
startDestination = Screen.Planner.Home,
labelRes = Res.string.shell_tab_planner,
icon = Lucide.CalendarDays,
),
Recipes(
startDestination = Screen.Recipes.Home,
labelRes = Res.string.shell_tab_recipes,
icon = Lucide.BookOpenText,
),
Pantry(
startDestination = Screen.Pantry.Home,
labelRes = Res.string.shell_tab_pantry,
@@ -41,6 +41,6 @@ enum class DockDestination(
;
companion object {
val Default: DockDestination = Planner
val Default: DockDestination = Home
}
}

View File

@@ -10,12 +10,12 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.navigation3.runtime.entryProvider
import androidx.navigation3.ui.NavDisplay
import dev.ulfrx.recipe.ui.screens.home.HomeScreen
import dev.ulfrx.recipe.ui.screens.home.HomeViewModel
import dev.ulfrx.recipe.ui.screens.pantry.PantryScreen
import dev.ulfrx.recipe.ui.screens.pantry.PantryViewModel
import dev.ulfrx.recipe.ui.screens.planner.PlannerScreen
import dev.ulfrx.recipe.ui.screens.planner.PlannerViewModel
import dev.ulfrx.recipe.ui.screens.recipes.RecipesScreen
import dev.ulfrx.recipe.ui.screens.recipes.RecipesViewModel
import dev.ulfrx.recipe.ui.screens.shopping.ShoppingScreen
import dev.ulfrx.recipe.ui.screens.shopping.ShoppingViewModel
import org.koin.compose.viewmodel.koinViewModel
@@ -72,14 +72,14 @@ fun RootNavDisplay(
modifier = Modifier.fillMaxSize(),
onBack = { navigator.goBack(tab) },
entryProvider = entryProvider {
entry<Screen.Home.Root> {
val vm: HomeViewModel = koinViewModel()
HomeScreen(viewModel = vm)
}
entry<Screen.Planner.Home> {
val vm: PlannerViewModel = koinViewModel()
PlannerScreen(viewModel = vm)
}
entry<Screen.Recipes.Home> {
val vm: RecipesViewModel = koinViewModel()
RecipesScreen(viewModel = vm)
}
entry<Screen.Pantry.Home> {
val vm: PantryViewModel = koinViewModel()
PantryScreen(viewModel = vm)

View File

@@ -7,22 +7,25 @@ import kotlinx.serialization.Serializable
* Type-safe Nav 3 destinations. Each leaf is a `@Serializable` `NavKey` so the
* back stack can be persisted (Nav 3 uses kotlinx-serialization for restoration).
*
* Screens are grouped by tab so future detail destinations (Phase 5+) slot in
* without polluting the top-level namespace — e.g. `Screen.Recipes.Detail(id)`.
* The grouping is purely a code-organisation convenience; Nav 3 treats each
* leaf as an independent NavKey regardless of nesting.
* Screens are grouped by tab so future detail destinations slot in without
* polluting the top-level namespace — e.g. `Screen.Pantry.Detail(id)`. The
* grouping is purely a code-organisation convenience; Nav 3 treats each leaf as
* an independent NavKey regardless of nesting.
*
* The Recipes catalog has no own tab — it is reached via the shell-wide search
* destination (see `ShellSearchViewModel`).
*/
sealed interface Screen : NavKey {
sealed interface Home : Screen {
@Serializable
data object Root : Home
}
sealed interface Planner : Screen {
@Serializable
data object Home : Planner
}
sealed interface Recipes : Screen {
@Serializable
data object Home : Recipes
}
sealed interface Pantry : Screen {
@Serializable
data object Home : Pantry

View File

@@ -1,4 +1,4 @@
package dev.ulfrx.recipe.ui.screens.recipes
package dev.ulfrx.recipe.ui.screens.home
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
@@ -19,19 +19,12 @@ import dev.ulfrx.recipe.ui.components.empty.EmptyState
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_recipes_subtitle
import recipe.composeapp.generated.resources.empty_recipes_title
import recipe.composeapp.generated.resources.shell_tab_recipes
import recipe.composeapp.generated.resources.empty_home_subtitle
import recipe.composeapp.generated.resources.empty_home_title
import recipe.composeapp.generated.resources.shell_tab_home
/**
* Phase 2.1 empty-state screen for the Recipes tab. Phase 5 replaces the
* empty body with the recipe catalog grid.
*
* Search is now shell-wide (see `AppShell` + `ShellSearchViewModel`) this
* screen no longer owns any bottom-chrome state.
*/
@Composable
fun RecipesScreen(viewModel: RecipesViewModel) {
fun HomeScreen(viewModel: HomeViewModel) {
@Suppress("UNUSED_VARIABLE")
val state by viewModel.state.collectAsStateWithLifecycle()
@@ -47,15 +40,15 @@ fun RecipesScreen(viewModel: RecipesViewModel) {
verticalArrangement = Arrangement.Top,
) {
BasicText(
text = stringResource(Res.string.shell_tab_recipes),
text = stringResource(Res.string.shell_tab_home),
style = RecipeTheme.typography.title.copy(color = RecipeTheme.colors.content),
modifier = Modifier.padding(horizontal = RecipeTheme.spacing.lg),
)
Box(modifier = Modifier.fillMaxSize()) {
EmptyState(
icon = DockDestination.Recipes.icon,
title = stringResource(Res.string.empty_recipes_title),
subtitle = stringResource(Res.string.empty_recipes_subtitle),
icon = DockDestination.Home.icon,
title = stringResource(Res.string.empty_home_title),
subtitle = stringResource(Res.string.empty_home_subtitle),
)
}
}

View File

@@ -0,0 +1,15 @@
package dev.ulfrx.recipe.ui.screens.home
import androidx.lifecycle.ViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
data class HomeState(
val isEmpty: Boolean = true,
)
class HomeViewModel : ViewModel() {
private val _state = MutableStateFlow(HomeState())
val state: StateFlow<HomeState> = _state.asStateFlow()
}

View File

@@ -1,19 +0,0 @@
package dev.ulfrx.recipe.ui.screens.recipes
import androidx.lifecycle.ViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
/**
* UI state for [RecipesScreen]. Phase 2.1 ships only the empty state. Phase 5
* (Recipe Catalog Read Path) extends this with `recipes` etc.
*/
data class RecipesState(
val isEmpty: Boolean = true,
)
class RecipesViewModel : ViewModel() {
private val _state = MutableStateFlow(RecipesState())
val state: StateFlow<RecipesState> = _state.asStateFlow()
}

View File

@@ -7,7 +7,7 @@ data object RecipeGlass {
val menu: RecipeGlassStyle = RecipeGlassStyle(
refraction = 0.10f,
curve = 0.5f,
edge = 0.05f,
edge = 0.04f,
dispersion = 0.05f,
saturation = 0.5f,
contrast = 1.3f,