From 7c825970ead9cfe869b850210bb3f8ff351c5773 Mon Sep 17 00:00:00 2001 From: Erik <44932832+HowRuck@users.noreply.github.com> Date: Sat, 7 Sep 2024 22:01:02 +0200 Subject: [PATCH] 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 --- .../datasource/models/GetItemLabelResponse.kt | 12 +++ .../models/GetShoppingListResponse.kt | 1 + .../ui/details/ShoppingListScreen.kt | 91 +++++++++++++------ .../ui/details/ShoppingListScreenState.kt | 11 +++ .../ui/details/ShoppingListViewModel.kt | 3 +- .../util/ShopingListLabelHelper.kt | 89 ++++++++++++++++++ .../src/main/res/values-de/strings.xml | 1 + .../src/main/res/values-es/strings.xml | 1 + .../src/main/res/values-fr/strings.xml | 1 + .../src/main/res/values-nl/strings.xml | 1 + .../src/main/res/values-pt/strings.xml | 1 + .../src/main/res/values-ru/strings.xml | 1 + .../src/main/res/values/strings.xml | 1 + .../ui/details/ShoppingListViewModelTest.kt | 70 +++++++++++++- .../components/LazyColumnWithLoadingState.kt | 1 - 15 files changed, 250 insertions(+), 35 deletions(-) create mode 100644 datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetItemLabelResponse.kt create mode 100644 features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/util/ShopingListLabelHelper.kt diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetItemLabelResponse.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetItemLabelResponse.kt new file mode 100644 index 0000000..10b0831 --- /dev/null +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetItemLabelResponse.kt @@ -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 +) \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetShoppingListResponse.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetShoppingListResponse.kt index c946534..57e0793 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetShoppingListResponse.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetShoppingListResponse.kt @@ -23,6 +23,7 @@ data class GetShoppingListItemResponse( @SerialName("quantity") val quantity: Double = 0.0, @SerialName("unit") val unit: GetUnitResponse? = null, @SerialName("food") val food: GetFoodResponse? = null, + @SerialName("label") val label: GetItemLabelResponse? = null, @SerialName("recipeReferences") val recipeReferences: List = emptyList(), ) diff --git a/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/ui/details/ShoppingListScreen.kt b/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/ui/details/ShoppingListScreen.kt index b1230a5..e23da23 100644 --- a/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/ui/details/ShoppingListScreen.kt +++ b/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/ui/details/ShoppingListScreen.kt @@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState 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_lists.ui.composables.EditableItemBox 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.Dimens import gq.kirmanak.mealient.ui.components.BaseScreen @@ -145,47 +147,76 @@ private fun ShoppingListScreen( } }, lazyListState = lazyListState - ) { items -> - val firstCheckedItemIndex = items.indexOfFirst { it.checked } - lastAddedItemIndex = - items.indexOfLast { it is ShoppingListItemState.NewItem } + ) { sortedItems -> - itemsIndexed(items, { _, item -> item.id }) { index, itemState -> - if (itemState is ShoppingListItemState.ExistingItem) { - if (itemState.isEditing) { - val state = remember { - ShoppingListItemEditorState( - state = itemState, - foods = loadingState.data?.foods.orEmpty(), - units = loadingState.data?.units.orEmpty(), + lastAddedItemIndex = sortedItems.indexOfLast { it is ShoppingListItemState.NewItem } + val firstCheckedItemIndex = sortedItems.indexOfFirst { it.checked } + + itemsIndexed(sortedItems, { _, item -> item.id}) { index, itemState -> + when (itemState) { + is ShoppingListItemState.ItemLabel -> { + ShoppingListSectionHeader(state = itemState) + } + 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( - state = state, - onEditCancelled = { onEditCancel(itemState) }, - onEditConfirmed = { onEditConfirm(itemState, state) }, - ) - } 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) }, + state = itemState.item, + onEditCancelled = { onAddCancel(itemState) }, + onEditConfirmed = { onAddConfirm(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 fun ShoppingListItemEditor( state: ShoppingListItemEditorState, diff --git a/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/ui/details/ShoppingListScreenState.kt b/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/ui/details/ShoppingListScreenState.kt index 5f55fe3..95ce452 100644 --- a/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/ui/details/ShoppingListScreenState.kt +++ b/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/ui/details/ShoppingListScreenState.kt @@ -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.GetShoppingListItemResponse import gq.kirmanak.mealient.datasource.models.GetUnitResponse +import gq.kirmanak.mealient.shopping_lists.util.ItemLabelGroup import java.util.UUID internal data class ShoppingListScreenState( @@ -14,6 +15,9 @@ internal data class ShoppingListScreenState( ) sealed class ShoppingListItemState { + data class ItemLabel( + val group: ItemLabelGroup, + ) : ShoppingListItemState() data class ExistingItem( val item: GetShoppingListItemResponse, @@ -30,16 +34,23 @@ val ShoppingListItemState.id: String get() = when (this) { is ShoppingListItemState.ExistingItem -> item.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 get() = when (this) { is ShoppingListItemState.ExistingItem -> item.checked is ShoppingListItemState.NewItem -> false + is ShoppingListItemState.ItemLabel -> false } val ShoppingListItemState.position: Int get() = when (this) { is ShoppingListItemState.ExistingItem -> item.position is ShoppingListItemState.NewItem -> item.position + is ShoppingListItemState.ItemLabel -> -1 } diff --git a/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/ui/details/ShoppingListViewModel.kt b/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/ui/details/ShoppingListViewModel.kt index 4360c82..6f4f339 100644 --- a/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/ui/details/ShoppingListViewModel.kt +++ b/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/ui/details/ShoppingListViewModel.kt @@ -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.ShoppingListsRepo 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.LoadingState import gq.kirmanak.mealient.ui.util.LoadingStateNoData @@ -134,7 +135,7 @@ internal class ShoppingListViewModel @Inject constructor( ShoppingListScreenState( name = data.shoppingList.name, listId = data.shoppingList.id, - items = items, + items = groupItemsByLabel(items), foods = data.foods.sortedBy { it.name }, units = data.units.sortedBy { it.name }, ) diff --git a/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/util/ShopingListLabelHelper.kt b/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/util/ShopingListLabelHelper.kt new file mode 100644 index 0000000..6d52912 --- /dev/null +++ b/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/util/ShopingListLabelHelper.kt @@ -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 +): List { + + // 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, + items: List, + 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() + + 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 +} \ No newline at end of file diff --git a/features/shopping_lists/src/main/res/values-de/strings.xml b/features/shopping_lists/src/main/res/values-de/strings.xml index c75d3e3..82247ba 100644 --- a/features/shopping_lists/src/main/res/values-de/strings.xml +++ b/features/shopping_lists/src/main/res/values-de/strings.xml @@ -33,4 +33,5 @@ Eingabe löschen Entfernen von %1$s Bestätigen Sie, dass Sie die Einkaufsliste mit allen Artikeln entfernen möchten. + Sonstiges diff --git a/features/shopping_lists/src/main/res/values-es/strings.xml b/features/shopping_lists/src/main/res/values-es/strings.xml index 930b9d1..0a53d73 100644 --- a/features/shopping_lists/src/main/res/values-es/strings.xml +++ b/features/shopping_lists/src/main/res/values-es/strings.xml @@ -33,4 +33,5 @@ Borrar entrada Eliminación de %1$s Confirme que desea eliminar la lista de la compra incluyendo todos los artículos. + Otros diff --git a/features/shopping_lists/src/main/res/values-fr/strings.xml b/features/shopping_lists/src/main/res/values-fr/strings.xml index 1ba6da0..fcbd804 100644 --- a/features/shopping_lists/src/main/res/values-fr/strings.xml +++ b/features/shopping_lists/src/main/res/values-fr/strings.xml @@ -33,4 +33,5 @@ Effacer l\'entrée Suppression de %1$s Confirmez que vous souhaitez supprimer la liste d\'achats, y compris tous les articles. + Autre diff --git a/features/shopping_lists/src/main/res/values-nl/strings.xml b/features/shopping_lists/src/main/res/values-nl/strings.xml index e71628a..90d8b29 100644 --- a/features/shopping_lists/src/main/res/values-nl/strings.xml +++ b/features/shopping_lists/src/main/res/values-nl/strings.xml @@ -33,4 +33,5 @@ Invoer wissen Verwijderen %1$s Bevestig dat je de boodschappenlijst inclusief alle items wilt verwijderen. + Overig diff --git a/features/shopping_lists/src/main/res/values-pt/strings.xml b/features/shopping_lists/src/main/res/values-pt/strings.xml index 3200498..51ea2f6 100644 --- a/features/shopping_lists/src/main/res/values-pt/strings.xml +++ b/features/shopping_lists/src/main/res/values-pt/strings.xml @@ -33,4 +33,5 @@ Limpar entrada Remoção de %1$s Confirme que pretende remover a lista de compras, incluindo todos os artigos. + Outros diff --git a/features/shopping_lists/src/main/res/values-ru/strings.xml b/features/shopping_lists/src/main/res/values-ru/strings.xml index 6df6d16..3c609ec 100644 --- a/features/shopping_lists/src/main/res/values-ru/strings.xml +++ b/features/shopping_lists/src/main/res/values-ru/strings.xml @@ -33,4 +33,5 @@ Очистить ввод Удаление %1$s Подтвердите, что вы хотите удалить список покупок, включая все товары. + Другое diff --git a/features/shopping_lists/src/main/res/values/strings.xml b/features/shopping_lists/src/main/res/values/strings.xml index c816921..f26ec42 100644 --- a/features/shopping_lists/src/main/res/values/strings.xml +++ b/features/shopping_lists/src/main/res/values/strings.xml @@ -35,5 +35,6 @@ Clear input Removing %1$s Confirm that you want to remove the shopping list including all items. + Other \ No newline at end of file diff --git a/features/shopping_lists/src/test/kotlin/gq/kirmanak/mealient/shopping_lists/ui/details/ShoppingListViewModelTest.kt b/features/shopping_lists/src/test/kotlin/gq/kirmanak/mealient/shopping_lists/ui/details/ShoppingListViewModelTest.kt index 7cd89be..fb94fcf 100644 --- a/features/shopping_lists/src/test/kotlin/gq/kirmanak/mealient/shopping_lists/ui/details/ShoppingListViewModelTest.kt +++ b/features/shopping_lists/src/test/kotlin/gq/kirmanak/mealient/shopping_lists/ui/details/ShoppingListViewModelTest.kt @@ -1,6 +1,7 @@ package gq.kirmanak.mealient.shopping_lists.ui.details import androidx.lifecycle.SavedStateHandle +import gq.kirmanak.mealient.datasource.models.GetItemLabelResponse import gq.kirmanak.mealient.datasource.models.GetFoodResponse import gq.kirmanak.mealient.datasource.models.GetShoppingListItemRecipeReferenceResponse 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.shopping_lists.repo.ShoppingListsAuthRepo 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.ui.util.LoadingHelper import gq.kirmanak.mealient.ui.util.LoadingHelperFactory @@ -134,7 +136,50 @@ internal class ShoppingListViewModelTest : BaseUnitTest() { 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( id = "1", @@ -164,6 +209,7 @@ private val milk = GetShoppingListItemResponse( quantity = 500.0, unit = mlUnit, food = milkFood, + label = milkLabel, recipeReferences = listOf( GetShoppingListItemRecipeReferenceResponse( recipeId = "1", @@ -176,7 +222,7 @@ private val shoppingListResponse = GetShoppingListResponse( id = "shoppingListId", groupId = "shoppingListGroupId", name = "shoppingListName", - listItems = listOf(blackTeaBags, milk), + listItems = listOf(blackTeaBags, milk, bread, apple), recipeReferences = listOf() ) @@ -190,14 +236,32 @@ private val shoppingListScreen = ShoppingListScreenState( name = "shoppingListName", listId = "shoppingListId", items = listOf( + ShoppingListItemState.ItemLabel( + group = ItemLabelGroup.Label(apple.label + ?: GetItemLabelResponse(name = "", "", "", "")), + ), + ShoppingListItemState.ExistingItem( + item = apple, + isEditing = false + ), + ShoppingListItemState.ItemLabel( + group = ItemLabelGroup.DefaultLabel, + ), ShoppingListItemState.ExistingItem( item = blackTeaBags, isEditing = false ), + ShoppingListItemState.ExistingItem( + item = bread, + isEditing = false + ), + ShoppingListItemState.ItemLabel( + group = ItemLabelGroup.CheckedItems + ), ShoppingListItemState.ExistingItem( item = milk, isEditing = false - ) + ), ), foods = listOf(milkFood), units = listOf(mlUnit) diff --git a/ui/src/main/kotlin/gq/kirmanak/mealient/ui/components/LazyColumnWithLoadingState.kt b/ui/src/main/kotlin/gq/kirmanak/mealient/ui/components/LazyColumnWithLoadingState.kt index 4e67ceb..54583fc 100644 --- a/ui/src/main/kotlin/gq/kirmanak/mealient/ui/components/LazyColumnWithLoadingState.kt +++ b/ui/src/main/kotlin/gq/kirmanak/mealient/ui/components/LazyColumnWithLoadingState.kt @@ -92,4 +92,3 @@ fun LazyColumnWithLoadingState( } } } -