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

File diff suppressed because it is too large Load Diff

View File

@ -990,6 +990,7 @@ private fun SuggestionRow(
is ListDetailViewModel.Suggestion.Active -> "Sur la liste" to MaterialTheme.colorScheme.tertiary
is ListDetailViewModel.Suggestion.Recent -> "Recently Used" to LocalStatusColors.current.safe
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
}
Row(

View File

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

View File

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