From 3966901da2b740010e15bd36295963e9b8866d21 Mon Sep 17 00:00:00 2001 From: jonasgaudian <43753916+jonasgaudian@users.noreply.github.com> Date: Tue, 17 Feb 2026 22:23:12 +0100 Subject: [PATCH] Implement intelligent merging for duplicate vocabulary items --- .../viewmodel/VocabularyViewModel.kt | 117 ++++++++++++++++-- 1 file changed, 107 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/eu/gaudian/translator/viewmodel/VocabularyViewModel.kt b/app/src/main/java/eu/gaudian/translator/viewmodel/VocabularyViewModel.kt index aa6ce81..d4780b0 100644 --- a/app/src/main/java/eu/gaudian/translator/viewmodel/VocabularyViewModel.kt +++ b/app/src/main/java/eu/gaudian/translator/viewmodel/VocabularyViewModel.kt @@ -335,7 +335,6 @@ class VocabularyViewModel @Inject constructor( } - fun deleteVocabularyItemsById(list: List) { Log.d(TAG, "Deleting vocabulary items with IDs: $list") viewModelScope.launch { @@ -407,18 +406,116 @@ class VocabularyViewModel @Inject constructor( } /** - * Handles the merging of two duplicate vocabulary items. - * Placeholder logic: Deletes the new item, effectively keeping the original. - * A full implementation would merge properties like stage, categories, and features. + * Handles the merging of two duplicate vocabulary items intelligently. + * Merges learning progress (stages, counts), categories, and features. + * Keeps the existing item and updates it with merged data. + * + * @param newItem The duplicate item to merge (will be deleted) + * @param existingItem The original item to keep and update */ fun mergeDuplicateItems(newItem: VocabularyItem, existingItem: VocabularyItem) { - Log.d(TAG, "mergeDuplicateItems: Merging ${newItem.id} into ${existingItem.id}. (Placeholder logic)") - //TODO - // Placeholder: For now, this just deletes the new item. - // A full implementation would merge stages, categories, etc., and update the existing item based on the rules. + Log.d(TAG, "mergeDuplicateItems: Intelligently merging ${newItem.id} into ${existingItem.id}") viewModelScope.launch { - statusService.showSuccessMessage("Items merged!", 2) - deleteVocabularyItemsById(listOf(newItem.id)) + try { + // 1. Get states for both items + val newState = vocabularyRepository.getVocabularyItemStateById(newItem.id) + val existingState = vocabularyRepository.getVocabularyItemStateById(existingItem.id) + + // 2. Get stages for both items + val newStage = vocabularyRepository.getVocabularyItemStage(newItem.id).first() + val existingStage = vocabularyRepository.getVocabularyItemStage(existingItem.id).first() + + // 3. Merge learning progress - keep the better stage (higher in progression) + val stageOrder = listOf( + VocabularyStage.NEW, VocabularyStage.STAGE_1, VocabularyStage.STAGE_2, + VocabularyStage.STAGE_3, VocabularyStage.STAGE_4, VocabularyStage.STAGE_5, + VocabularyStage.LEARNED + ) + val mergedStage = if (stageOrder.indexOf(newStage) > stageOrder.indexOf(existingStage)) { + newStage + } else { + existingStage + } + + // 4. Merge answer counts and timestamps - keep higher counts and most recent timestamps + val mergedCorrectCount = (newState?.correctAnswerCount ?: 0) + (existingState?.correctAnswerCount ?: 0) + val mergedIncorrectCount = (newState?.incorrectAnswerCount ?: 0) + (existingState?.incorrectAnswerCount ?: 0) + + val mergedLastCorrect = listOfNotNull( + newState?.lastCorrectAnswer, + existingState?.lastCorrectAnswer + ).maxOrNull() + + val mergedLastIncorrect = listOfNotNull( + newState?.lastIncorrectAnswer, + existingState?.lastIncorrectAnswer + ).maxOrNull() + + // 5. Merge features - prefer non-null, non-empty features + val mergedFeatures = when { + !existingItem.features.isNullOrBlank() -> existingItem.features + !newItem.features.isNullOrBlank() -> newItem.features + else -> null + } + + // 6. Merge zipf frequencies - prefer non-null values + val mergedZipfFirst = existingItem.zipfFrequencyFirst ?: newItem.zipfFrequencyFirst + val mergedZipfSecond = existingItem.zipfFrequencySecond ?: newItem.zipfFrequencySecond + + // 7. Keep the earlier creation date + val mergedCreatedAt = listOfNotNull( + newItem.createdAt, + existingItem.createdAt + ).minOrNull() ?: existingItem.createdAt + + // 8. Get categories for both items and merge them + val allMappings = vocabularyRepository.getCategoryMappings() + val newCategories = allMappings.filter { it.vocabularyItemId == newItem.id }.map { it.categoryId } + val existingCategories = allMappings.filter { it.vocabularyItemId == existingItem.id }.map { it.categoryId } + val mergedCategories = (newCategories + existingCategories).distinct() + + // 9. Update the existing item with merged data + val updatedItem = existingItem.copy( + features = mergedFeatures, + zipfFrequencyFirst = mergedZipfFirst, + zipfFrequencySecond = mergedZipfSecond, + createdAt = mergedCreatedAt + ) + + vocabularyRepository.editVocabularyItem(updatedItem) + + // 10. Update stage if it changed + if (mergedStage != existingStage) { + vocabularyRepository.changeVocabularyItemStage(updatedItem, mergedStage) + } + + // 11. Update state with merged progress + val mergedState = eu.gaudian.translator.model.VocabularyItemState( + vocabularyItemId = existingItem.id, + lastCorrectAnswer = mergedLastCorrect, + lastIncorrectAnswer = mergedLastIncorrect, + correctAnswerCount = mergedCorrectCount, + incorrectAnswerCount = mergedIncorrectCount + ) + vocabularyRepository.saveVocabularyItemState(mergedState) + + // 12. Add any new categories to the existing item + val categoriesToAdd = mergedCategories - existingCategories.toSet() + categoriesToAdd.forEach { categoryId -> + vocabularyRepository.addVocabularyItemToList(existingItem.id, categoryId) + } + + // 13. Finally, delete the duplicate item + deleteVocabularyItemsById(listOf(newItem.id)) + + statusService.showSuccessMessage("Items merged successfully! Progress and categories preserved.", 3) + Log.i(TAG, "mergeDuplicateItems: Successfully merged ${newItem.id} into ${existingItem.id}. " + + "Stage: $existingStage→$mergedStage, Correct: ${existingState?.correctAnswerCount ?: 0}→$mergedCorrectCount, " + + "Categories: ${existingCategories.size}→${mergedCategories.size}") + } catch (e: Exception) { + statusService.showErrorMessage("Error merging items: ${e.message}") + Log.e(TAG, "Error in mergeDuplicateItems: ${e.message}") + } } }