refactor: allow users to edit morphs from word zoom widget

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: ggurdin <ggurdin@gmail.com>
Co-authored-by: ggurdin <46800240+ggurdin@users.noreply.github.com>
This commit is contained in:
Sofanyas Genene 2025-04-24 09:54:02 -04:00 committed by GitHub
parent bff6f3d222
commit 698f058da6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 382 additions and 302 deletions

View file

@ -0,0 +1,233 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:fluffychat/pangea/common/constants/model_keys.dart';
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart';
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
import 'package:fluffychat/pangea/events/models/tokens_event_content_model.dart';
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
import 'package:fluffychat/pangea/morphs/default_morph_mapping.dart';
import 'package:fluffychat/pangea/morphs/get_grammar_copy.dart';
import 'package:fluffychat/pangea/morphs/morph_features_enum.dart';
import 'package:fluffychat/pangea/morphs/morph_repo.dart';
import 'package:fluffychat/widgets/future_loading_dialog.dart';
class EditMorphWidget extends StatefulWidget {
final PangeaToken token;
final PangeaMessageEvent pangeaMessageEvent;
final MorphFeaturesEnum morphFeature;
final VoidCallback onClose;
const EditMorphWidget({
required this.token,
required this.pangeaMessageEvent,
required this.morphFeature,
required this.onClose,
super.key,
});
@override
State<EditMorphWidget> createState() => EditMorphWidgetState();
}
class EditMorphWidgetState extends State<EditMorphWidget> {
List<String>? _availableMorphTags;
String? _selectedMorphTag;
@override
void initState() {
super.initState();
_setAvailableMorphs(widget.morphFeature);
_selectedMorphTag = _assignedMorphTag;
}
@override
void didUpdateWidget(covariant EditMorphWidget oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.morphFeature != widget.morphFeature) {
_setAvailableMorphs(widget.morphFeature);
}
}
String? get _assignedMorphTag => widget.token.morph[widget.morphFeature];
Future<void> _setAvailableMorphs(MorphFeaturesEnum feature) async {
try {
setState(() => _availableMorphTags = null);
final resp = await MorphsRepo.get();
_availableMorphTags = resp.getDisplayTags(
feature.name,
);
} catch (e) {
_availableMorphTags = defaultMorphMapping.getDisplayTags(
feature.name,
);
} finally {
if (mounted) setState(() {});
}
}
void _saveChanges() {
if (_selectedMorphTag == null) return;
showFutureLoadingDialog(
context: context,
future: () => _sendEditedMessage(
(token) {
token.morph[widget.morphFeature] = _selectedMorphTag!;
if (widget.morphFeature.name.toLowerCase() == 'pos') {
token.pos = _selectedMorphTag!;
}
return token;
},
),
);
}
Future<void> _sendEditedMessage(
PangeaToken Function(PangeaToken token) changeCallback,
) async {
try {
final pm = widget.pangeaMessageEvent;
final existingTokens = pm.originalSent!.tokens!
.map((token) => PangeaToken.fromJson(token.toJson()))
.toList();
final tokenIndex = existingTokens.indexWhere(
(token) => token.text.offset == widget.token.text.offset,
);
if (tokenIndex == -1) {
throw Exception("Token not found in message");
}
existingTokens[tokenIndex] = changeCallback(existingTokens[tokenIndex]);
await pm.room.pangeaSendTextEvent(
pm.messageDisplayText,
editEventId: pm.eventId,
originalSent: pm.originalSent?.content,
originalWritten: pm.originalWritten?.content,
tokensSent: PangeaMessageTokens(
tokens: existingTokens,
detections: pm.originalSent?.detections,
),
tokensWritten: pm.originalWritten?.tokens != null
? PangeaMessageTokens(
tokens: pm.originalWritten!.tokens!,
detections: pm.originalWritten?.detections,
)
: null,
choreo: pm.originalSent?.choreo,
messageTag: ModelKey.messageTagMorphEdit,
);
widget.onClose();
} catch (e) {
ErrorHandler.logError(
e: e,
data: {
"selectedMorphTag": _selectedMorphTag,
"morphFeature": widget.morphFeature.name,
"pangeaMessageEvent": widget.pangeaMessageEvent.event.content,
},
);
}
}
bool get _canSaveChanges =>
_selectedMorphTag != _assignedMorphTag && _selectedMorphTag != null;
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
spacing: 8.0,
children: [
Text(
"${L10n.of(context).pangeaBotIsFallible} ${L10n.of(context).chooseCorrectLabel}",
textAlign: TextAlign.center,
style: const TextStyle(fontStyle: FontStyle.italic),
),
if (_availableMorphTags == null || _availableMorphTags!.isEmpty)
const CircularProgressIndicator()
else
Wrap(
alignment: WrapAlignment.center,
children: _availableMorphTags!.map((tag) {
return Container(
margin: const EdgeInsets.all(2),
padding: EdgeInsets.zero,
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(
Radius.circular(10),
),
border: Border.all(
color: _selectedMorphTag == tag
? Theme.of(context).colorScheme.primary
: Colors.transparent,
style: BorderStyle.solid,
width: 2.0,
),
),
child: TextButton(
style: ButtonStyle(
padding: WidgetStateProperty.all(
const EdgeInsets.symmetric(
horizontal: 7,
),
),
backgroundColor: WidgetStateProperty.all<Color>(
_selectedMorphTag == tag
? Theme.of(context).colorScheme.primary.withAlpha(50)
: Colors.transparent,
),
shape: WidgetStateProperty.all(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
),
onPressed: () => setState(() => _selectedMorphTag = tag),
child: Text(
getGrammarCopy(
category: widget.morphFeature.name,
lemma: tag,
context: context,
) ??
tag,
textAlign: TextAlign.center,
),
),
);
}).toList(),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 10,
children: [
ElevatedButton(
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
),
padding: const EdgeInsets.symmetric(horizontal: 10),
),
onPressed: widget.onClose,
child: Text(L10n.of(context).cancel),
),
ElevatedButton(
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
),
padding: const EdgeInsets.symmetric(horizontal: 10),
),
onPressed: _canSaveChanges ? _saveChanges : null,
child: Text(L10n.of(context).saveChanges),
),
],
),
],
);
}
}

View file

@ -86,11 +86,9 @@ class ReadingAssistanceInputBar extends StatelessWidget {
);
} else if (overlayController.selectedMorph != null) {
content = MorphFocusWidget(
token: overlayController.selectedMorph!.token,
morphFeature: overlayController.selectedMorph!.morph,
pangeaMessageEvent: overlayController.pangeaMessageEvent!,
overlayController: overlayController,
onEditDone: () => overlayController.setState(() {}),
);
} else {
content = Center(

View file

@ -4,7 +4,8 @@ class WordZoomActivityButton extends StatelessWidget {
final Widget icon;
final bool isSelected;
final VoidCallback onPressed;
final VoidCallback? onDoubleTap;
final VoidCallback? onLongPress;
final String? tooltip;
final double? opacity;
@ -12,6 +13,8 @@ class WordZoomActivityButton extends StatelessWidget {
required this.icon,
required this.isSelected,
required this.onPressed,
this.onDoubleTap,
this.onLongPress,
this.tooltip,
this.opacity,
super.key,
@ -22,25 +25,29 @@ class WordZoomActivityButton extends StatelessWidget {
Widget buttonContent = AnimatedSize(
duration: const Duration(milliseconds: 200),
curve: Curves.easeInOut,
child: IconButton(
onPressed: onPressed,
icon: AnimatedBuilder(
animation: Listenable.merge([ValueNotifier(isSelected)]),
builder: (context, child) {
return Transform.scale(
scale: isSelected ? 1.25 : 1.0,
child: icon,
);
},
child: GestureDetector(
onDoubleTap: onDoubleTap,
onLongPress: onLongPress,
child: IconButton(
onPressed: onPressed,
icon: AnimatedBuilder(
animation: Listenable.merge([ValueNotifier(isSelected)]),
builder: (context, child) {
return Transform.scale(
scale: isSelected ? 1.25 : 1.0,
child: icon,
);
},
),
iconSize: 24, // Keep this constant as scaling handles the size change
color: isSelected ? Theme.of(context).colorScheme.primary : null,
visualDensity: VisualDensity.compact,
// style: IconButton.styleFrom(
// backgroundColor: isSelected
// ? Theme.of(context).colorScheme.primary.withValues(alpha: 0.25)
// : Colors.transparent,
// ),
),
iconSize: 24, // Keep this constant as scaling handles the size change
color: isSelected ? Theme.of(context).colorScheme.primary : null,
visualDensity: VisualDensity.compact,
// style: IconButton.styleFrom(
// backgroundColor: isSelected
// ? Theme.of(context).colorScheme.primary.withValues(alpha: 0.25)
// : Colors.transparent,
// ),
),
);

View file

@ -8,6 +8,7 @@ import 'package:matrix/matrix_api_lite/model/message_types.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart';
import 'package:fluffychat/pangea/morphs/morph_features_enum.dart';
import 'package:fluffychat/pangea/practice_activities/activity_type_enum.dart';
import 'package:fluffychat/pangea/toolbar/controllers/tts_controller.dart';
import 'package:fluffychat/pangea/toolbar/enums/message_mode_enum.dart';
@ -38,6 +39,8 @@ class ReadingAssistanceContent extends StatefulWidget {
}
class ReadingAssistanceContentState extends State<ReadingAssistanceContent> {
MorphFeaturesEnum? _selectedEditMorphFeature;
TtsController get ttsController =>
widget.overlayController.widget.chatController.choreographer.tts;
@ -125,6 +128,8 @@ class ReadingAssistanceContentState extends State<ReadingAssistanceContent> {
messageEvent: widget.overlayController.pangeaMessageEvent!,
tts: ttsController,
overlayController: widget.overlayController,
editMorph: (m) => setState(() => _selectedEditMorphFeature = m),
selectedEditMorphFeature: _selectedEditMorphFeature,
);
}
}

View file

@ -8,38 +8,27 @@ import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:fluffychat/pangea/analytics_details_popup/analytics_details_popup.dart';
import 'package:fluffychat/pangea/analytics_details_popup/morph_meaning_widget.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
import 'package:fluffychat/pangea/common/constants/model_keys.dart';
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
import 'package:fluffychat/pangea/constructs/construct_identifier.dart';
import 'package:fluffychat/pangea/constructs/construct_level_enum.dart';
import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart';
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
import 'package:fluffychat/pangea/events/models/tokens_event_content_model.dart';
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
import 'package:fluffychat/pangea/lemmas/construct_xp_widget.dart';
import 'package:fluffychat/pangea/morphs/default_morph_mapping.dart';
import 'package:fluffychat/pangea/morphs/get_grammar_copy.dart';
import 'package:fluffychat/pangea/morphs/edit_morph_widget.dart';
import 'package:fluffychat/pangea/morphs/morph_feature_display.dart';
import 'package:fluffychat/pangea/morphs/morph_features_enum.dart';
import 'package:fluffychat/pangea/morphs/morph_repo.dart';
import 'package:fluffychat/pangea/morphs/morph_tag_display.dart';
import 'package:fluffychat/pangea/toolbar/reading_assistance_input_row/morph_selection.dart';
import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart';
import 'package:fluffychat/widgets/future_loading_dialog.dart';
class MorphFocusWidget extends StatefulWidget {
final PangeaToken token;
final MorphFeaturesEnum morphFeature;
final PangeaMessageEvent pangeaMessageEvent;
final MessageOverlayController overlayController;
final VoidCallback onEditDone;
const MorphFocusWidget({
required this.token,
required this.morphFeature,
required this.pangeaMessageEvent,
required this.overlayController,
required this.onEditDone,
super.key,
});
@ -48,36 +37,32 @@ class MorphFocusWidget extends StatefulWidget {
}
class MorphFocusWidgetState extends State<MorphFocusWidget> {
bool editMode = false;
PangeaToken get token => widget.overlayController.selectedToken!;
bool _editMode = false;
/// the morphological tag that the user has selected in edit mode
String selectedMorphTag = "";
List<String> morphFeatures = [];
String _selectedMorphTag = "";
final ScrollController _scrollController = ScrollController();
void resetMorphTag() {
void _resetMorphTag() {
setState(
() => selectedMorphTag =
widget.token.getMorphTag(widget.morphFeature) ?? "X",
() => _selectedMorphTag = token.getMorphTag(widget.morphFeature) ?? "X",
);
}
@override
void initState() {
super.initState();
resetMorphTag();
_setAvailableMorphs();
_resetMorphTag();
}
@override
void didUpdateWidget(MorphFocusWidget oldWidget) {
if (widget.token != oldWidget.token ||
widget.morphFeature != oldWidget.morphFeature) {
resetMorphTag();
_setAvailableMorphs();
setState(() => editMode = false);
if (widget.morphFeature != oldWidget.morphFeature) {
_resetMorphTag();
setState(() => _editMode = false);
}
super.didUpdateWidget(oldWidget);
}
@ -88,98 +73,15 @@ class MorphFocusWidgetState extends State<MorphFocusWidget> {
super.dispose();
}
void enterEditMode() {
void _enterEditMode() {
setState(() {
editMode = true;
_editMode = true;
});
}
PangeaMessageEvent get pm => widget.pangeaMessageEvent;
Future<void> _setAvailableMorphs() async {
try {
final resp = await MorphsRepo.get();
morphFeatures = resp.getDisplayTags(
widget.morphFeature.name,
);
} catch (e) {
morphFeatures = defaultMorphMapping.getDisplayTags(
widget.morphFeature.name,
);
} finally {
if (mounted) setState(() {});
}
}
/// confirm the changes made by the user
/// this will send a new message to the server
/// with the new morphological tag
Future<void> saveChanges(
PangeaToken Function(PangeaToken token) changeCallback,
) async {
try {
// NOTE: it is not clear how this would work if the user was not editing the originalSent tokens
// this case would only happen in immersion mode which is disabled until further notice
// this flow assumes that the user is editing the originalSent tokens
// if not, we'll get an error and we'll cross that bridge
// make a copy of the original tokens
final existingTokens = pm.originalSent!.tokens!
.map((token) => PangeaToken.fromJson(token.toJson()))
.toList();
// change the morphological tag in the selected token
final tokenIndex = existingTokens
.indexWhere((token) => token.text.offset == widget.token.text.offset);
if (tokenIndex == -1) {
throw Exception("Token not found in message");
}
existingTokens[tokenIndex] = changeCallback(existingTokens[tokenIndex]);
// send a new message as an edit to original message to the server
// including the new tokens
// marking the message as a morphological edit will allow use to filter
// from some processing and potentially find the data for LLM fine-tuning
await pm.room.pangeaSendTextEvent(
pm.messageDisplayText,
editEventId: pm.eventId,
originalSent: pm.originalSent?.content,
originalWritten: pm.originalWritten?.content,
tokensSent: PangeaMessageTokens(
tokens: existingTokens,
detections: pm.originalSent?.detections,
),
tokensWritten: pm.originalWritten?.tokens != null
? PangeaMessageTokens(
tokens: pm.originalWritten!.tokens!,
detections: pm.originalWritten?.detections,
)
: null,
choreo: pm.originalSent?.choreo,
messageTag: ModelKey.messageTagMorphEdit,
);
setState(() => editMode = false);
widget.onEditDone();
} catch (e) {
SnackBar(
content: Text(L10n.of(context).oopsSomethingWentWrong),
);
ErrorHandler.logError(
e: e,
data: {
"selectedMorphTag": selectedMorphTag,
"morphFeature": widget.morphFeature,
"token": widget.token.toJson(),
"pangeaMessageEvent": widget.pangeaMessageEvent.event.content,
},
);
}
}
ConstructIdentifier get id {
ConstructIdentifier get _id {
return ConstructIdentifier(
lemma: selectedMorphTag,
lemma: _selectedMorphTag,
type: ConstructTypeEnum.morph,
category: widget.morphFeature.name,
);
@ -187,7 +89,7 @@ class MorphFocusWidgetState extends State<MorphFocusWidget> {
@override
Widget build(BuildContext context) {
if (!editMode) {
if (!_editMode) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
@ -196,7 +98,7 @@ class MorphFocusWidgetState extends State<MorphFocusWidget> {
MorphFeatureDisplay(
morphFeature: widget.morphFeature,
),
if (widget.token.getMorphTag(widget.morphFeature) != null) ...[
if (token.getMorphTag(widget.morphFeature) != null) ...[
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
@ -205,26 +107,26 @@ class MorphFocusWidgetState extends State<MorphFocusWidget> {
triggerMode: TooltipTriggerMode.tap,
message: L10n.of(context).doubleClickToEdit,
child: GestureDetector(
onLongPress: enterEditMode,
onDoubleTap: enterEditMode,
onLongPress: _enterEditMode,
onDoubleTap: _enterEditMode,
child: MorphTagDisplay(
morphFeature: widget.morphFeature,
morphTag: widget.token.getMorphTag(widget.morphFeature) ??
morphTag: token.getMorphTag(widget.morphFeature) ??
L10n.of(context).nan,
textColor: Theme.of(context).brightness ==
Brightness.light
? id.constructUses.lemmaCategory.darkColor(context)
: id.constructUses.lemmaCategory.color(context),
? _id.constructUses.lemmaCategory.darkColor(context)
: _id.constructUses.lemmaCategory.color(context),
),
),
),
const SizedBox(width: 6),
ConstructXpWidget(
id: id,
id: _id,
onTap: () => showDialog<AnalyticsPopupWrapper>(
context: context,
builder: (context) => AnalyticsPopupWrapper(
constructZoom: id,
constructZoom: _id,
view: ConstructTypeEnum.morph,
),
),
@ -233,7 +135,7 @@ class MorphFocusWidgetState extends State<MorphFocusWidget> {
),
MorphMeaningWidget(
feature: widget.morphFeature,
tag: widget.token.getMorphTag(widget.morphFeature)!,
tag: token.getMorphTag(widget.morphFeature)!,
style: Theme.of(context).textTheme.bodyLarge,
),
] else
@ -244,117 +146,19 @@ class MorphFocusWidgetState extends State<MorphFocusWidget> {
return Padding(
padding: const EdgeInsets.all(4.0),
child: Column(
children: [
Text(
"${L10n.of(context).pangeaBotIsFallible} ${L10n.of(context).chooseCorrectLabel}",
textAlign: TextAlign.center,
style: const TextStyle(fontStyle: FontStyle.italic),
),
if (morphFeatures.isEmpty)
const CircularProgressIndicator()
else
Wrap(
children: morphFeatures.map((tag) {
return Container(
margin: const EdgeInsets.all(2),
padding: EdgeInsets.zero,
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(
Radius.circular(10),
),
border: Border.all(
color: selectedMorphTag == tag
? Theme.of(context).colorScheme.primary
: Colors.transparent,
style: BorderStyle.solid,
width: 2.0,
),
),
child: TextButton(
style: ButtonStyle(
padding: WidgetStateProperty.all(
const EdgeInsets.symmetric(
horizontal: 7,
),
),
backgroundColor: WidgetStateProperty.all<Color>(
selectedMorphTag == tag
? Theme.of(context)
.colorScheme
.primary
.withAlpha(50)
: Colors.transparent,
),
shape: WidgetStateProperty.all(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
),
onPressed: () {
setState(() => selectedMorphTag = tag);
},
child: Text(
getGrammarCopy(
category: widget.morphFeature.name,
lemma: tag,
context: context,
) ??
tag,
textAlign: TextAlign.center,
),
),
);
}).toList(),
child: EditMorphWidget(
token: token,
pangeaMessageEvent: widget.pangeaMessageEvent,
morphFeature: widget.morphFeature,
onClose: () {
setState(() => _editMode = false);
widget.overlayController.onMorphActivitySelect(
MorphSelection(
token,
widget.morphFeature,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 10,
children: [
ElevatedButton(
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
),
padding: const EdgeInsets.symmetric(horizontal: 10),
),
onPressed: () {
setState(() {
editMode = false;
});
},
child: Text(L10n.of(context).cancel),
),
ElevatedButton(
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
),
padding: const EdgeInsets.symmetric(horizontal: 10),
),
onPressed:
selectedMorphTag == widget.token.morph[widget.morphFeature]
? null
: () => showFutureLoadingDialog(
context: context,
future: () => saveChanges(
(token) {
token.morph[widget.morphFeature] =
selectedMorphTag;
if (widget.morphFeature.name.toLowerCase() ==
'pos') {
token.pos = selectedMorphTag;
}
return token;
},
),
),
child: Text(L10n.of(context).saveChanges),
),
],
),
],
);
},
),
);
}

View file

@ -16,11 +16,13 @@ class MorphologicalListItem extends StatelessWidget {
final MorphFeaturesEnum morphFeature;
final PangeaToken token;
final MessageOverlayController overlayController;
final VoidCallback editMorph;
const MorphologicalListItem({
required this.morphFeature,
required this.token,
required this.overlayController,
required this.editMorph,
super.key,
});
@ -55,6 +57,16 @@ class MorphologicalListItem extends StatelessWidget {
isSelected: isSelected,
onPressed: () => overlayController
.onMorphActivitySelect(MorphSelection(token, morphFeature)),
onLongPress: () {
overlayController
.onMorphActivitySelect(MorphSelection(token, morphFeature));
editMorph();
},
onDoubleTap: () {
overlayController
.onMorphActivitySelect(MorphSelection(token, morphFeature));
editMorph();
},
tooltip: shouldDoActivity
? morphFeature.getDisplayCopy(context)
: getGrammarCopy(

View file

@ -8,6 +8,7 @@ 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/construct_xp_widget.dart';
import 'package:fluffychat/pangea/lemmas/lemma_emoji_row.dart';
import 'package:fluffychat/pangea/morphs/edit_morph_widget.dart';
import 'package:fluffychat/pangea/morphs/morph_features_enum.dart';
import 'package:fluffychat/pangea/practice_activities/activity_type_enum.dart';
import 'package:fluffychat/pangea/toolbar/controllers/tts_controller.dart';
@ -24,6 +25,9 @@ class WordZoomWidget extends StatelessWidget {
final PangeaMessageEvent messageEvent;
final TtsController tts;
final MessageOverlayController overlayController;
final Function(MorphFeaturesEnum?) editMorph;
final MorphFeaturesEnum? selectedEditMorphFeature;
const WordZoomWidget({
super.key,
@ -31,11 +35,13 @@ class WordZoomWidget extends StatelessWidget {
required this.messageEvent,
required this.tts,
required this.overlayController,
required this.editMorph,
required this.selectedEditMorphFeature,
});
PangeaToken get _selectedToken => overlayController.selectedToken!;
void onEditDone() => overlayController.initializeTokensAndMode();
void _onEditDone() => overlayController.initializeTokensAndMode();
bool get hasEmojiActivity =>
overlayController.practiceSelection?.hasActiveActivityByToken(
@ -83,7 +89,7 @@ class WordZoomWidget extends StatelessWidget {
},
onEditDone: () {
debugPrint("what are we doing edits with?");
onEditDone();
_onEditDone();
},
tts: tts,
overlayController: overlayController,
@ -104,56 +110,68 @@ class WordZoomWidget extends StatelessWidget {
const SizedBox(
height: 8.0,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
constraints: const BoxConstraints(
minHeight: 40,
),
alignment: Alignment.center,
child: LemmaEmojiRow(
cId: _selectedToken.vocabConstructID,
onTapOverride: overlayController.hideWordCardContent &&
hasEmojiActivity
? () => overlayController.updateToolbarMode(
MessageMode.wordEmoji,
)
: null,
isSelected:
overlayController.toolbarMode == MessageMode.wordEmoji,
emojiSetCallback: () => overlayController.setState(() {}),
shouldShowEmojis: !hasEmojiActivity,
),
),
],
),
const SizedBox(
height: 8.0,
),
Container(
constraints: const BoxConstraints(
minHeight: 40,
),
alignment: Alignment.center,
child: Wrap(
alignment: WrapAlignment.center,
runAlignment: WrapAlignment.center,
crossAxisAlignment: WrapCrossAlignment.center,
spacing: 8,
if (selectedEditMorphFeature == null)
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
LemmaMeaningWidget(
constructUse: token.vocabConstructID.constructUses,
langCode: MatrixState.pangeaController.languageController
.userL2?.langCodeShort ??
LanguageKeys.defaultLanguage,
token: overlayController.selectedToken!,
controller: overlayController,
style: Theme.of(context).textTheme.bodyLarge,
Container(
constraints: const BoxConstraints(
minHeight: 40,
),
alignment: Alignment.center,
child: LemmaEmojiRow(
cId: _selectedToken.vocabConstructID,
onTapOverride: overlayController.hideWordCardContent &&
hasEmojiActivity
? () => overlayController.updateToolbarMode(
MessageMode.wordEmoji,
)
: null,
isSelected: overlayController.toolbarMode ==
MessageMode.wordEmoji,
emojiSetCallback: () => overlayController.setState(() {}),
shouldShowEmojis: !hasEmojiActivity,
),
),
],
),
const SizedBox(
height: 8.0,
),
if (selectedEditMorphFeature == null)
Container(
constraints: const BoxConstraints(
minHeight: 40,
),
alignment: Alignment.center,
child: Wrap(
alignment: WrapAlignment.center,
runAlignment: WrapAlignment.center,
crossAxisAlignment: WrapCrossAlignment.center,
spacing: 8,
children: [
LemmaMeaningWidget(
constructUse: token.vocabConstructID.constructUses,
langCode: MatrixState.pangeaController.languageController
.userL2?.langCodeShort ??
LanguageKeys.defaultLanguage,
token: overlayController.selectedToken!,
controller: overlayController,
style: Theme.of(context).textTheme.bodyLarge,
),
],
),
)
else
EditMorphWidget(
token: token,
pangeaMessageEvent: overlayController.pangeaMessageEvent!,
morphFeature: selectedEditMorphFeature!,
onClose: () {
editMorph(null);
overlayController.setState(() {});
},
),
const SizedBox(
height: 8.0,
),
@ -196,6 +214,9 @@ class WordZoomWidget extends StatelessWidget {
),
token: _selectedToken,
overlayController: overlayController,
editMorph: () => editMorph(
MorphFeaturesEnumExtension.fromString(cId.category),
),
),
),
],