Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion composeApp/src/commonMain/kotlin/model/Recipe.kt
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ class Recipe {
filterForTime: TimeRange?,
filterForRecipeType: RecipeType?,
filterForSkillLevel: Range?,
filterForSeason: Season?
filterForSeason: Season?,
filterForIngredients: Set<String> = emptySet()
): Boolean {
// Apply Filters
if (filterForEatingHabit != null && !dietaryHabit.matches(filterForEatingHabit))
Expand All @@ -67,6 +68,14 @@ class Recipe {
if (filterForSeason != null && !season.contains(filterForSeason))
return false

// Filter by ingredients (recipe must contain ALL selected ingredients)
if (filterForIngredients.isNotEmpty()) {
val recipeIngredientIds = shoppingIngredients.map { it.ingredientRef }.toSet()
if (!recipeIngredientIds.containsAll(filterForIngredients)) {
return false
}
}

// Filter Text
if (name.contains(searchText, ignoreCase = true))
return true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.clickable
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
Expand Down Expand Up @@ -47,15 +49,18 @@ fun IngredientPickerDialog(
onDismiss: () -> Unit,
modifier: Modifier = Modifier,
selectedIngredients: List<String> = emptyList(),
multiSelect: Boolean = false
) {
Dialog(
onDismissRequest = onDismiss
) {
IngredientPickerContent(
ingredientList = ingredientList,
onSelected = onSelected,
onDismiss = onDismiss,
modifier = modifier,
selectedIngredients = selectedIngredients
selectedIngredients = selectedIngredients,
multiSelect = multiSelect
)
}
}
Expand All @@ -65,8 +70,10 @@ fun IngredientPickerDialog(
private fun IngredientPickerContent(
ingredientList: List<Ingredient>,
onSelected: (currency: Ingredient) -> Unit,
onDismiss: () -> Unit,
modifier: Modifier = Modifier,
selectedIngredients: List<String> = emptyList(),
multiSelect: Boolean = false
) {
var query by remember { mutableStateOf("") }
var filteredIngredients by remember { mutableStateOf(ingredientList) }
Expand Down Expand Up @@ -125,6 +132,19 @@ private fun IngredientPickerContent(
contentDescription = "Search"
)
},
trailingIcon = {
Icon(
imageVector = Icons.Default.Close,
contentDescription = "Close",
modifier = Modifier.clickable {
if (query.isEmpty()) {
onDismiss()
} else {
query = ""
}
}
)
},
modifier = Modifier.padding(bottom = 8.dp)
)
}
Expand All @@ -136,7 +156,12 @@ private fun IngredientPickerContent(
) {
IngredientCard(
ingredient = it,
onClick = { onSelected(it) },
onClick = {
onSelected(it)
if (!multiSelect) {
onDismiss()
}
},
modifier = Modifier.fillMaxWidth(),
selected = selectedIngredients.contains(it.uid)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ fun DrawerContent(
onClose: () -> Unit,
onLogoutNavigation: () -> Unit,
onManageParticipants: () -> Unit,
onManageRecipes: () -> Unit
onManageRecipes: () -> Unit,
onViewRecipes: () -> Unit
) {
val login: LoginAndRegister = koinInject()
var showConfirmDialog by remember { mutableStateOf(false) }
Expand All @@ -46,13 +47,24 @@ fun DrawerContent(
onManageParticipants()
}
)
HorizontalDivider()
Text("Rezepte", fontWeight = FontWeight.Bold, modifier = Modifier.padding(16.dp))
HorizontalDivider()
NavigationDrawerItem(
label = { Text(text = "Rezeptliste ansehen") },
selected = false,
onClick = {
onViewRecipes()
}
)
NavigationDrawerItem(
label = { Text(text = "Rezepte verwalten") },
selected = false,
onClick = {
onManageRecipes()
}
)
HorizontalDivider()
Text("Benutzer", fontWeight = FontWeight.Bold, modifier = Modifier.padding(16.dp))
HorizontalDivider()
NavigationDrawerItem(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,8 @@ fun EventOverview(
onClose = { scope.launch { drawerState.close() } },
onLogoutNavigation = { onAction(ActionsEventOverview.Logout) },
onManageParticipants = { onAction(NavigationActions.GoToRoute(Routes.ParticipantAdministration)) },
onManageRecipes = { onAction(NavigationActions.GoToRoute(Routes.RecipeManagement)) }
onManageRecipes = { onAction(NavigationActions.GoToRoute(Routes.RecipeManagement)) },
onViewRecipes = { onAction(NavigationActions.GoToRoute(Routes.RecipeList)) }
)
},
gesturesEnabled = true
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package view.event.new_meal_screen

import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import model.Ingredient
import view.admin.new_participant.IngredientPickerDialog

@Composable
fun IngredientFilter(
selectedIngredientIds: Set<String>,
onIngredientsChange: (Set<String>) -> Unit,
allIngredients: List<Ingredient>
) {
var showIngredientPicker by remember { mutableStateOf(false) }
var isFilterActive by remember(selectedIngredientIds) {
mutableStateOf(selectedIngredientIds.isNotEmpty())
}

// Create a display text for selected ingredients
val displayText = if (isFilterActive) {
val ingredientNames = selectedIngredientIds.mapNotNull { id ->
allIngredients.find { it.uid == id }?.name
}
"Zutat: ${ingredientNames.joinToString(", ")}"
} else {
"Zutaten"
}

FilterChip(
text = displayText,
isSelected = isFilterActive,
onClick = { showIngredientPicker = true }
)

// Ingredient picker dialog (multi-select mode)
if (showIngredientPicker) {
IngredientPickerDialog(
onDismiss = { showIngredientPicker = false },
onSelected = { ingredient ->
// Toggle selection (add if not selected, remove if already selected)
if (selectedIngredientIds.contains(ingredient.uid)) {
onIngredientsChange(selectedIngredientIds - ingredient.uid)
} else {
onIngredientsChange(selectedIngredientIds + ingredient.uid)
}
},
selectedIngredients = selectedIngredientIds.toList(),
ingredientList = allIngredients,
multiSelect = true
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,16 @@ fun EditMealScreen(
val recipeViewModel: RecipeViewModel = koinInject()
val allRecipesState = recipeViewModel.state.collectAsState()
val canParticipantEat: ParticipantCanEatRecipe = koinInject()
val ingredientViewModel: view.event.categorized_shopping_list.IngredientViewModel = koinInject()
val ingredientsState = ingredientViewModel.state.collectAsState()

NewMealPage(
state = state.value,
allRecipes = allRecipesState.value,
allIngredients = when (val ingredientsResult = ingredientsState.value) {
is ResultState.Success -> ingredientsResult.data
else -> emptyList()
},
onAction = { action ->
when (action) {
is EditMealActions.ViewRecipe -> recipeOverviewViewModel.handleAction(
Expand Down Expand Up @@ -125,6 +131,7 @@ fun EditMealScreen(
fun NewMealPage(
state: ResultState<EventState>,
allRecipes: List<Recipe>,
allIngredients: List<model.Ingredient>,
onAction: (BaseAction) -> Unit,
participantCanEatRecipe: (ParticipantTime, RecipeSelection) -> Boolean,
getRecipeEatingError: suspend (ParticipantTime, RecipeSelection) -> String?,
Expand All @@ -140,6 +147,7 @@ fun NewMealPage(
onAction = onAction,
state = state,
allRecipes = allRecipes,
allIngredients = allIngredients,
isSearchBarActive = isSearchBarActive,
onSearchBarActiveChange = { isSearchBarActive = it }
)
Expand Down Expand Up @@ -256,6 +264,7 @@ private fun navigateToRecipe(
fun SearchBarComponent(
state: ResultState<EventState>,
allRecipes: List<Recipe>,
allIngredients: List<model.Ingredient>,
onAction: (BaseAction) -> Unit,
isSearchBarActive: Boolean,
onSearchBarActiveChange: (Boolean) -> Unit
Expand All @@ -268,6 +277,7 @@ fun SearchBarComponent(
var selectedRecipeTypeFilter: RecipeType? by remember { mutableStateOf(null) }
var selectedSeasonFilter: Season? by remember { mutableStateOf(null) }
var selectedSkillLevelFilter: Range? by remember { mutableStateOf(null) }
var selectedIngredientFilters by remember { mutableStateOf(setOf<String>()) }


when (state) {
Expand Down Expand Up @@ -330,6 +340,11 @@ fun SearchBarComponent(
onFilterSelect = { selectedSkillLevelFilter = it },
selectedRecipeType = selectedSkillLevelFilter
)
IngredientFilter(
selectedIngredientIds = selectedIngredientFilters,
onIngredientsChange = { selectedIngredientFilters = it },
allIngredients = allIngredients
)
}
HorizontalDivider(thickness = 4.dp)
RecipeList(
Expand All @@ -346,7 +361,8 @@ fun SearchBarComponent(
filterForTime = selectedTimeFilter,
filterForRecipeType = selectedRecipeTypeFilter,
filterForSkillLevel = selectedSkillLevelFilter,
filterForSeason = selectedSeasonFilter
filterForSeason = selectedSeasonFilter,
selectedIngredientFilters = selectedIngredientFilters
)
}
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ fun RecipeList(
filterForTime: TimeRange?,
filterForSkillLevel: Range?,
filterForSeason: Season?,
filterForRecipeType: RecipeType?
filterForRecipeType: RecipeType?,
selectedIngredientFilters: Set<String> = emptySet()
) {
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
allRecipes
Expand All @@ -41,7 +42,8 @@ fun RecipeList(
filterForTime,
filterForRecipeType,
filterForSkillLevel,
filterForSeason
filterForSeason,
selectedIngredientFilters
)
}
.forEach { recipe ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,8 @@ interface RecipeOverviewActions : BaseAction {
data class InitializeScreen(val recipeSelection: RecipeSelection, val eventId: String?) :
RecipeOverviewActions

data class InitializeScreenWithRecipeId(val recipeId: String) :
RecipeOverviewActions

data class UpdateNumberOfPortions(val newNumberOfPortions: Int) : RecipeOverviewActions
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,31 @@ class RecipeOverviewViewModel(
}
}

private fun initializeViewModelWithRecipeId(recipeId: String) {
_recipeState.value = ResultState.Loading
viewModelScope.launch {
val recipe = eventRepository.getRecipeById(recipeId)
val recipeSelection = RecipeSelection().apply {
recipeRef = recipeId
this.recipe = recipe
selectedRecipeName = recipe.name
guestCount = 1
}
val calulatedMap = calculatedIngredientAmounts.calculateAmountsForRecipe(
mutableMapOf(),
recipeSelection,
eventId = null
)
_recipeState.value = ResultState.Success(
RecipeOverviewState(
recipeSelection = recipeSelection,
calculatedIngredientAmounts = calulatedMap.values.toList(),
numberOfPortions = 1
)
)
}
}

private fun changePortionNumber(numberOfPortions: Int) {
val state = _recipeState.value.getSuccessData() ?: return
viewModelScope.launch {
Expand All @@ -69,6 +94,10 @@ class RecipeOverviewViewModel(
action.eventId
)

is RecipeOverviewActions.InitializeScreenWithRecipeId -> initializeViewModelWithRecipeId(
action.recipeId
)

is RecipeOverviewActions.UpdateNumberOfPortions -> changePortionNumber(action.newNumberOfPortions)
}
}
Expand Down
Loading
Loading