fix(emoji_activity_generator): ensure unique choices

This commit is contained in:
wcjord 2025-04-05 12:43:47 -04:00
parent 3acab885d9
commit 868669484a
4 changed files with 81 additions and 50 deletions

View file

@ -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<String, int> 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}',
);
}
}

View file

@ -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<PangeaToken> 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);

View file

@ -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<String, PracticeSelection>? _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<String> 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<MapEntry<String, PracticeSelection>>()
.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);

View file

@ -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<MessageSelectionOverlay>
: 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