From 58513051f7b902a0ca832c14ef5d554c2af19fa1 Mon Sep 17 00:00:00 2001 From: ggurdin <46800240+ggurdin@users.noreply.github.com> Date: Mon, 16 Feb 2026 16:00:28 -0500 Subject: [PATCH] fix: always pass pos and morph when available on request TTS (#5716) --- .../activity_vocab_widget.dart | 1 + .../vocab_analytics_details_view.dart | 34 ++++++++++++++++--- .../word_text_with_audio_button.dart | 7 ++++ .../analytics_misc/construct_use_model.dart | 3 -- .../analytics_practice_page.dart | 6 +++- .../analytics_practice_view.dart | 13 +++---- .../common/widgets/word_audio_button.dart | 7 ++++ .../phonetic_transcription_widget.dart | 4 +-- .../message_practice/practice_controller.dart | 2 ++ .../toolbar/word_card/word_zoom_widget.dart | 6 ++-- 10 files changed, 65 insertions(+), 18 deletions(-) diff --git a/lib/pangea/activity_sessions/activity_session_chat/activity_vocab_widget.dart b/lib/pangea/activity_sessions/activity_session_chat/activity_vocab_widget.dart index f0a08666a..0ae04c0ea 100644 --- a/lib/pangea/activity_sessions/activity_session_chat/activity_vocab_widget.dart +++ b/lib/pangea/activity_sessions/activity_session_chat/activity_vocab_widget.dart @@ -228,6 +228,7 @@ class _WordCardWrapperState extends State<_WordCardWrapper> { category: widget.v.pos, ), langCode: widget.langCode, + pos: widget.v.pos, onClose: () { MatrixState.pAnyState.closeOverlay(widget.target); widget.onClose(); diff --git a/lib/pangea/analytics_details_popup/vocab_analytics_details_view.dart b/lib/pangea/analytics_details_popup/vocab_analytics_details_view.dart index 51a083510..5738ccc35 100644 --- a/lib/pangea/analytics_details_popup/vocab_analytics_details_view.dart +++ b/lib/pangea/analytics_details_popup/vocab_analytics_details_view.dart @@ -55,7 +55,14 @@ class VocabDetailsView extends StatelessWidget { ? level.color(context) : level.darkColor(context)); - final forms = construct?.forms ?? []; + final forms = + construct?.uses + .where((u) => u.form != null) + .map((use) => _VocabForm(use.form!, use.category)) + .toSet() + .toList() ?? + []; + final tokenText = PangeaTokenText.fromString(constructId.lemma); final token = PangeaToken( text: tokenText, @@ -154,7 +161,7 @@ class VocabDetailsView extends StatelessWidget { class _VocabForms extends StatelessWidget { final String lemma; - final List forms; + final List<_VocabForm> forms; final Color textColor; const _VocabForms({ @@ -184,11 +191,12 @@ class _VocabForms extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ WordTextWithAudioButton( - text: form, + text: form.form, + pos: form.pos, style: Theme.of( context, ).textTheme.bodyLarge?.copyWith(color: textColor), - uniqueID: "$form-$lemma-$i", + uniqueID: "${form.form}-$lemma-$i", langCode: MatrixState.pangeaController.userController.userL2Code!, ), @@ -201,3 +209,21 @@ class _VocabForms extends StatelessWidget { ); } } + +class _VocabForm { + final String form; + final String pos; + + const _VocabForm(this.form, this.pos); + + @override + bool operator ==(Object other) => + identical(this, other) || + other is _VocabForm && + runtimeType == other.runtimeType && + form.toLowerCase() == other.form.toLowerCase() && + pos == other.pos; + + @override + int get hashCode => form.toLowerCase().hashCode ^ pos.hashCode; +} diff --git a/lib/pangea/analytics_details_popup/word_text_with_audio_button.dart b/lib/pangea/analytics_details_popup/word_text_with_audio_button.dart index a2224cb66..2025c93e1 100644 --- a/lib/pangea/analytics_details_popup/word_text_with_audio_button.dart +++ b/lib/pangea/analytics_details_popup/word_text_with_audio_button.dart @@ -4,6 +4,9 @@ import 'package:fluffychat/pangea/common/widgets/word_audio_button.dart'; class WordTextWithAudioButton extends StatelessWidget { final String text; + final String pos; + final Map? morph; + final String uniqueID; final TextStyle? style; final double? iconSize; @@ -14,6 +17,8 @@ class WordTextWithAudioButton extends StatelessWidget { required this.text, required this.uniqueID, required this.langCode, + required this.pos, + this.morph, this.style, this.iconSize, }); @@ -39,6 +44,8 @@ class WordTextWithAudioButton extends StatelessWidget { baseOpacity: 1, langCode: langCode, padding: const EdgeInsets.only(left: 8.0), + pos: pos, + morph: morph, ), ], ); diff --git a/lib/pangea/analytics_misc/construct_use_model.dart b/lib/pangea/analytics_misc/construct_use_model.dart index 454d27efc..3add5842e 100644 --- a/lib/pangea/analytics_misc/construct_use_model.dart +++ b/lib/pangea/analytics_misc/construct_use_model.dart @@ -84,9 +84,6 @@ class ConstructUses { _ => ConstructLevelEnum.flowers, }; - List get forms => - _uses.map((e) => e.form).whereType().toSet().toList(); - List get cappedUses { final result = []; var totalXp = 0; diff --git a/lib/pangea/analytics_practice/analytics_practice_page.dart b/lib/pangea/analytics_practice/analytics_practice_page.dart index 791595bfb..e4f0e3c3d 100644 --- a/lib/pangea/analytics_practice/analytics_practice_page.dart +++ b/lib/pangea/analytics_practice/analytics_practice_page.dart @@ -253,9 +253,13 @@ class AnalyticsPracticeState extends State } else { return; } + + final token = activityTarget.value!.target.tokens.first; TtsController.tryToSpeak( - activityTarget.value!.target.tokens.first.vocabConstructID.lemma, + token.vocabConstructID.lemma, langCode: _l2!.langCode, + pos: token.pos, + morph: token.morph.map((k, v) => MapEntry(k.name, v)), ); } diff --git a/lib/pangea/analytics_practice/analytics_practice_view.dart b/lib/pangea/analytics_practice/analytics_practice_view.dart index 3b96d15a6..54134b950 100644 --- a/lib/pangea/analytics_practice/analytics_practice_view.dart +++ b/lib/pangea/analytics_practice/analytics_practice_view.dart @@ -149,6 +149,8 @@ class _AnalyticsActivityView extends StatelessWidget { final isVocabType = controller.widget.type == ConstructTypeEnum.vocab; + final token = target.target.tokens.first; + return Column( children: [ Text( @@ -162,12 +164,11 @@ class _AnalyticsActivityView extends StatelessWidget { ), if (isVocabType && !isAudioActivity) PhoneticTranscriptionWidget( - text: target - .target - .tokens - .first - .vocabConstructID - .lemma, + text: token.vocabConstructID.lemma, + pos: token.pos, + morph: token.morph.map( + (k, v) => MapEntry(k.name, v), + ), textLanguage: MatrixState .pangeaController .userController diff --git a/lib/pangea/common/widgets/word_audio_button.dart b/lib/pangea/common/widgets/word_audio_button.dart index 0adba8318..0f0d5107d 100644 --- a/lib/pangea/common/widgets/word_audio_button.dart +++ b/lib/pangea/common/widgets/word_audio_button.dart @@ -8,6 +8,9 @@ import 'package:fluffychat/widgets/matrix.dart'; class WordAudioButton extends StatefulWidget { final String text; + final String pos; + final Map? morph; + final bool isSelected; final double baseOpacity; final String uniqueID; @@ -23,6 +26,8 @@ class WordAudioButton extends StatefulWidget { required this.text, required this.uniqueID, required this.langCode, + required this.pos, + this.morph, this.isSelected = false, this.baseOpacity = 1, this.callbackOverride, @@ -94,6 +99,8 @@ class WordAudioButtonState extends State { context: context, targetID: 'word-audio-button-${widget.uniqueID}', langCode: widget.langCode, + pos: widget.pos, + morph: widget.morph, onStart: () { if (mounted) { setState(() => _isPlaying = true); diff --git a/lib/pangea/phonetic_transcription/phonetic_transcription_widget.dart b/lib/pangea/phonetic_transcription/phonetic_transcription_widget.dart index a631e8b36..3777b6d0a 100644 --- a/lib/pangea/phonetic_transcription/phonetic_transcription_widget.dart +++ b/lib/pangea/phonetic_transcription/phonetic_transcription_widget.dart @@ -18,7 +18,7 @@ class PhoneticTranscriptionWidget extends StatefulWidget { final LanguageModel textLanguage; /// POS tag for disambiguation (from PangeaToken, e.g. "VERB"). - final String? pos; + final String pos; /// Morph features for disambiguation (from PangeaToken). final Map? morph; @@ -35,7 +35,7 @@ class PhoneticTranscriptionWidget extends StatefulWidget { super.key, required this.text, required this.textLanguage, - this.pos, + required this.pos, this.morph, this.style, this.iconSize, diff --git a/lib/pangea/toolbar/message_practice/practice_controller.dart b/lib/pangea/toolbar/message_practice/practice_controller.dart index 21270bb80..ea266055d 100644 --- a/lib/pangea/toolbar/message_practice/practice_controller.dart +++ b/lib/pangea/toolbar/message_practice/practice_controller.dart @@ -241,6 +241,8 @@ class PracticeController with ChangeNotifier { TtsController.tryToSpeak( token.text.content, langCode: MatrixState.pangeaController.userController.userL2!.langCode, + pos: token.pos, + morph: token.morph.map((k, v) => MapEntry(k.name, v)), ); } diff --git a/lib/pangea/toolbar/word_card/word_zoom_widget.dart b/lib/pangea/toolbar/word_card/word_zoom_widget.dart index e3e4da6d2..0afd527f3 100644 --- a/lib/pangea/toolbar/word_card/word_zoom_widget.dart +++ b/lib/pangea/toolbar/word_card/word_zoom_widget.dart @@ -29,7 +29,7 @@ class WordZoomWidget extends StatelessWidget { final Event? event; /// POS tag for PT v2 disambiguation (e.g. "VERB"). - final String? pos; + final String pos; /// Morph features for PT v2 disambiguation (e.g. {"Tense": "Past"}). final Map? morph; @@ -45,9 +45,9 @@ class WordZoomWidget extends StatelessWidget { required this.token, required this.construct, required this.langCode, + required this.pos, this.onClose, this.event, - this.pos, this.morph, this.enableEmojiSelection = true, this.onDismissNewWordOverlay, @@ -153,6 +153,8 @@ class WordZoomWidget extends StatelessWidget { ) : WordAudioButton( text: token.content, + pos: pos, + morph: morph, uniqueID: "lemma-content-${token.content}", langCode: langCode, iconSize: 24.0,