Add home screen
This commit is contained in:
@@ -14,8 +14,8 @@
|
|||||||
<string name="auth_error_unknown">Coś poszło nie tak. Spróbuj ponownie.</string>
|
<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) -->
|
<!-- 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_planner">Planer</string>
|
||||||
<string name="shell_tab_recipes">Przepisy</string>
|
|
||||||
<string name="shell_tab_pantry">Spiżarnia</string>
|
<string name="shell_tab_pantry">Spiżarnia</string>
|
||||||
<string name="shell_tab_shopping">Zakupy</string>
|
<string name="shell_tab_shopping">Zakupy</string>
|
||||||
|
|
||||||
@@ -37,10 +37,10 @@
|
|||||||
<string name="dock_expand_a11y">Rozwiń pasek nawigacji</string>
|
<string name="dock_expand_a11y">Rozwiń pasek nawigacji</string>
|
||||||
|
|
||||||
<!-- Phase 2.1 — Empty-state copy (UI-09, CONTEXT D-10/D-11/D-12) -->
|
<!-- 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_title">Twój plan tygodnia czeka</string>
|
||||||
<string name="empty_planner_subtitle">Wkrótce zobaczysz tu zaplanowane posiłki.</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_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_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>
|
<string name="empty_shopping_title">Lista zakupów czeka na Twój plan</string>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package dev.ulfrx.recipe.di
|
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.pantry.PantryViewModel
|
||||||
import dev.ulfrx.recipe.ui.screens.planner.PlannerViewModel
|
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.search.ShellSearchViewModel
|
||||||
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
|
||||||
@@ -14,8 +14,8 @@ val shellModule =
|
|||||||
// Active-tab tracking lives in TabNavigator (a `remember`-scoped state holder
|
// 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
|
// owned by AppShell), not in a shell-level VM, so there is no ShellViewModel
|
||||||
// to register.
|
// to register.
|
||||||
|
viewModel<HomeViewModel>()
|
||||||
viewModel<PlannerViewModel>()
|
viewModel<PlannerViewModel>()
|
||||||
viewModel<RecipesViewModel>()
|
|
||||||
viewModel<PantryViewModel>()
|
viewModel<PantryViewModel>()
|
||||||
viewModel<ShoppingViewModel>()
|
viewModel<ShoppingViewModel>()
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
package dev.ulfrx.recipe.navigation
|
package dev.ulfrx.recipe.navigation
|
||||||
|
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import com.composables.icons.lucide.BookOpenText
|
|
||||||
import com.composables.icons.lucide.CalendarDays
|
import com.composables.icons.lucide.CalendarDays
|
||||||
|
import com.composables.icons.lucide.House
|
||||||
import com.composables.icons.lucide.Lucide
|
import com.composables.icons.lucide.Lucide
|
||||||
import com.composables.icons.lucide.Package
|
import com.composables.icons.lucide.Package
|
||||||
import com.composables.icons.lucide.ShoppingCart
|
import com.composables.icons.lucide.ShoppingCart
|
||||||
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.shell_tab_home
|
||||||
import recipe.composeapp.generated.resources.shell_tab_pantry
|
import recipe.composeapp.generated.resources.shell_tab_pantry
|
||||||
import recipe.composeapp.generated.resources.shell_tab_planner
|
import recipe.composeapp.generated.resources.shell_tab_planner
|
||||||
import recipe.composeapp.generated.resources.shell_tab_recipes
|
|
||||||
import recipe.composeapp.generated.resources.shell_tab_shopping
|
import recipe.composeapp.generated.resources.shell_tab_shopping
|
||||||
|
|
||||||
enum class DockDestination(
|
enum class DockDestination(
|
||||||
@@ -18,16 +18,16 @@ enum class DockDestination(
|
|||||||
val labelRes: StringResource,
|
val labelRes: StringResource,
|
||||||
val icon: ImageVector,
|
val icon: ImageVector,
|
||||||
) {
|
) {
|
||||||
|
Home(
|
||||||
|
startDestination = Screen.Home.Root,
|
||||||
|
labelRes = Res.string.shell_tab_home,
|
||||||
|
icon = Lucide.House,
|
||||||
|
),
|
||||||
Planner(
|
Planner(
|
||||||
startDestination = Screen.Planner.Home,
|
startDestination = Screen.Planner.Home,
|
||||||
labelRes = Res.string.shell_tab_planner,
|
labelRes = Res.string.shell_tab_planner,
|
||||||
icon = Lucide.CalendarDays,
|
icon = Lucide.CalendarDays,
|
||||||
),
|
),
|
||||||
Recipes(
|
|
||||||
startDestination = Screen.Recipes.Home,
|
|
||||||
labelRes = Res.string.shell_tab_recipes,
|
|
||||||
icon = Lucide.BookOpenText,
|
|
||||||
),
|
|
||||||
Pantry(
|
Pantry(
|
||||||
startDestination = Screen.Pantry.Home,
|
startDestination = Screen.Pantry.Home,
|
||||||
labelRes = Res.string.shell_tab_pantry,
|
labelRes = Res.string.shell_tab_pantry,
|
||||||
@@ -41,6 +41,6 @@ enum class DockDestination(
|
|||||||
;
|
;
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val Default: DockDestination = Planner
|
val Default: DockDestination = Home
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,12 +10,12 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.navigation3.runtime.entryProvider
|
import androidx.navigation3.runtime.entryProvider
|
||||||
import androidx.navigation3.ui.NavDisplay
|
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.PantryScreen
|
||||||
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.PlannerViewModel
|
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.ShoppingScreen
|
||||||
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,14 +72,14 @@ fun RootNavDisplay(
|
|||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
onBack = { navigator.goBack(tab) },
|
onBack = { navigator.goBack(tab) },
|
||||||
entryProvider = entryProvider {
|
entryProvider = entryProvider {
|
||||||
|
entry<Screen.Home.Root> {
|
||||||
|
val vm: HomeViewModel = koinViewModel()
|
||||||
|
HomeScreen(viewModel = vm)
|
||||||
|
}
|
||||||
entry<Screen.Planner.Home> {
|
entry<Screen.Planner.Home> {
|
||||||
val vm: PlannerViewModel = koinViewModel()
|
val vm: PlannerViewModel = koinViewModel()
|
||||||
PlannerScreen(viewModel = vm)
|
PlannerScreen(viewModel = vm)
|
||||||
}
|
}
|
||||||
entry<Screen.Recipes.Home> {
|
|
||||||
val vm: RecipesViewModel = koinViewModel()
|
|
||||||
RecipesScreen(viewModel = vm)
|
|
||||||
}
|
|
||||||
entry<Screen.Pantry.Home> {
|
entry<Screen.Pantry.Home> {
|
||||||
val vm: PantryViewModel = koinViewModel()
|
val vm: PantryViewModel = koinViewModel()
|
||||||
PantryScreen(viewModel = vm)
|
PantryScreen(viewModel = vm)
|
||||||
|
|||||||
@@ -7,22 +7,25 @@ import kotlinx.serialization.Serializable
|
|||||||
* Type-safe Nav 3 destinations. Each leaf is a `@Serializable` `NavKey` so the
|
* 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).
|
* 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
|
* Screens are grouped by tab so future detail destinations slot in without
|
||||||
* without polluting the top-level namespace — e.g. `Screen.Recipes.Detail(id)`.
|
* polluting the top-level namespace — e.g. `Screen.Pantry.Detail(id)`. The
|
||||||
* The grouping is purely a code-organisation convenience; Nav 3 treats each
|
* grouping is purely a code-organisation convenience; Nav 3 treats each leaf as
|
||||||
* leaf as an independent NavKey regardless of nesting.
|
* 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 Screen : NavKey {
|
||||||
|
sealed interface Home : Screen {
|
||||||
|
@Serializable
|
||||||
|
data object Root : Home
|
||||||
|
}
|
||||||
|
|
||||||
sealed interface Planner : Screen {
|
sealed interface Planner : Screen {
|
||||||
@Serializable
|
@Serializable
|
||||||
data object Home : Planner
|
data object Home : Planner
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed interface Recipes : Screen {
|
|
||||||
@Serializable
|
|
||||||
data object Home : Recipes
|
|
||||||
}
|
|
||||||
|
|
||||||
sealed interface Pantry : Screen {
|
sealed interface Pantry : Screen {
|
||||||
@Serializable
|
@Serializable
|
||||||
data object Home : Pantry
|
data object Home : Pantry
|
||||||
|
|||||||
@@ -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.background
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
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 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_recipes_subtitle
|
import recipe.composeapp.generated.resources.empty_home_subtitle
|
||||||
import recipe.composeapp.generated.resources.empty_recipes_title
|
import recipe.composeapp.generated.resources.empty_home_title
|
||||||
import recipe.composeapp.generated.resources.shell_tab_recipes
|
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
|
@Composable
|
||||||
fun RecipesScreen(viewModel: RecipesViewModel) {
|
fun HomeScreen(viewModel: HomeViewModel) {
|
||||||
@Suppress("UNUSED_VARIABLE")
|
@Suppress("UNUSED_VARIABLE")
|
||||||
val state by viewModel.state.collectAsStateWithLifecycle()
|
val state by viewModel.state.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
@@ -47,15 +40,15 @@ fun RecipesScreen(viewModel: RecipesViewModel) {
|
|||||||
verticalArrangement = Arrangement.Top,
|
verticalArrangement = Arrangement.Top,
|
||||||
) {
|
) {
|
||||||
BasicText(
|
BasicText(
|
||||||
text = stringResource(Res.string.shell_tab_recipes),
|
text = stringResource(Res.string.shell_tab_home),
|
||||||
style = RecipeTheme.typography.title.copy(color = RecipeTheme.colors.content),
|
style = RecipeTheme.typography.title.copy(color = RecipeTheme.colors.content),
|
||||||
modifier = Modifier.padding(horizontal = RecipeTheme.spacing.lg),
|
modifier = Modifier.padding(horizontal = RecipeTheme.spacing.lg),
|
||||||
)
|
)
|
||||||
Box(modifier = Modifier.fillMaxSize()) {
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
EmptyState(
|
EmptyState(
|
||||||
icon = DockDestination.Recipes.icon,
|
icon = DockDestination.Home.icon,
|
||||||
title = stringResource(Res.string.empty_recipes_title),
|
title = stringResource(Res.string.empty_home_title),
|
||||||
subtitle = stringResource(Res.string.empty_recipes_subtitle),
|
subtitle = stringResource(Res.string.empty_home_subtitle),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
@@ -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()
|
|
||||||
}
|
|
||||||
@@ -7,7 +7,7 @@ data object RecipeGlass {
|
|||||||
val menu: RecipeGlassStyle = RecipeGlassStyle(
|
val menu: RecipeGlassStyle = RecipeGlassStyle(
|
||||||
refraction = 0.10f,
|
refraction = 0.10f,
|
||||||
curve = 0.5f,
|
curve = 0.5f,
|
||||||
edge = 0.05f,
|
edge = 0.04f,
|
||||||
dispersion = 0.05f,
|
dispersion = 0.05f,
|
||||||
saturation = 0.5f,
|
saturation = 0.5f,
|
||||||
contrast = 1.3f,
|
contrast = 1.3f,
|
||||||
|
|||||||
Reference in New Issue
Block a user