feat: increment catalog version from 4 to 5 with comprehensive grocery domain restructuring and expanded item coverage

- Bump catalog version to 5
- Reorganize grocery domain structure with refined category hierarchy
- Expand item definitions with enhanced metadata, aliases, and tagging
- Add comprehensive bilingual support across all domains and categories
- Include detailed emoji, color, and sort order specifications for improved UI presentation
- Extend coverage across fruits, vegetables, grains, condiments, beverages, dairy, meat, produce, and specialty items
This commit is contained in:
Bruno Charest 2026-04-30 08:48:33 -04:00
parent d569c344b0
commit 49eafca209
4 changed files with 7030 additions and 7002 deletions

View File

@ -990,6 +990,7 @@ private fun SuggestionRow(
is ListDetailViewModel.Suggestion.Active -> "Sur la liste" to MaterialTheme.colorScheme.tertiary is ListDetailViewModel.Suggestion.Active -> "Sur la liste" to MaterialTheme.colorScheme.tertiary
is ListDetailViewModel.Suggestion.Recent -> "Recently Used" to LocalStatusColors.current.safe is ListDetailViewModel.Suggestion.Recent -> "Recently Used" to LocalStatusColors.current.safe
is ListDetailViewModel.Suggestion.Catalog -> suggestion.item.category to MaterialTheme.colorScheme.onSurfaceVariant is ListDetailViewModel.Suggestion.Catalog -> suggestion.item.category to MaterialTheme.colorScheme.onSurfaceVariant
is ListDetailViewModel.Suggestion.RoomCatalog -> (suggestion.categoryName ?: "Catalogue") to MaterialTheme.colorScheme.onSurfaceVariant
is ListDetailViewModel.Suggestion.Create -> "Créer" to MaterialTheme.colorScheme.primary is ListDetailViewModel.Suggestion.Create -> "Créer" to MaterialTheme.colorScheme.primary
} }
Row( Row(

View File

@ -18,6 +18,7 @@ import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -87,6 +88,10 @@ class ListDetailViewModel @Inject constructor(
override val label: String = item.name override val label: String = item.name
override val emoji: String = item.emoji override val emoji: String = item.emoji
} }
data class RoomCatalog(val item: CatalogItemEntity, val categoryName: String?) : Suggestion() {
override val label: String = item.name
override val emoji: String = item.emoji
}
data class Recent(val item: ShoppingListItemUi) : Suggestion() { data class Recent(val item: ShoppingListItemUi) : Suggestion() {
override val label: String = item.productName override val label: String = item.productName
override val emoji: String = item.emoji override val emoji: String = item.emoji
@ -152,31 +157,51 @@ class ListDetailViewModel @Inject constructor(
val suggestions: StateFlow<List<Suggestion>> = combine( val suggestions: StateFlow<List<Suggestion>> = combine(
_searchQuery, _searchQuery,
state state
) { rawQuery, uiState -> ) { rawQuery, uiState -> rawQuery to uiState }
.flatMapLatest { (rawQuery, uiState) ->
val query = rawQuery.trim() val query = rawQuery.trim()
if (query.isEmpty()) return@combine emptyList() if (query.isEmpty() || uiState !is UiState.Ready) {
return@flatMapLatest flowOf(emptyList())
}
val ready = uiState as? UiState.Ready ?: return@combine emptyList() val ready = uiState
val results = mutableListOf<Suggestion>() val staticResults = mutableListOf<Suggestion>()
// 1) Articles déjà sur la liste active (priorité haute pour rappel) // 1) Articles déjà sur la liste active (priorité haute pour rappel)
ready.activeItems ready.activeItems
.filter { it.productName.contains(query, ignoreCase = true) } .filter { it.productName.contains(query, ignoreCase = true) }
.take(2) .take(2)
.forEach { results.add(Suggestion.Active(it)) } .forEach { staticResults.add(Suggestion.Active(it)) }
// 2) Recently used → restauration rapide // 2) Recently used → restauration rapide
ready.recentlyUsed ready.recentlyUsed
.filter { it.productName.contains(query, ignoreCase = true) } .filter { it.productName.contains(query, ignoreCase = true) }
.take(3) .take(3)
.forEach { results.add(Suggestion.Recent(it)) } .forEach { staticResults.add(Suggestion.Recent(it)) }
// 3) Catalogue // 3) Catalogue statique (fallback si absent de la DB)
catalog.search(query, limit = 6) val staticCatalogItems = catalog.search(query, limit = 20)
.filter { item -> .filter { item -> staticResults.none { it.label.equals(item.name, ignoreCase = true) } }
results.none { it.label.equals(item.name, ignoreCase = true) }
val categoryMap = catalogDomains.value.flatMap { it.categoriesWithItems }
.associate { it.category.categoryId to it.category.name }
// 3bis) Catalogue Room (tous domaines/catégories + items futurs)
catalogRepository.search(query, limit = 20).map { dbItems ->
val results = staticResults.toMutableList()
dbItems.forEach { item ->
if (results.none { it.label.equals(item.name, ignoreCase = true) }) {
val catName = item.primaryCategoryId?.let { categoryMap[it] } ?: "Catalogue"
results.add(Suggestion.RoomCatalog(item, catName))
}
}
staticCatalogItems.forEach { item ->
if (results.none { it.label.equals(item.name, ignoreCase = true) }) {
results.add(Suggestion.Catalog(item))
}
} }
.forEach { results.add(Suggestion.Catalog(it)) }
// 4) Création d'un own item si aucune correspondance exacte // 4) Création d'un own item si aucune correspondance exacte
val exact = results.any { it.label.equals(query, ignoreCase = true) } val exact = results.any { it.label.equals(query, ignoreCase = true) }
@ -184,6 +209,7 @@ class ListDetailViewModel @Inject constructor(
results.add(0, Suggestion.Create(query)) results.add(0, Suggestion.Create(query))
} }
results results
}
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList()) }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())
// ── Actions ───────────────────────────────────────────────────────────── // ── Actions ─────────────────────────────────────────────────────────────
@ -203,6 +229,7 @@ class ListDetailViewModel @Inject constructor(
fun applySuggestion(suggestion: Suggestion) { fun applySuggestion(suggestion: Suggestion) {
when (suggestion) { when (suggestion) {
is Suggestion.Catalog -> addCatalogItem(suggestion.item) is Suggestion.Catalog -> addCatalogItem(suggestion.item)
is Suggestion.RoomCatalog -> addCatalogItem(suggestion.item)
is Suggestion.Recent -> restoreItem(suggestion.item.id) is Suggestion.Recent -> restoreItem(suggestion.item.id)
is Suggestion.Active -> { /* déjà sur la liste, ne fait rien */ } is Suggestion.Active -> { /* déjà sur la liste, ne fait rien */ }
is Suggestion.Create -> addCustomItem(suggestion.rawText) is Suggestion.Create -> addCustomItem(suggestion.rawText)

View File

@ -1,4 +1,4 @@
MAJOR=1 MAJOR=1
MINOR=22 MINOR=24
PATCH=0 PATCH=0
CODE=33 CODE=35