Group items in shopping lists based on labels (#319)
* Add data classes to hold food label information - Created `GetFoodLabelResponse` data class to represent food label details - Updated `GetFoodResponse` to include `label` property of type `GetFoodLabelResponse` * Add backend to sort items in shopping lists by label * Add UI code to sort items in shopping lists by label * Use label from ShoppingListItem instead of Food * Use list for ShoppingListItems and labels storage * Fix incorrect routing code * Only add DefaultLabel if there are items with a label * Small improvements to comments and formatting
This commit is contained in:
@@ -0,0 +1,12 @@
|
|||||||
|
package gq.kirmanak.mealient.datasource.models
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class GetItemLabelResponse(
|
||||||
|
@SerialName("name") val name: String,
|
||||||
|
@SerialName("color") val color: String,
|
||||||
|
@SerialName("groupId") val grpId: String,
|
||||||
|
@SerialName("id") val id: String
|
||||||
|
)
|
||||||
@@ -23,6 +23,7 @@ data class GetShoppingListItemResponse(
|
|||||||
@SerialName("quantity") val quantity: Double = 0.0,
|
@SerialName("quantity") val quantity: Double = 0.0,
|
||||||
@SerialName("unit") val unit: GetUnitResponse? = null,
|
@SerialName("unit") val unit: GetUnitResponse? = null,
|
||||||
@SerialName("food") val food: GetFoodResponse? = null,
|
@SerialName("food") val food: GetFoodResponse? = null,
|
||||||
|
@SerialName("label") val label: GetItemLabelResponse? = null,
|
||||||
@SerialName("recipeReferences") val recipeReferences: List<GetShoppingListItemRecipeReferenceResponse> = emptyList(),
|
@SerialName("recipeReferences") val recipeReferences: List<GetShoppingListItemRecipeReferenceResponse> = emptyList(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.Column
|
|||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
@@ -50,6 +51,7 @@ import gq.kirmanak.mealient.datasource.models.GetUnitResponse
|
|||||||
import gq.kirmanak.mealient.shopping_list.R
|
import gq.kirmanak.mealient.shopping_list.R
|
||||||
import gq.kirmanak.mealient.shopping_lists.ui.composables.EditableItemBox
|
import gq.kirmanak.mealient.shopping_lists.ui.composables.EditableItemBox
|
||||||
import gq.kirmanak.mealient.shopping_lists.ui.composables.getErrorMessage
|
import gq.kirmanak.mealient.shopping_lists.ui.composables.getErrorMessage
|
||||||
|
import gq.kirmanak.mealient.shopping_lists.util.ItemLabelGroup
|
||||||
import gq.kirmanak.mealient.ui.AppTheme
|
import gq.kirmanak.mealient.ui.AppTheme
|
||||||
import gq.kirmanak.mealient.ui.Dimens
|
import gq.kirmanak.mealient.ui.Dimens
|
||||||
import gq.kirmanak.mealient.ui.components.BaseScreen
|
import gq.kirmanak.mealient.ui.components.BaseScreen
|
||||||
@@ -145,47 +147,76 @@ private fun ShoppingListScreen(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
lazyListState = lazyListState
|
lazyListState = lazyListState
|
||||||
) { items ->
|
) { sortedItems ->
|
||||||
val firstCheckedItemIndex = items.indexOfFirst { it.checked }
|
|
||||||
lastAddedItemIndex =
|
|
||||||
items.indexOfLast { it is ShoppingListItemState.NewItem }
|
|
||||||
|
|
||||||
itemsIndexed(items, { _, item -> item.id }) { index, itemState ->
|
lastAddedItemIndex = sortedItems.indexOfLast { it is ShoppingListItemState.NewItem }
|
||||||
if (itemState is ShoppingListItemState.ExistingItem) {
|
val firstCheckedItemIndex = sortedItems.indexOfFirst { it.checked }
|
||||||
if (itemState.isEditing) {
|
|
||||||
val state = remember {
|
itemsIndexed(sortedItems, { _, item -> item.id}) { index, itemState ->
|
||||||
ShoppingListItemEditorState(
|
when (itemState) {
|
||||||
state = itemState,
|
is ShoppingListItemState.ItemLabel -> {
|
||||||
foods = loadingState.data?.foods.orEmpty(),
|
ShoppingListSectionHeader(state = itemState)
|
||||||
units = loadingState.data?.units.orEmpty(),
|
}
|
||||||
|
is ShoppingListItemState.ExistingItem -> {
|
||||||
|
if (itemState.isEditing) {
|
||||||
|
val state = remember {
|
||||||
|
ShoppingListItemEditorState(
|
||||||
|
state = itemState,
|
||||||
|
foods = loadingState.data?.foods.orEmpty(),
|
||||||
|
units = loadingState.data?.units.orEmpty(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
ShoppingListItemEditor(
|
||||||
|
state = state,
|
||||||
|
onEditCancelled = { onEditCancel(itemState) },
|
||||||
|
onEditConfirmed = { onEditConfirm(itemState, state) },
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
ShoppingListItem(
|
||||||
|
itemState = itemState,
|
||||||
|
showDivider = firstCheckedItemIndex == index,
|
||||||
|
modifier = Modifier.background(MaterialTheme.colorScheme.surface),
|
||||||
|
onCheckedChange = { onItemCheckedChange(itemState, it) },
|
||||||
|
onDismissed = { onDeleteItem(itemState) },
|
||||||
|
onEditStart = { onEditStart(itemState) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
is ShoppingListItemState.NewItem -> {
|
||||||
ShoppingListItemEditor(
|
ShoppingListItemEditor(
|
||||||
state = state,
|
state = itemState.item,
|
||||||
onEditCancelled = { onEditCancel(itemState) },
|
onEditCancelled = { onAddCancel(itemState) },
|
||||||
onEditConfirmed = { onEditConfirm(itemState, state) },
|
onEditConfirmed = { onAddConfirm(itemState) }
|
||||||
)
|
|
||||||
} else {
|
|
||||||
ShoppingListItem(
|
|
||||||
itemState = itemState,
|
|
||||||
showDivider = index == firstCheckedItemIndex && index != 0,
|
|
||||||
modifier = Modifier.background(MaterialTheme.colorScheme.surface),
|
|
||||||
onCheckedChange = { onItemCheckedChange(itemState, it) },
|
|
||||||
onDismissed = { onDeleteItem(itemState) },
|
|
||||||
onEditStart = { onEditStart(itemState) },
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else if (itemState is ShoppingListItemState.NewItem) {
|
|
||||||
ShoppingListItemEditor(
|
|
||||||
state = itemState.item,
|
|
||||||
onEditCancelled = { onAddCancel(itemState) },
|
|
||||||
onEditConfirmed = { onAddConfirm(itemState) }
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ShoppingListSectionHeader(state: ShoppingListItemState.ItemLabel) {
|
||||||
|
// Skip displaying checked items group and otherwise display the label name
|
||||||
|
val displayLabel = when (state.group) {
|
||||||
|
is ItemLabelGroup.DefaultLabel -> stringResource(
|
||||||
|
R.string.shopping_lists_screen_default_label)
|
||||||
|
is ItemLabelGroup.Label -> state.group.label.name
|
||||||
|
is ItemLabelGroup.CheckedItems -> return
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(MaterialTheme.colorScheme.surface),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = displayLabel,
|
||||||
|
style = MaterialTheme.typography.headlineSmall,
|
||||||
|
modifier = Modifier.padding(horizontal = Dimens.Small)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ShoppingListItemEditor(
|
fun ShoppingListItemEditor(
|
||||||
state: ShoppingListItemEditorState,
|
state: ShoppingListItemEditorState,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package gq.kirmanak.mealient.shopping_lists.ui.details
|
|||||||
import gq.kirmanak.mealient.datasource.models.GetFoodResponse
|
import gq.kirmanak.mealient.datasource.models.GetFoodResponse
|
||||||
import gq.kirmanak.mealient.datasource.models.GetShoppingListItemResponse
|
import gq.kirmanak.mealient.datasource.models.GetShoppingListItemResponse
|
||||||
import gq.kirmanak.mealient.datasource.models.GetUnitResponse
|
import gq.kirmanak.mealient.datasource.models.GetUnitResponse
|
||||||
|
import gq.kirmanak.mealient.shopping_lists.util.ItemLabelGroup
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
internal data class ShoppingListScreenState(
|
internal data class ShoppingListScreenState(
|
||||||
@@ -14,6 +15,9 @@ internal data class ShoppingListScreenState(
|
|||||||
)
|
)
|
||||||
|
|
||||||
sealed class ShoppingListItemState {
|
sealed class ShoppingListItemState {
|
||||||
|
data class ItemLabel(
|
||||||
|
val group: ItemLabelGroup,
|
||||||
|
) : ShoppingListItemState()
|
||||||
|
|
||||||
data class ExistingItem(
|
data class ExistingItem(
|
||||||
val item: GetShoppingListItemResponse,
|
val item: GetShoppingListItemResponse,
|
||||||
@@ -30,16 +34,23 @@ val ShoppingListItemState.id: String
|
|||||||
get() = when (this) {
|
get() = when (this) {
|
||||||
is ShoppingListItemState.ExistingItem -> item.id
|
is ShoppingListItemState.ExistingItem -> item.id
|
||||||
is ShoppingListItemState.NewItem -> id
|
is ShoppingListItemState.NewItem -> id
|
||||||
|
is ShoppingListItemState.ItemLabel -> when (group) {
|
||||||
|
is ItemLabelGroup.Label -> group.label.id
|
||||||
|
is ItemLabelGroup.DefaultLabel -> "defaultLabelId"
|
||||||
|
is ItemLabelGroup.CheckedItems -> "checkedLabelId"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val ShoppingListItemState.checked: Boolean
|
val ShoppingListItemState.checked: Boolean
|
||||||
get() = when (this) {
|
get() = when (this) {
|
||||||
is ShoppingListItemState.ExistingItem -> item.checked
|
is ShoppingListItemState.ExistingItem -> item.checked
|
||||||
is ShoppingListItemState.NewItem -> false
|
is ShoppingListItemState.NewItem -> false
|
||||||
|
is ShoppingListItemState.ItemLabel -> false
|
||||||
}
|
}
|
||||||
|
|
||||||
val ShoppingListItemState.position: Int
|
val ShoppingListItemState.position: Int
|
||||||
get() = when (this) {
|
get() = when (this) {
|
||||||
is ShoppingListItemState.ExistingItem -> item.position
|
is ShoppingListItemState.ExistingItem -> item.position
|
||||||
is ShoppingListItemState.NewItem -> item.position
|
is ShoppingListItemState.NewItem -> item.position
|
||||||
|
is ShoppingListItemState.ItemLabel -> -1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import gq.kirmanak.mealient.logging.Logger
|
|||||||
import gq.kirmanak.mealient.shopping_lists.repo.ShoppingListsAuthRepo
|
import gq.kirmanak.mealient.shopping_lists.repo.ShoppingListsAuthRepo
|
||||||
import gq.kirmanak.mealient.shopping_lists.repo.ShoppingListsRepo
|
import gq.kirmanak.mealient.shopping_lists.repo.ShoppingListsRepo
|
||||||
import gq.kirmanak.mealient.shopping_lists.ui.destinations.ShoppingListScreenDestination
|
import gq.kirmanak.mealient.shopping_lists.ui.destinations.ShoppingListScreenDestination
|
||||||
|
import gq.kirmanak.mealient.shopping_lists.util.groupItemsByLabel
|
||||||
import gq.kirmanak.mealient.ui.util.LoadingHelperFactory
|
import gq.kirmanak.mealient.ui.util.LoadingHelperFactory
|
||||||
import gq.kirmanak.mealient.ui.util.LoadingState
|
import gq.kirmanak.mealient.ui.util.LoadingState
|
||||||
import gq.kirmanak.mealient.ui.util.LoadingStateNoData
|
import gq.kirmanak.mealient.ui.util.LoadingStateNoData
|
||||||
@@ -134,7 +135,7 @@ internal class ShoppingListViewModel @Inject constructor(
|
|||||||
ShoppingListScreenState(
|
ShoppingListScreenState(
|
||||||
name = data.shoppingList.name,
|
name = data.shoppingList.name,
|
||||||
listId = data.shoppingList.id,
|
listId = data.shoppingList.id,
|
||||||
items = items,
|
items = groupItemsByLabel(items),
|
||||||
foods = data.foods.sortedBy { it.name },
|
foods = data.foods.sortedBy { it.name },
|
||||||
units = data.units.sortedBy { it.name },
|
units = data.units.sortedBy { it.name },
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
package gq.kirmanak.mealient.shopping_lists.util
|
||||||
|
|
||||||
|
import gq.kirmanak.mealient.datasource.models.GetItemLabelResponse
|
||||||
|
import gq.kirmanak.mealient.shopping_lists.ui.details.ShoppingListItemState
|
||||||
|
import gq.kirmanak.mealient.shopping_lists.ui.details.checked
|
||||||
|
|
||||||
|
sealed class ItemLabelGroup {
|
||||||
|
data object DefaultLabel : ItemLabelGroup()
|
||||||
|
data object CheckedItems : ItemLabelGroup()
|
||||||
|
data class Label(val label: GetItemLabelResponse) : ItemLabelGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function that sorts items by label. The function returns a list of ShoppingListItemStates
|
||||||
|
* where items are grouped by label. The list is sorted in the following way:
|
||||||
|
* 1. Unchecked items are grouped by label.
|
||||||
|
* 2. Items without a label.
|
||||||
|
* 3. Checked items regardless of label information.
|
||||||
|
*
|
||||||
|
* The List contains `ShoppingListItemStates` with each new label group starting with an
|
||||||
|
* `ItemLabel` state and followed by the items with that label.
|
||||||
|
* The items within a group are alphabetically sorted by name.
|
||||||
|
*/
|
||||||
|
fun groupItemsByLabel(
|
||||||
|
items: List<ShoppingListItemState>
|
||||||
|
): List<ShoppingListItemState> {
|
||||||
|
|
||||||
|
// Group unchecked items by label and sort each group by label name
|
||||||
|
val uncheckedItemsGroupedByLabel = items
|
||||||
|
.filterNot { it.checked }
|
||||||
|
.groupBy { item ->
|
||||||
|
(item as? ShoppingListItemState.ExistingItem)?.item?.label?.let {
|
||||||
|
ItemLabelGroup.Label(it)
|
||||||
|
} ?: ItemLabelGroup.DefaultLabel
|
||||||
|
}.mapValues { (_, groupedItems) ->
|
||||||
|
groupedItems.sortedBy { item ->
|
||||||
|
(item as? ShoppingListItemState.ExistingItem)?.item?.label?.name ?: ""
|
||||||
|
}
|
||||||
|
}.toMutableMap()
|
||||||
|
|
||||||
|
// Remove items with no label from grouped items to prevent them from being displayed first.
|
||||||
|
// Store these items in a separate list to add them to the end of the list later.
|
||||||
|
val uncheckedItemsNoLabel = uncheckedItemsGroupedByLabel[ItemLabelGroup.DefaultLabel].orEmpty()
|
||||||
|
uncheckedItemsGroupedByLabel.remove(ItemLabelGroup.DefaultLabel)
|
||||||
|
|
||||||
|
// Put all checked items into a single group to display them without label-specific headers
|
||||||
|
val checkedItems = items.filter { it.checked }
|
||||||
|
.sortedBy { item ->
|
||||||
|
(item as? ShoppingListItemState.ExistingItem)?.item?.label?.name ?: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to add a group of items with a label to a list.
|
||||||
|
*/
|
||||||
|
fun addLabeledGroupToList(
|
||||||
|
result: MutableList<ShoppingListItemState>,
|
||||||
|
items: List<ShoppingListItemState>,
|
||||||
|
label: ItemLabelGroup?
|
||||||
|
) {
|
||||||
|
if (label != null) {
|
||||||
|
result.add(
|
||||||
|
ShoppingListItemState.ItemLabel(
|
||||||
|
group = label,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
result.addAll(items)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add groups to the result list in the correct order
|
||||||
|
val result = mutableListOf<ShoppingListItemState>()
|
||||||
|
|
||||||
|
uncheckedItemsGroupedByLabel.forEach { (labelGroup, items) ->
|
||||||
|
addLabeledGroupToList(result, items, labelGroup)
|
||||||
|
}
|
||||||
|
if (uncheckedItemsNoLabel.isNotEmpty()) {
|
||||||
|
// Only add DefaultLabel if there are items with a label to avoid cluttering the UI
|
||||||
|
if (result.isNotEmpty()) {
|
||||||
|
addLabeledGroupToList(result, uncheckedItemsNoLabel, ItemLabelGroup.DefaultLabel)
|
||||||
|
} else {
|
||||||
|
addLabeledGroupToList(result, uncheckedItemsNoLabel, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (checkedItems.isNotEmpty()) {
|
||||||
|
addLabeledGroupToList(result, checkedItems, ItemLabelGroup.CheckedItems)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
@@ -33,4 +33,5 @@
|
|||||||
<string name="shopping_lists_dialog_name_clear_input">Eingabe löschen</string>
|
<string name="shopping_lists_dialog_name_clear_input">Eingabe löschen</string>
|
||||||
<string name="shopping_lists_dialog_delete_confirm_title">Entfernen von %1$s</string>
|
<string name="shopping_lists_dialog_delete_confirm_title">Entfernen von %1$s</string>
|
||||||
<string name="shopping_lists_dialog_delete_confirm_text">Bestätigen Sie, dass Sie die Einkaufsliste mit allen Artikeln entfernen möchten.</string>
|
<string name="shopping_lists_dialog_delete_confirm_text">Bestätigen Sie, dass Sie die Einkaufsliste mit allen Artikeln entfernen möchten.</string>
|
||||||
|
<string name="shopping_lists_screen_default_label">Sonstiges</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -33,4 +33,5 @@
|
|||||||
<string name="shopping_lists_dialog_name_clear_input">Borrar entrada</string>
|
<string name="shopping_lists_dialog_name_clear_input">Borrar entrada</string>
|
||||||
<string name="shopping_lists_dialog_delete_confirm_title">Eliminación de %1$s</string>
|
<string name="shopping_lists_dialog_delete_confirm_title">Eliminación de %1$s</string>
|
||||||
<string name="shopping_lists_dialog_delete_confirm_text">Confirme que desea eliminar la lista de la compra incluyendo todos los artículos.</string>
|
<string name="shopping_lists_dialog_delete_confirm_text">Confirme que desea eliminar la lista de la compra incluyendo todos los artículos.</string>
|
||||||
|
<string name="shopping_lists_screen_default_label">Otros</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -33,4 +33,5 @@
|
|||||||
<string name="shopping_lists_dialog_name_clear_input">Effacer l\'entrée</string>
|
<string name="shopping_lists_dialog_name_clear_input">Effacer l\'entrée</string>
|
||||||
<string name="shopping_lists_dialog_delete_confirm_title">Suppression de %1$s</string>
|
<string name="shopping_lists_dialog_delete_confirm_title">Suppression de %1$s</string>
|
||||||
<string name="shopping_lists_dialog_delete_confirm_text">Confirmez que vous souhaitez supprimer la liste d\'achats, y compris tous les articles.</string>
|
<string name="shopping_lists_dialog_delete_confirm_text">Confirmez que vous souhaitez supprimer la liste d\'achats, y compris tous les articles.</string>
|
||||||
|
<string name="shopping_lists_screen_default_label">Autre</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -33,4 +33,5 @@
|
|||||||
<string name="shopping_lists_dialog_name_clear_input">Invoer wissen</string>
|
<string name="shopping_lists_dialog_name_clear_input">Invoer wissen</string>
|
||||||
<string name="shopping_lists_dialog_delete_confirm_title">Verwijderen %1$s</string>
|
<string name="shopping_lists_dialog_delete_confirm_title">Verwijderen %1$s</string>
|
||||||
<string name="shopping_lists_dialog_delete_confirm_text">Bevestig dat je de boodschappenlijst inclusief alle items wilt verwijderen.</string>
|
<string name="shopping_lists_dialog_delete_confirm_text">Bevestig dat je de boodschappenlijst inclusief alle items wilt verwijderen.</string>
|
||||||
|
<string name="shopping_lists_screen_default_label">Overig</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -33,4 +33,5 @@
|
|||||||
<string name="shopping_lists_dialog_name_clear_input">Limpar entrada</string>
|
<string name="shopping_lists_dialog_name_clear_input">Limpar entrada</string>
|
||||||
<string name="shopping_lists_dialog_delete_confirm_title">Remoção de %1$s</string>
|
<string name="shopping_lists_dialog_delete_confirm_title">Remoção de %1$s</string>
|
||||||
<string name="shopping_lists_dialog_delete_confirm_text">Confirme que pretende remover a lista de compras, incluindo todos os artigos.</string>
|
<string name="shopping_lists_dialog_delete_confirm_text">Confirme que pretende remover a lista de compras, incluindo todos os artigos.</string>
|
||||||
|
<string name="shopping_lists_screen_default_label">Outros</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -33,4 +33,5 @@
|
|||||||
<string name="shopping_lists_dialog_name_clear_input">Очистить ввод</string>
|
<string name="shopping_lists_dialog_name_clear_input">Очистить ввод</string>
|
||||||
<string name="shopping_lists_dialog_delete_confirm_title">Удаление %1$s</string>
|
<string name="shopping_lists_dialog_delete_confirm_title">Удаление %1$s</string>
|
||||||
<string name="shopping_lists_dialog_delete_confirm_text">Подтвердите, что вы хотите удалить список покупок, включая все товары.</string>
|
<string name="shopping_lists_dialog_delete_confirm_text">Подтвердите, что вы хотите удалить список покупок, включая все товары.</string>
|
||||||
|
<string name="shopping_lists_screen_default_label">Другое</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -35,5 +35,6 @@
|
|||||||
<string name="shopping_lists_dialog_name_clear_input">Clear input</string>
|
<string name="shopping_lists_dialog_name_clear_input">Clear input</string>
|
||||||
<string name="shopping_lists_dialog_delete_confirm_title">Removing %1$s</string>
|
<string name="shopping_lists_dialog_delete_confirm_title">Removing %1$s</string>
|
||||||
<string name="shopping_lists_dialog_delete_confirm_text">Confirm that you want to remove the shopping list including all items.</string>
|
<string name="shopping_lists_dialog_delete_confirm_text">Confirm that you want to remove the shopping list including all items.</string>
|
||||||
|
<string name="shopping_lists_screen_default_label">Other</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package gq.kirmanak.mealient.shopping_lists.ui.details
|
package gq.kirmanak.mealient.shopping_lists.ui.details
|
||||||
|
|
||||||
import androidx.lifecycle.SavedStateHandle
|
import androidx.lifecycle.SavedStateHandle
|
||||||
|
import gq.kirmanak.mealient.datasource.models.GetItemLabelResponse
|
||||||
import gq.kirmanak.mealient.datasource.models.GetFoodResponse
|
import gq.kirmanak.mealient.datasource.models.GetFoodResponse
|
||||||
import gq.kirmanak.mealient.datasource.models.GetShoppingListItemRecipeReferenceResponse
|
import gq.kirmanak.mealient.datasource.models.GetShoppingListItemRecipeReferenceResponse
|
||||||
import gq.kirmanak.mealient.datasource.models.GetShoppingListItemResponse
|
import gq.kirmanak.mealient.datasource.models.GetShoppingListItemResponse
|
||||||
@@ -8,6 +9,7 @@ import gq.kirmanak.mealient.datasource.models.GetShoppingListResponse
|
|||||||
import gq.kirmanak.mealient.datasource.models.GetUnitResponse
|
import gq.kirmanak.mealient.datasource.models.GetUnitResponse
|
||||||
import gq.kirmanak.mealient.shopping_lists.repo.ShoppingListsAuthRepo
|
import gq.kirmanak.mealient.shopping_lists.repo.ShoppingListsAuthRepo
|
||||||
import gq.kirmanak.mealient.shopping_lists.repo.ShoppingListsRepo
|
import gq.kirmanak.mealient.shopping_lists.repo.ShoppingListsRepo
|
||||||
|
import gq.kirmanak.mealient.shopping_lists.util.ItemLabelGroup
|
||||||
import gq.kirmanak.mealient.test.BaseUnitTest
|
import gq.kirmanak.mealient.test.BaseUnitTest
|
||||||
import gq.kirmanak.mealient.ui.util.LoadingHelper
|
import gq.kirmanak.mealient.ui.util.LoadingHelper
|
||||||
import gq.kirmanak.mealient.ui.util.LoadingHelperFactory
|
import gq.kirmanak.mealient.ui.util.LoadingHelperFactory
|
||||||
@@ -134,7 +136,50 @@ internal class ShoppingListViewModelTest : BaseUnitTest() {
|
|||||||
|
|
||||||
private val mlUnit = GetUnitResponse("ml", "")
|
private val mlUnit = GetUnitResponse("ml", "")
|
||||||
|
|
||||||
private val milkFood = GetFoodResponse("Milk", "")
|
private val milkLabel = GetItemLabelResponse("Milk", "#FF0000", "1", "0")
|
||||||
|
private val milkFood = GetFoodResponse(name = "Milk", id ="")
|
||||||
|
|
||||||
|
private val breadFood = GetFoodResponse(name = "Bread", id = "")
|
||||||
|
|
||||||
|
private val appleLabel = GetItemLabelResponse("Fruit", "#FF0000", "1", "1")
|
||||||
|
private val appleFood = GetFoodResponse(name = "Apple", id = "")
|
||||||
|
|
||||||
|
private val apple = GetShoppingListItemResponse(
|
||||||
|
id = "4",
|
||||||
|
shoppingListId = "1",
|
||||||
|
checked = false,
|
||||||
|
position = 0,
|
||||||
|
isFood = true,
|
||||||
|
note = "Apple",
|
||||||
|
quantity = 1.0,
|
||||||
|
unit = null,
|
||||||
|
food = appleFood,
|
||||||
|
label = appleLabel,
|
||||||
|
recipeReferences = listOf(
|
||||||
|
GetShoppingListItemRecipeReferenceResponse(
|
||||||
|
recipeId = "1",
|
||||||
|
recipeQuantity = 1.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
private val bread = GetShoppingListItemResponse(
|
||||||
|
id = "3",
|
||||||
|
shoppingListId = "1",
|
||||||
|
checked = false,
|
||||||
|
position = 0,
|
||||||
|
isFood = false,
|
||||||
|
note = "Bread",
|
||||||
|
quantity = 1.0,
|
||||||
|
unit = null,
|
||||||
|
food = breadFood,
|
||||||
|
recipeReferences = listOf(
|
||||||
|
GetShoppingListItemRecipeReferenceResponse(
|
||||||
|
recipeId = "1",
|
||||||
|
recipeQuantity = 1.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
private val blackTeaBags = GetShoppingListItemResponse(
|
private val blackTeaBags = GetShoppingListItemResponse(
|
||||||
id = "1",
|
id = "1",
|
||||||
@@ -164,6 +209,7 @@ private val milk = GetShoppingListItemResponse(
|
|||||||
quantity = 500.0,
|
quantity = 500.0,
|
||||||
unit = mlUnit,
|
unit = mlUnit,
|
||||||
food = milkFood,
|
food = milkFood,
|
||||||
|
label = milkLabel,
|
||||||
recipeReferences = listOf(
|
recipeReferences = listOf(
|
||||||
GetShoppingListItemRecipeReferenceResponse(
|
GetShoppingListItemRecipeReferenceResponse(
|
||||||
recipeId = "1",
|
recipeId = "1",
|
||||||
@@ -176,7 +222,7 @@ private val shoppingListResponse = GetShoppingListResponse(
|
|||||||
id = "shoppingListId",
|
id = "shoppingListId",
|
||||||
groupId = "shoppingListGroupId",
|
groupId = "shoppingListGroupId",
|
||||||
name = "shoppingListName",
|
name = "shoppingListName",
|
||||||
listItems = listOf(blackTeaBags, milk),
|
listItems = listOf(blackTeaBags, milk, bread, apple),
|
||||||
recipeReferences = listOf()
|
recipeReferences = listOf()
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -190,14 +236,32 @@ private val shoppingListScreen = ShoppingListScreenState(
|
|||||||
name = "shoppingListName",
|
name = "shoppingListName",
|
||||||
listId = "shoppingListId",
|
listId = "shoppingListId",
|
||||||
items = listOf(
|
items = listOf(
|
||||||
|
ShoppingListItemState.ItemLabel(
|
||||||
|
group = ItemLabelGroup.Label(apple.label
|
||||||
|
?: GetItemLabelResponse(name = "", "", "", "")),
|
||||||
|
),
|
||||||
|
ShoppingListItemState.ExistingItem(
|
||||||
|
item = apple,
|
||||||
|
isEditing = false
|
||||||
|
),
|
||||||
|
ShoppingListItemState.ItemLabel(
|
||||||
|
group = ItemLabelGroup.DefaultLabel,
|
||||||
|
),
|
||||||
ShoppingListItemState.ExistingItem(
|
ShoppingListItemState.ExistingItem(
|
||||||
item = blackTeaBags,
|
item = blackTeaBags,
|
||||||
isEditing = false
|
isEditing = false
|
||||||
),
|
),
|
||||||
|
ShoppingListItemState.ExistingItem(
|
||||||
|
item = bread,
|
||||||
|
isEditing = false
|
||||||
|
),
|
||||||
|
ShoppingListItemState.ItemLabel(
|
||||||
|
group = ItemLabelGroup.CheckedItems
|
||||||
|
),
|
||||||
ShoppingListItemState.ExistingItem(
|
ShoppingListItemState.ExistingItem(
|
||||||
item = milk,
|
item = milk,
|
||||||
isEditing = false
|
isEditing = false
|
||||||
)
|
),
|
||||||
),
|
),
|
||||||
foods = listOf(milkFood),
|
foods = listOf(milkFood),
|
||||||
units = listOf(mlUnit)
|
units = listOf(mlUnit)
|
||||||
|
|||||||
@@ -92,4 +92,3 @@ fun <T> LazyColumnWithLoadingState(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user