From 868669484a099bab91a71ca288b0b549b1ba5502 Mon Sep 17 00:00:00 2001 From: wcjord <32568597+wcjord@users.noreply.github.com> Date: Sat, 5 Apr 2025 12:43:47 -0400 Subject: [PATCH] fix(emoji_activity_generator): ensure unique choices --- .../practice_activities/practice_match.dart | 66 +++++++++++-------- .../practice_selection.dart | 7 +- .../practice_selection_repo.dart | 44 +++++++++---- .../widgets/message_selection_overlay.dart | 14 ++-- 4 files changed, 81 insertions(+), 50 deletions(-) diff --git a/lib/pangea/practice_activities/practice_match.dart b/lib/pangea/practice_activities/practice_match.dart index 06ea971f4..58fa684a1 100644 --- a/lib/pangea/practice_activities/practice_match.dart +++ b/lib/pangea/practice_activities/practice_match.dart @@ -1,9 +1,8 @@ -import 'package:flutter/material.dart'; - import 'package:collection/collection.dart'; - +import 'package:fluffychat/pangea/common/utils/error_handler.dart'; import 'package:fluffychat/pangea/constructs/construct_form.dart'; import 'package:fluffychat/pangea/practice_activities/practice_choice.dart'; +import 'package:flutter/material.dart'; class PracticeMatchActivity { /// The constructIdenfifiers involved in the activity @@ -20,35 +19,50 @@ class PracticeMatchActivity { 'Construct: ${ith.key}, Forms: ${ith.value}', ); } - // if there are multiple forms for a construct, pick one to display - // each cosntruct will have ~3 forms - // sometimes a form could be in multiple constructs - // so we need to make sure we don't display the same form twice - // if we get to one that is already displayed, we can pick a different form - // either from that construct's options, or returning to the previous construct - // and picking a different form from there + // for all the entries in matchInfo, remove an Strings that appear in multiple entries + final Map allForms = {}; for (final ith in matchInfo.entries) { - for (int i = 0; i < ith.value.length; i++) { - final String acceptableAnswer = ith.value[i]; - if (!choices - .any((element) => element.choiceContent == acceptableAnswer)) { - choices.add( - PracticeChoice(choiceContent: acceptableAnswer, form: ith.key), - ); - debugPrint( - 'Added choice: ${choices.last.choiceContent} for form: ${choices.last.form.form}', - ); - i = ith.value.length; // break out of the loop + for (final form in ith.value) { + if (allForms.containsKey(form)) { + allForms[form] = allForms[form]! + 1; + } else { + allForms[form] = 1; } - // TODO: if none found, we can probably pick a different form for the other one } } - // remove any items from matchInfo that don't have an item in choices - for (final ith in matchInfo.keys) { - if (!choices.any((choice) => choice.form == ith)) { - matchInfo.remove(ith); + for (final ith in matchInfo.entries) { + if (ith.value.isEmpty) { + matchInfo.remove(ith.key); + continue; } + choices.add( + PracticeChoice( + choiceContent: ith.value.firstWhere( + (element) => allForms[element] == 1, + orElse: () { + ErrorHandler.logError( + m: "no unique emoji for construct", + data: { + 'construct': ith.key, + 'forms': ith.value, + "practice_match": toJson(), + }, + ); + final String first = ith.value.first; + // remove the element from the other entry to avoid duplicates + for (final ith in matchInfo.entries) { + ith.value.removeWhere((choice) => choice == first); + } + return ith.value.first; + }, + ), + form: ith.key, + ), + ); + debugPrint( + 'Added PracticeChoice Construct: ${ith.key}, Forms: ${ith.value}', + ); } } diff --git a/lib/pangea/practice_activities/practice_selection.dart b/lib/pangea/practice_activities/practice_selection.dart index 5a1eeaed8..f6f67fd08 100644 --- a/lib/pangea/practice_activities/practice_selection.dart +++ b/lib/pangea/practice_activities/practice_selection.dart @@ -1,9 +1,6 @@ import 'dart:developer'; -import 'package:flutter/foundation.dart'; - import 'package:collection/collection.dart'; - import 'package:fluffychat/pangea/events/models/pangea_token_model.dart'; import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart'; import 'package:fluffychat/pangea/lemmas/lemma_info_response.dart'; @@ -12,6 +9,7 @@ import 'package:fluffychat/pangea/practice_activities/activity_type_enum.dart'; import 'package:fluffychat/pangea/practice_activities/practice_selection_repo.dart'; import 'package:fluffychat/pangea/practice_activities/practice_target.dart'; import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/foundation.dart'; class PracticeSelection { late String _userL2; @@ -41,7 +39,8 @@ class PracticeSelection { List get tokens => _tokens; bool get eligibleForPractice => - _tokens.any((t) => t.lemma.saveVocab) && langCode == _userL2; + _tokens.any((t) => t.lemma.saveVocab) && + langCode.split("-")[0] == _userL2.split("-")[0]; String get messageText => PangeaToken.reconstructText(tokens); diff --git a/lib/pangea/practice_activities/practice_selection_repo.dart b/lib/pangea/practice_activities/practice_selection_repo.dart index b80bbb7a3..5198b0c4e 100644 --- a/lib/pangea/practice_activities/practice_selection_repo.dart +++ b/lib/pangea/practice_activities/practice_selection_repo.dart @@ -1,9 +1,8 @@ -import 'package:flutter/material.dart'; - -import 'package:get_storage/get_storage.dart'; - +import 'package:fluffychat/pangea/common/utils/error_handler.dart'; import 'package:fluffychat/pangea/events/models/pangea_token_model.dart'; import 'package:fluffychat/pangea/practice_activities/practice_selection.dart'; +import 'package:flutter/material.dart'; +import 'package:get_storage/get_storage.dart'; class PracticeSelectionRepo { static final GetStorage _storage = GetStorage('practice_selection_cache'); @@ -21,18 +20,39 @@ class PracticeSelectionRepo { _memoryCache[key] = entry; } + static MapEntry? _parsePracticeSelection( + String key) { + if (!_storage.hasData(key)) { + return null; + } + try { + final entry = PracticeSelection.fromJson(_storage.read(key)); + return MapEntry(key, entry); + } catch (e, s) { + ErrorHandler.logError( + m: 'Failed to parse PracticeSelection from JSON', + e: e, + s: s, + data: { + 'key': key, + 'json': _storage.read(key), + }, + ); + _storage.remove(key); + return null; + } + } + static void clean() { final Iterable keys = _storage.getKeys(); if (keys.length > 300) { final entries = keys - .map((key) { - final entry = PracticeSelection.fromJson(_storage.read(key)); - return MapEntry(key, entry); - }) + .map((key) => _parsePracticeSelection(key)) + .where((entry) => entry != null) .cast>() .toList() ..sort((a, b) => a.value.createdAt.compareTo(b.value.createdAt)); - for (var i = 0; i < 5; i++) { + for (var i = 0; i < 5 && i < entries.length; i++) { _storage.remove(entries[i].key); } } @@ -53,9 +73,9 @@ class PracticeSelectionRepo { return _memoryCache[key]; } - final entryJson = _storage.read(key); - if (entryJson != null) { - final entry = PracticeSelection.fromJson(entryJson); + final stored = _parsePracticeSelection(key); + if (stored != null) { + final entry = stored.value; if (DateTime.now().difference(entry.createdAt).inDays > 1) { debugPrint('removing old entry ${entry.createdAt}'); _storage.remove(key); diff --git a/lib/pangea/toolbar/widgets/message_selection_overlay.dart b/lib/pangea/toolbar/widgets/message_selection_overlay.dart index b1b903c2c..a9989ed55 100644 --- a/lib/pangea/toolbar/widgets/message_selection_overlay.dart +++ b/lib/pangea/toolbar/widgets/message_selection_overlay.dart @@ -1,13 +1,7 @@ import 'dart:async'; import 'dart:developer'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/scheduler.dart'; - import 'package:collection/collection.dart'; -import 'package:matrix/matrix.dart'; - import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pages/chat/chat.dart'; import 'package:fluffychat/pangea/common/utils/error_handler.dart'; @@ -31,6 +25,10 @@ import 'package:fluffychat/pangea/toolbar/reading_assistance_input_row/morph_sel import 'package:fluffychat/pangea/toolbar/widgets/message_selection_positioner.dart'; import 'package:fluffychat/pangea/toolbar/widgets/reading_assistance_content.dart'; import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:matrix/matrix.dart'; /// Controls data at the top level of the toolbar (mainly token / toolbar mode selection) class MessageSelectionOverlay extends StatefulWidget { @@ -423,8 +421,8 @@ class MessageOverlayController extends State : null; bool get messageInUserL2 => - pangeaMessageEvent?.messageDisplayLangCode == - MatrixState.pangeaController.languageController.userL2?.langCode; + pangeaMessageEvent?.messageDisplayLangCode.split("-")[0] == + MatrixState.pangeaController.languageController.userL2?.langCodeShort; PangeaToken? get selectedToken => pangeaMessageEvent?.messageDisplayRepresentation?.tokens