Merge pull request #4885 from pangeachat/4877-add-full-message-info-to-lemma-info-request-whenever-available

feat: send message info in lemma info request
This commit is contained in:
ggurdin 2025-12-18 11:46:41 -05:00 committed by GitHub
commit 27358a1472
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 79 additions and 31 deletions

View file

@ -63,6 +63,7 @@ class VocabDetailsEmojiSelectorState extends State<VocabDetailsEmojiSelector>
langCode: MatrixState.pangeaController.userController.userL2Code!,
emoji: selectedEmoji,
onEmojiSelected: _setEmoji,
messageInfo: const {},
);
}
}

View file

@ -151,7 +151,8 @@ class ConstructIdentifier {
uses: [],
);
LemmaInfoRequest get lemmaInfoRequest => LemmaInfoRequest(
LemmaInfoRequest lemmaInfoRequest(Map<String, dynamic> messageInfo) =>
LemmaInfoRequest(
partOfSpeech: category,
lemmaLang:
MatrixState.pangeaController.userController.userL2?.langCodeShort ??
@ -160,12 +161,16 @@ class ConstructIdentifier {
MatrixState.pangeaController.userController.userL1?.langCodeShort ??
LanguageKeys.defaultLanguage,
lemma: lemma,
messageInfo: messageInfo,
);
/// [lemmmaLang] if not set, assumed to be userL2
Future<Result<LemmaInfoResponse>> getLemmaInfo() => LemmaInfoRepo.get(
Future<Result<LemmaInfoResponse>> getLemmaInfo(
Map<String, dynamic> messageInfo,
) =>
LemmaInfoRepo.get(
MatrixState.pangeaController.userController.accessToken,
lemmaInfoRequest,
lemmaInfoRequest(messageInfo),
);
List<String> get userSetEmoji => userLemmaInfo.emojis ?? [];

View file

@ -6,7 +6,7 @@ abstract class JsonSerializable {
}
class ContentFeedback<T extends JsonSerializable> {
final JsonSerializable content;
final T content;
final String feedback;
ContentFeedback(this.content, this.feedback);

View file

@ -18,6 +18,7 @@ class LemmaHighlightEmojiRow extends StatefulWidget {
final String langCode;
final Function(String) onEmojiSelected;
final Map<String, dynamic> messageInfo;
final String? emoji;
final Widget? selectedEmojiBadge;
@ -27,6 +28,7 @@ class LemmaHighlightEmojiRow extends StatefulWidget {
required this.cId,
required this.langCode,
required this.onEmojiSelected,
required this.messageInfo,
this.emoji,
this.selectedEmojiBadge,
});
@ -63,6 +65,7 @@ class LemmaHighlightEmojiRowState extends State<LemmaHighlightEmojiRow> {
return LemmaMeaningBuilder(
langCode: widget.langCode,
constructId: widget.cId,
messageInfo: widget.messageInfo,
builder: (context, controller) {
if (controller.error != null) {
return const SizedBox.shrink();

View file

@ -34,7 +34,9 @@ class LemmaInfoRepo {
static Future<Result<LemmaInfoResponse>> get(
String accessToken,
LemmaInfoRequest request,
) {
) async {
await GetStorage.init('lemma_storage');
// 1. Try memory cache
final cached = _getCached(request);
if (cached != null) {
@ -44,7 +46,7 @@ class LemmaInfoRepo {
// 2. Try disk cache
final stored = _getStored(request);
if (stored != null) {
return Future.value(Result.value(stored));
return Result.value(stored);
}
// 3. Fetch from network (safe future)
@ -66,6 +68,8 @@ class LemmaInfoRepo {
LemmaInfoRequest request,
LemmaInfoResponse resultFuture,
) async {
await GetStorage.init('lemma_storage');
final key = request.hashCode.toString();
try {
await _storage.write(key, resultFuture.toJson());

View file

@ -1,6 +1,7 @@
import 'package:collection/collection.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
import 'package:fluffychat/pangea/constructs/construct_identifier.dart';
import 'package:fluffychat/pangea/events/models/content_feedback.dart';
import 'package:fluffychat/pangea/lemmas/lemma_info_response.dart';
class LemmaInfoRequest {
@ -8,14 +9,16 @@ class LemmaInfoRequest {
final String partOfSpeech;
final String lemmaLang;
final String userL1;
final Map<String, dynamic> messageInfo;
List<ContentFeedback<LemmaInfoResponse>> feedback;
List<LemmaInfoResponse> feedback;
LemmaInfoRequest({
required String partOfSpeech,
required String lemmaLang,
required this.userL1,
required this.lemma,
required this.messageInfo,
this.feedback = const [],
}) : partOfSpeech = partOfSpeech.toLowerCase(),
lemmaLang = lemmaLang.toLowerCase();
@ -27,6 +30,7 @@ class LemmaInfoRequest {
'lemma_lang': lemmaLang,
'user_l1': userL1,
'feedback': feedback.map((e) => e.toJson()).toList(),
'message_info': messageInfo,
};
}
@ -37,11 +41,15 @@ class LemmaInfoRequest {
runtimeType == other.runtimeType &&
lemma == other.lemma &&
partOfSpeech == other.partOfSpeech &&
feedback == other.feedback;
const ListEquality().equals(feedback, other.feedback) &&
userL1 == other.userL1;
@override
int get hashCode =>
lemma.hashCode ^ partOfSpeech.hashCode ^ feedback.hashCode;
lemma.hashCode ^
partOfSpeech.hashCode ^
const ListEquality().hash(feedback) ^
userL1.hashCode;
String get storageKey {
return 'l:$lemma,p:$partOfSpeech,lang:$lemmaLang,l1:$userL1';

View file

@ -1,3 +1,5 @@
import 'package:collection/collection.dart';
import 'package:fluffychat/pangea/events/models/content_feedback.dart';
class LemmaInfoResponse implements JsonSerializable {
@ -35,12 +37,9 @@ class LemmaInfoResponse implements JsonSerializable {
identical(this, other) ||
other is LemmaInfoResponse &&
runtimeType == other.runtimeType &&
emoji.length == other.emoji.length &&
emoji.every((element) => other.emoji.contains(element)) &&
const ListEquality().equals(emoji, other.emoji) &&
meaning == other.meaning;
@override
int get hashCode =>
emoji.fold(0, (prev, element) => prev ^ element.hashCode) ^
meaning.hashCode;
int get hashCode => const ListEquality().hash(emoji) ^ meaning.hashCode;
}

View file

@ -30,6 +30,8 @@ class _LemmaMeaningLoader extends AsyncLoader<LemmaInfoResponse> {
class LemmaMeaningBuilder extends StatefulWidget {
final String langCode;
final ConstructIdentifier constructId;
final Map<String, dynamic> messageInfo;
final Widget Function(
BuildContext context,
LemmaMeaningBuilderState controller,
@ -40,6 +42,7 @@ class LemmaMeaningBuilder extends StatefulWidget {
required this.langCode,
required this.constructId,
required this.builder,
required this.messageInfo,
});
@override
@ -85,6 +88,7 @@ class LemmaMeaningBuilderState extends State<LemmaMeaningBuilder> {
lemmaLang: widget.langCode,
userL1: MatrixState.pangeaController.userController.userL1?.langCode ??
LanguageKeys.defaultLanguage,
messageInfo: widget.messageInfo,
);
void _reload() {

View file

@ -12,10 +12,12 @@ class LemmaMeaningWidget extends StatelessWidget {
final ConstructIdentifier constructId;
final TextStyle? style;
final InlineSpan? leading;
final Map<String, dynamic> messageInfo;
const LemmaMeaningWidget({
super.key,
required this.constructId,
required this.messageInfo,
this.style,
this.leading,
});
@ -25,6 +27,7 @@ class LemmaMeaningWidget extends StatelessWidget {
return LemmaMeaningBuilder(
langCode: MatrixState.pangeaController.userController.userL2!.langCode,
constructId: constructId,
messageInfo: messageInfo,
builder: (context, controller) {
if (controller.isLoading) {
return const TextLoadingShimmer();

View file

@ -34,7 +34,9 @@ class PhoneticTranscriptionRepo {
static Future<Result<PhoneticTranscriptionResponse>> get(
String accessToken,
PhoneticTranscriptionRequest request,
) {
) async {
await GetStorage.init('phonetic_transcription_storage');
// 1. Try memory cache
final cached = _getCached(request);
if (cached != null) {
@ -66,6 +68,7 @@ class PhoneticTranscriptionRepo {
PhoneticTranscriptionRequest request,
PhoneticTranscriptionResponse resultFuture,
) async {
await GetStorage.init('phonetic_transcription_storage');
final key = request.hashCode.toString();
try {
await _storage.write(key, resultFuture.toJson());

View file

@ -10,18 +10,20 @@ import 'package:fluffychat/pangea/practice_activities/practice_match.dart';
class EmojiActivityGenerator {
static Future<MessageActivityResponse> get(
MessageActivityRequest req,
) async {
MessageActivityRequest req, {
required Map<String, dynamic> messageInfo,
}) async {
if (req.targetTokens.length <= 1) {
throw Exception("Emoji activity requires at least 2 tokens");
}
return _matchActivity(req);
return _matchActivity(req, messageInfo: messageInfo);
}
static Future<MessageActivityResponse> _matchActivity(
MessageActivityRequest req,
) async {
MessageActivityRequest req, {
required Map<String, dynamic> messageInfo,
}) async {
final Map<ConstructForm, List<String>> matchInfo = {};
final List<PangeaToken> missingEmojis = [];
@ -39,7 +41,7 @@ class EmojiActivityGenerator {
final List<Future<Result<LemmaInfoResponse>>> lemmaInfoFutures =
missingEmojis
.map((token) => token.vocabConstructID.getLemmaInfo())
.map((token) => token.vocabConstructID.getLemmaInfo(messageInfo))
.toList();
final List<Result<LemmaInfoResponse>> lemmaInfos =

View file

@ -12,11 +12,12 @@ import 'package:fluffychat/widgets/future_loading_dialog.dart';
class LemmaMeaningActivityGenerator {
static Future<MessageActivityResponse> get(
MessageActivityRequest req,
) async {
MessageActivityRequest req, {
required Map<String, dynamic> messageInfo,
}) async {
final List<Future<Result<LemmaInfoResponse>>> lemmaInfoFutures = req
.targetTokens
.map((token) => token.vocabConstructID.getLemmaInfo())
.map((token) => token.vocabConstructID.getLemmaInfo(messageInfo))
.toList();
final List<Result<LemmaInfoResponse>> lemmaInfos =

View file

@ -57,8 +57,9 @@ class PracticeRepo {
/// [event] is optional and used for saving the activity event to Matrix
static Future<Result<PracticeActivityModel>> getPracticeActivity(
MessageActivityRequest req,
) async {
MessageActivityRequest req, {
required Map<String, dynamic> messageInfo,
}) async {
final cached = _getCached(req);
if (cached != null) return Result.value(cached);
@ -66,6 +67,7 @@ class PracticeRepo {
final MessageActivityResponse res = await _routePracticeActivity(
accessToken: MatrixState.pangeaController.userController.accessToken,
req: req,
messageInfo: messageInfo,
);
_setCached(req, res);
@ -109,18 +111,19 @@ class PracticeRepo {
static Future<MessageActivityResponse> _routePracticeActivity({
required String accessToken,
required MessageActivityRequest req,
required Map<String, dynamic> messageInfo,
}) async {
// some activities we'll get from the server and others we'll generate locally
switch (req.targetType) {
case ActivityTypeEnum.emoji:
return EmojiActivityGenerator.get(req);
return EmojiActivityGenerator.get(req, messageInfo: messageInfo);
case ActivityTypeEnum.lemmaId:
return LemmaActivityGenerator.get(req);
case ActivityTypeEnum.morphId:
return MorphActivityGenerator.get(req);
case ActivityTypeEnum.wordMeaning:
debugger(when: kDebugMode);
return LemmaMeaningActivityGenerator.get(req);
return LemmaMeaningActivityGenerator.get(req, messageInfo: messageInfo);
case ActivityTypeEnum.messageMeaning:
case ActivityTypeEnum.wordFocusListening:
return WordFocusListeningGenerator.get(req);

View file

@ -117,7 +117,7 @@ class TokenInfoFeedbackDialog extends StatelessWidget {
LemmaInfoResponse response,
) =>
LemmaInfoRepo.set(
token.vocabConstructID.lemmaInfoRequest,
token.vocabConstructID.lemmaInfoRequest(event.event.content),
response,
);

View file

@ -93,7 +93,10 @@ class PracticeController with ChangeNotifier {
targetMorphFeature: target.morphFeature,
);
final result = await PracticeRepo.getPracticeActivity(req);
final result = await PracticeRepo.getPracticeActivity(
req,
messageInfo: pangeaMessageEvent.event.content,
);
if (result.isValue) {
_activity = result.result;
}

View file

@ -11,12 +11,14 @@ class LemmaMeaningDisplay extends StatelessWidget {
final String langCode;
final ConstructIdentifier constructId;
final String text;
final Map<String, dynamic> messageInfo;
const LemmaMeaningDisplay({
super.key,
required this.langCode,
required this.constructId,
required this.text,
required this.messageInfo,
});
@override
@ -24,6 +26,7 @@ class LemmaMeaningDisplay extends StatelessWidget {
return LemmaMeaningBuilder(
langCode: langCode,
constructId: constructId,
messageInfo: messageInfo,
builder: (context, controller) {
if (controller.isError) {
return ErrorIndicator(

View file

@ -118,6 +118,7 @@ class LemmaReactionPickerState extends State<LemmaReactionPicker>
? _setEmoji(emoji)
: _sendOrRedactReaction(emoji),
emoji: _selectedEmoji,
messageInfo: widget.event?.content ?? {},
selectedEmojiBadge: widget.event != null &&
_selectedEmoji != null &&
_sentReaction(_selectedEmoji!) == null

View file

@ -13,6 +13,7 @@ class TokenFeedbackButton extends StatelessWidget {
final String text;
final Function(LemmaInfoResponse, String) onFlagTokenInfo;
final Map<String, dynamic> messageInfo;
const TokenFeedbackButton({
super.key,
@ -20,6 +21,7 @@ class TokenFeedbackButton extends StatelessWidget {
required this.constructId,
required this.text,
required this.onFlagTokenInfo,
required this.messageInfo,
});
@override
@ -27,6 +29,7 @@ class TokenFeedbackButton extends StatelessWidget {
return LemmaMeaningBuilder(
langCode: textLanguage.langCode,
constructId: constructId,
messageInfo: messageInfo,
builder: (context, lemmaController) {
return PhoneticTranscriptionBuilder(
textLanguage: textLanguage,

View file

@ -115,6 +115,7 @@ class WordZoomWidget extends StatelessWidget {
constructId: construct,
text: token.content,
onFlagTokenInfo: onFlagTokenInfo!,
messageInfo: event?.content ?? {},
)
: const SizedBox(
width: 40.0,
@ -152,6 +153,7 @@ class WordZoomWidget extends StatelessWidget {
langCode: langCode,
constructId: construct,
text: token.content,
messageInfo: event?.content ?? {},
),
],
),