dev/feat: refactor of instructions, tweaking of morph/lemma edits

This commit is contained in:
wcjord 2025-01-11 13:25:07 -05:00
parent 7054d92532
commit bbc791b314
23 changed files with 434 additions and 482 deletions

View file

@ -4660,7 +4660,6 @@
"publicProfileTitle": "Allow my profile to be found in search",
"publicProfileDesc": "By enabling this option, I confirm that I am of legal age in my country of residence",
"clickWordsInstructions": "Click on individual words for more activities.",
"chooseBestDefinition": "What does this word mean?",
"chooseBaseForm": "Choose the base form",
"notTheCodeError": "Sorry, that's not the code!",
"totalXP": "Total XP",
@ -4700,11 +4699,43 @@
"downloading": "Downloading...",
"failedFetchUserAnalytics": "Failed to download user analytics",
"downloadComplete": "Download complete!",
"editMorphologicalLabel": "Pangea Bot makes mistakes too! What should this label be?",
"whatIsTheMorphTag": "What is the {morphologicalFeature} of '{wordForm}'?",
"@whatIsTheMorphTag": {
"type": "text",
"placeholders": {
"morphologicalFeature": {},
"wordForm": {}
}
},
"dataAvailable": "Data availability",
"lemmasNeverUsedCorrectly": "Number of lemmas used correctly 0 times",
"available": "Available",
"unavailable": "Unavailable",
"accessingMemberAnalytics": "Accessing member analytics...",
"editLemmaMeaning": "Pangea Bot makes mistakes too! What should be the definition of this lemma?"
"pangeaBotIsFallible": "Pangea Bot makes mistakes too!",
"whatIsMeaning": "What does '{lemma}' mean?",
"@whatIsMeaning": {
"type": "text",
"placeholders": {
"lemma": {},
"partOfSpeech": {}
}
},
"pickAnEmoji": "What's your favorite emoji for '{lemma}'?",
"@pickAnEmoji": {
"type": "text",
"placeholders": {
"lemma": {},
"partOfSpeech": {}
}
},
"lemmaMeaningInstructionsBody": "Above is the meaning of the lemma. Double-click to edit.",
"doubleClickToEdit": "Double-click to edit.",
"removeFeature": "Remove {feature}",
"@removeFeature": {
"type": "text",
"placeholders": {
"feature": {}
}
}
}

View file

@ -1,8 +1,3 @@
import 'package:flutter/material.dart';
import 'package:matrix/matrix.dart';
import 'package:scroll_to_index/scroll_to_index.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/pages/chat/chat.dart';
@ -10,11 +5,15 @@ import 'package:fluffychat/pages/chat/events/message.dart';
import 'package:fluffychat/pages/chat/seen_by_row.dart';
import 'package:fluffychat/pages/chat/typing_indicators.dart';
import 'package:fluffychat/pages/user_bottom_sheet/user_bottom_sheet.dart';
import 'package:fluffychat/pangea/enum/instructions_enum.dart';
import 'package:fluffychat/pangea/instructions/instructions_enum.dart';
import 'package:fluffychat/pangea/instructions/instructions_show_popup.dart';
import 'package:fluffychat/utils/account_config.dart';
import 'package:fluffychat/utils/adaptive_bottom_sheet.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/filtered_timeline_extension.dart';
import 'package:fluffychat/utils/platform_infos.dart';
import 'package:flutter/material.dart';
import 'package:matrix/matrix.dart';
import 'package:scroll_to_index/scroll_to_index.dart';
class ChatEventList extends StatelessWidget {
final ChatController controller;
@ -59,7 +58,7 @@ class ChatEventList extends StatelessWidget {
)
.toList();
if (msgEvents.isEmpty) return;
controller.pangeaController.instructions.showInstructionsPopup(
instructionsShowPopup(
context,
InstructionsEnum.clickMessage,
msgEvents[0].eventId,

View file

@ -1,9 +1,6 @@
import 'dart:async';
import 'dart:developer';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:fluffychat/pangea/choreographer/controllers/choreographer.dart';
import 'package:fluffychat/pangea/choreographer/controllers/it_controller.dart';
import 'package:fluffychat/pangea/choreographer/widgets/it_bar_buttons.dart';
@ -12,11 +9,14 @@ import 'package:fluffychat/pangea/choreographer/widgets/translation_finished_flo
import 'package:fluffychat/pangea/constants/choreo_constants.dart';
import 'package:fluffychat/pangea/controllers/put_analytics_controller.dart';
import 'package:fluffychat/pangea/enum/construct_use_type_enum.dart';
import 'package:fluffychat/pangea/enum/instructions_enum.dart';
import 'package:fluffychat/pangea/instructions/instructions_enum.dart';
import 'package:fluffychat/pangea/instructions/instructions_inline_tooltip.dart';
import 'package:fluffychat/pangea/pages/settings_learning/settings_learning.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:fluffychat/pangea/utils/inline_tooltip.dart';
import 'package:fluffychat/pangea/widgets/animations/gain_points.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import '../../controllers/it_feedback_controller.dart';
import '../../models/it_response_model.dart';
import '../../utils/overlay.dart';
@ -67,7 +67,7 @@ class ITBarState extends State<ITBar> with SingleTickerProviderStateMixin {
}
bool get showITInstructionsTooltip {
final toggledOff = InstructionsEnum.clickBestOption.toggledOff();
final toggledOff = InstructionsEnum.clickBestOption.isToggledOff;
if (!toggledOff) {
setState(() => showedClickInstruction = true);
}
@ -205,11 +205,11 @@ class ITBarState extends State<ITBar> with SingleTickerProviderStateMixin {
),
const SizedBox(height: 8.0),
if (showITInstructionsTooltip)
const InlineTooltip(
const InstructionsInlineTooltip(
instructionsEnum: InstructionsEnum.clickBestOption,
),
if (showTranslationsChoicesTooltip)
const InlineTooltip(
const InstructionsInlineTooltip(
instructionsEnum: InstructionsEnum.translationChoices,
),
Container(

View file

@ -1,8 +1,9 @@
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
import 'package:fluffychat/pangea/instructions/instructions_enum.dart';
import 'package:fluffychat/pangea/instructions/instructions_show_popup.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/material.dart';
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
import 'package:fluffychat/pangea/enum/instructions_enum.dart';
import 'package:fluffychat/widgets/matrix.dart';
import '../../widgets/common/bot_face_svg.dart';
import '../controllers/choreographer.dart';
import '../controllers/it_controller.dart';
@ -37,7 +38,7 @@ class ITBotButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
choreographer.pangeaController.instructions.showInstructionsPopup(
instructionsShowPopup(
context,
InstructionsEnum.itInstructions,
choreographer.itBotTransformTargetKey,
@ -45,8 +46,7 @@ class ITBotButton extends StatelessWidget {
return IconButton(
icon: const BotFace(width: 40.0, expression: BotExpression.idle),
onPressed: () =>
choreographer.pangeaController.instructions.showInstructionsPopup(
onPressed: () => instructionsShowPopup(
context,
InstructionsEnum.itInstructions,
choreographer.itBotTransformTargetKey,

View file

@ -2,11 +2,6 @@ import 'dart:async';
import 'dart:developer';
import 'dart:math';
import 'package:flutter/foundation.dart';
import 'package:matrix/matrix.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:fluffychat/pangea/constants/bot_mode.dart';
import 'package:fluffychat/pangea/constants/class_default_values.dart';
import 'package:fluffychat/pangea/constants/pangea_event_types.dart';
@ -30,8 +25,11 @@ import 'package:fluffychat/pangea/guard/p_vguard.dart';
import 'package:fluffychat/pangea/models/bot_options_model.dart';
import 'package:fluffychat/pangea/utils/bot_name.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:fluffychat/pangea/utils/instructions.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/foundation.dart';
import 'package:matrix/matrix.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import '../../config/app_config.dart';
import '../utils/firebase_analytics.dart';
import '../utils/p_store.dart';
@ -51,7 +49,6 @@ class PangeaController {
// TODO: make these static so we can remove from here
late ContextualDefinitionController definitions;
late ITFeedbackController itFeedback;
late InstructionsController instructions;
late SubscriptionController subscriptionController;
late TextToSpeechController textToSpeech;
late SpeechToTextController speechToText;
@ -106,7 +103,6 @@ class PangeaController {
messageData = MessageDataController(this);
wordNet = WordController(this);
definitions = ContextualDefinitionController(this);
instructions = InstructionsController(this);
subscriptionController = SubscriptionController(this);
itFeedback = ITFeedbackController(this);
textToSpeech = TextToSpeechController(this);

View file

@ -0,0 +1,53 @@
import 'package:fluffychat/pangea/instructions/instructions_enum.dart';
import 'package:fluffychat/widgets/matrix.dart';
/// The user's settings for whether or not to show instuction messages.
class InstructionSettings {
Map<String, bool> _instructions = {};
InstructionSettings([Map<String, bool>? instructions]) {
if (instructions != null) {
_instructions = instructions;
} else {
for (final key in InstructionsEnum.values) {
_instructions[key.toString()] = false;
}
}
}
factory InstructionSettings.fromJson(Map<String, dynamic> json) {
final Map<String, bool> instructions = {};
for (final key in InstructionsEnum.values) {
instructions[key.toString()] = json[key.toString()] ?? false;
}
return InstructionSettings(instructions);
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
for (final key in InstructionsEnum.values) {
data[key.toString()] = _instructions[key.toString()];
}
return data;
}
factory InstructionSettings.migrateFromAccountData() {
final accountData =
MatrixState.pangeaController.matrixState.client.accountData;
final Map<String, bool> instructions = {};
for (final key in InstructionsEnum.values) {
instructions[key.toString()] =
(accountData[key.toString()]?.content[key.toString()] as bool?) ??
false;
}
return InstructionSettings(instructions);
}
bool getStatus(InstructionsEnum instruction) {
return _instructions[instruction.toString()] ?? false;
}
void setStatus(InstructionsEnum instruction, bool status) {
_instructions[instruction.toString()] = status;
}
}

View file

@ -1,12 +1,10 @@
import 'dart:developer';
import 'package:flutter/foundation.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:fluffychat/utils/platform_infos.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
enum InstructionsEnum {
itInstructions,
@ -20,6 +18,7 @@ enum InstructionsEnum {
missingVoice,
clickBestOption,
unlockedLanguageTools,
lemmaMeaning,
}
extension InstructionsEnumExtension on InstructionsEnum {
@ -41,6 +40,7 @@ extension InstructionsEnumExtension on InstructionsEnum {
case InstructionsEnum.translationChoices:
case InstructionsEnum.clickBestOption:
case InstructionsEnum.unlockedLanguageTools:
case InstructionsEnum.lemmaMeaning:
ErrorHandler.logError(
e: Exception("No title for this instruction"),
m: 'InstructionsEnumExtension.title',
@ -79,35 +79,18 @@ extension InstructionsEnumExtension on InstructionsEnum {
return l10n.clickBestOption;
case InstructionsEnum.unlockedLanguageTools:
return l10n.unlockedLanguageTools;
case InstructionsEnum.lemmaMeaning:
return l10n.lemmaMeaningInstructionsBody;
}
}
bool toggledOff() {
final instructionSettings =
MatrixState.pangeaController.userController.profile.instructionSettings;
switch (this) {
case InstructionsEnum.itInstructions:
return instructionSettings.showedItInstructions;
case InstructionsEnum.clickMessage:
return instructionSettings.showedClickMessage;
case InstructionsEnum.blurMeansTranslate:
return instructionSettings.showedBlurMeansTranslate;
case InstructionsEnum.tooltipInstructions:
return instructionSettings.showedTooltipInstructions;
case InstructionsEnum.speechToText:
return instructionSettings.showedSpeechToTextTooltip;
case InstructionsEnum.l1Translation:
return instructionSettings.showedL1TranslationTooltip;
case InstructionsEnum.translationChoices:
return instructionSettings.showedTranslationChoicesTooltip;
case InstructionsEnum.clickAgainToDeselect:
return instructionSettings.showedClickAgainToDeselect;
case InstructionsEnum.missingVoice:
return instructionSettings.showedMissingVoice;
case InstructionsEnum.clickBestOption:
return instructionSettings.showedClickBestOption;
case InstructionsEnum.unlockedLanguageTools:
return instructionSettings.showedUnlockedLanguageTools;
}
}
bool get isToggledOff =>
MatrixState.pangeaController.userController.profile.instructionSettings
.getStatus(this);
void setToggledOff(bool value) =>
MatrixState.pangeaController.userController.updateProfile((profile) {
profile.instructionSettings.setStatus(this, value);
return profile;
});
}

View file

@ -1,25 +1,23 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/pangea/enum/instructions_enum.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:fluffychat/pangea/instructions/instructions_enum.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
class InlineTooltip extends StatefulWidget {
class InstructionsInlineTooltip extends StatefulWidget {
final InstructionsEnum instructionsEnum;
const InlineTooltip({
const InstructionsInlineTooltip({
super.key,
required this.instructionsEnum,
});
@override
InlineTooltipState createState() => InlineTooltipState();
InstructionsInlineTooltipState createState() =>
InstructionsInlineTooltipState();
}
class InlineTooltipState extends State<InlineTooltip>
class InstructionsInlineTooltipState extends State<InstructionsInlineTooltip>
with SingleTickerProviderStateMixin {
bool _isToggledOff = true;
late AnimationController _controller;
@ -28,7 +26,7 @@ class InlineTooltipState extends State<InlineTooltip>
@override
void initState() {
super.initState();
_isToggledOff = widget.instructionsEnum.toggledOff();
_isToggledOff = widget.instructionsEnum.isToggledOff;
// Initialize AnimationController and Animation
_controller = AnimationController(
@ -52,10 +50,7 @@ class InlineTooltipState extends State<InlineTooltip>
}
void _closeTooltip() {
MatrixState.pangeaController.instructions.setToggledOff(
widget.instructionsEnum,
true,
);
widget.instructionsEnum.setToggledOff(true);
setState(() {
_isToggledOff = true;
_controller.reverse();

View file

@ -0,0 +1,71 @@
import 'package:fluffychat/pangea/instructions/instructions_enum.dart';
import 'package:fluffychat/pangea/instructions/instructions_toggle.dart';
import 'package:fluffychat/pangea/utils/bot_style.dart';
import 'package:fluffychat/pangea/utils/overlay.dart';
import 'package:fluffychat/pangea/widgets/common/bot_face_svg.dart';
import 'package:fluffychat/pangea/widgets/igc/card_header.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
/// Instruction Card gives users tips on
/// how to use Pangea Chat's features
Future<void> instructionsShowPopup(
BuildContext context,
InstructionsEnum key,
String transformTargetKey, {
bool showToggle = true,
Widget? customContent,
bool forceShow = false,
}) async {
final bool userLangsSet =
await MatrixState.pangeaController.userController.areUserLanguagesSet;
if (!userLangsSet) {
return;
}
// if ((_instructionsShown[key.toString()] ?? false) && !forceShow) {
// return;
// }
// _instructionsShown[key.toString()] = true;
if (key.isToggledOff && !forceShow) {
return;
}
final botStyle = BotStyle.text(context);
Future.delayed(
const Duration(seconds: 1),
() {
if (!context.mounted) return;
OverlayUtil.showPositionedCard(
context: context,
backDropToDismiss: false,
cardToShow: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
CardHeader(
text: key.title(L10n.of(context)),
botExpression: BotExpression.idle,
// onClose: () => {_instructionsClosed[key.toString()] = true},
),
const SizedBox(height: 10.0),
Padding(
padding: const EdgeInsets.all(6.0),
child: Text(
key.body(L10n.of(context)),
style: botStyle,
),
),
if (customContent != null) customContent,
if (showToggle) InstructionsToggle(instructionsKey: key),
],
),
maxHeight: 300,
maxWidth: 300,
transformTargetId: transformTargetKey,
closePrevOverlay: false,
);
},
);
}

View file

@ -0,0 +1,42 @@
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
import 'package:fluffychat/pangea/instructions/instructions_enum.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
/// User can toggle on to prevent Instruction Card
/// from appearing in future sessions
class InstructionsToggle extends StatefulWidget {
const InstructionsToggle({
super.key,
required this.instructionsKey,
});
final InstructionsEnum instructionsKey;
@override
InstructionsToggleState createState() => InstructionsToggleState();
}
class InstructionsToggleState extends State<InstructionsToggle> {
PangeaController pangeaController = MatrixState.pangeaController;
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return SwitchListTile.adaptive(
activeColor: AppConfig.activeToggleColor,
title: Text(L10n.of(context).doNotShowAgain),
value: widget.instructionsKey.isToggledOff,
onChanged: ((value) async {
widget.instructionsKey.setToggledOff(value);
setState(() {});
}),
);
}
}

View file

@ -1,11 +1,7 @@
import 'dart:developer';
import 'dart:math';
import 'package:flutter/foundation.dart';
import 'package:collection/collection.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/pangea/constants/language_constants.dart';
import 'package:fluffychat/pangea/constants/pangea_event_types.dart';
import 'package:fluffychat/pangea/enum/activity_type_enum.dart';
@ -20,6 +16,9 @@ import 'package:fluffychat/pangea/repo/lemma_info/lemma_info_repo.dart';
import 'package:fluffychat/pangea/repo/lemma_info/lemma_info_request.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/foundation.dart';
import 'package:matrix/matrix.dart';
import '../constants/model_keys.dart';
import 'lemma.dart';
@ -313,8 +312,15 @@ class PangeaToken {
case ActivityTypeEnum.hiddenWordListening:
return daysSinceLastUseByType(a) > 7;
case ActivityTypeEnum.lemmaId:
return _didActivitySuccessfully(ActivityTypeEnum.wordMeaning) &&
daysSinceLastUseByType(a) > 7;
return false;
// disabling lemma activities for now
// It has 2 purposes: learning value triangulating our determination of the lemma with
// AI plus user verification.However, displaying the lemma during the meaning activity helps
// disambiguate what the meaning activity is about. This is probably more valuable than the
// lemma activity itself. The piping for the lemma activity will stay there if we want to turn
//it back on, maybe in select instances.
// return _didActivitySuccessfully(ActivityTypeEnum.wordMeaning) &&
// daysSinceLastUseByType(a) > 7;
case ActivityTypeEnum.emoji:
return true;
case ActivityTypeEnum.morphId:

View file

@ -1,16 +1,17 @@
import 'dart:developer';
import 'package:flutter/foundation.dart';
import 'package:collection/collection.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:fluffychat/pangea/enum/activity_display_instructions_enum.dart';
import 'package:fluffychat/pangea/enum/activity_type_enum.dart';
import 'package:fluffychat/pangea/enum/analytics/morph_categories_enum.dart';
import 'package:fluffychat/pangea/enum/construct_type_enum.dart';
import 'package:fluffychat/pangea/models/pangea_token_model.dart';
import 'package:fluffychat/pangea/models/practice_activities.dart/multiple_choice_activity_model.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
class ConstructIdentifier {
final String lemma;
@ -228,7 +229,47 @@ class PracticeActivityModel {
required this.content,
});
String get question => content.question;
String get targetLemma =>
targetTokens?.first.lemma.text ??
tgtConstructs
.firstWhereOrNull(
(element) => element.type == ConstructTypeEnum.vocab,
)
?.lemma ??
"___";
String get partOfSpeech =>
targetTokens?.first.pos ??
tgtConstructs
.firstWhereOrNull(
(element) => element.type == ConstructTypeEnum.vocab,
)
?.category ??
"___";
String get targetWordForm => targetTokens?.first.text.content ?? "___";
/// we were setting the question copy on creation of the activity
/// but, in order to localize the question using the same system
/// as other copy, we should do it with context, when it is built
/// some types are doing this now, others should be migrated
String question(BuildContext context, String? morphFeature) {
switch (activityType) {
case ActivityTypeEnum.hiddenWordListening:
case ActivityTypeEnum.wordFocusListening:
case ActivityTypeEnum.lemmaId:
return content.question;
case ActivityTypeEnum.emoji:
return L10n.of(context).pickAnEmoji(targetLemma, partOfSpeech);
case ActivityTypeEnum.wordMeaning:
return L10n.of(context).whatIsMeaning(targetLemma, partOfSpeech);
case ActivityTypeEnum.morphId:
return L10n.of(context).whatIsTheMorphTag(
getMorphologicalCategoryCopy(morphFeature!, context) ?? morphFeature,
targetWordForm,
);
}
}
factory PracticeActivityModel.fromJson(Map<String, dynamic> json) {
// moving from multiple_choice to content as the key

View file

@ -1,10 +1,10 @@
import 'package:matrix/matrix.dart';
import 'package:fluffychat/pangea/constants/model_keys.dart';
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
import 'package:fluffychat/pangea/enum/instructions_enum.dart';
import 'package:fluffychat/pangea/instructions/instruction_settings.dart';
import 'package:fluffychat/pangea/models/space_model.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:matrix/matrix.dart';
import 'language_model.dart';
/// The user's settings learning settings.
@ -187,142 +187,20 @@ class UserToolSettings {
}
}
/// The user's settings for whether or not to show instuction messages.
class UserInstructions {
bool showedItInstructions;
bool showedClickMessage;
bool showedBlurMeansTranslate;
bool showedTooltipInstructions;
bool showedMissingVoice;
bool showedClickBestOption;
bool showedUnlockedLanguageTools;
bool showedSpeechToTextTooltip;
bool showedL1TranslationTooltip;
bool showedTranslationChoicesTooltip;
bool showedClickAgainToDeselect;
UserInstructions({
this.showedItInstructions = false,
this.showedClickMessage = false,
this.showedBlurMeansTranslate = false,
this.showedTooltipInstructions = false,
this.showedSpeechToTextTooltip = false,
this.showedL1TranslationTooltip = false,
this.showedTranslationChoicesTooltip = false,
this.showedClickAgainToDeselect = false,
this.showedMissingVoice = false,
this.showedClickBestOption = false,
this.showedUnlockedLanguageTools = false,
});
factory UserInstructions.fromJson(Map<String, dynamic> json) =>
UserInstructions(
showedItInstructions: json[InstructionsEnum.itInstructions.toString()],
showedClickMessage:
json[InstructionsEnum.clickMessage.toString()] ?? false,
showedBlurMeansTranslate:
json[InstructionsEnum.blurMeansTranslate.toString()] ?? false,
showedTooltipInstructions:
json[InstructionsEnum.tooltipInstructions.toString()] ?? false,
showedL1TranslationTooltip:
json[InstructionsEnum.l1Translation.toString()] ?? false,
showedTranslationChoicesTooltip:
json[InstructionsEnum.translationChoices.toString()] ?? false,
showedSpeechToTextTooltip:
json[InstructionsEnum.speechToText.toString()] ?? false,
showedClickAgainToDeselect:
json[InstructionsEnum.clickAgainToDeselect.toString()] ?? false,
showedMissingVoice:
json[InstructionsEnum.missingVoice.toString()] ?? false,
showedClickBestOption:
json[InstructionsEnum.clickBestOption.toString()] ?? false,
showedUnlockedLanguageTools:
json[InstructionsEnum.unlockedLanguageTools.toString()] ?? false,
);
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data[InstructionsEnum.itInstructions.toString()] = showedItInstructions;
data[InstructionsEnum.clickMessage.toString()] = showedClickMessage;
data[InstructionsEnum.blurMeansTranslate.toString()] =
showedBlurMeansTranslate;
data[InstructionsEnum.tooltipInstructions.toString()] =
showedTooltipInstructions;
data[InstructionsEnum.l1Translation.toString()] =
showedL1TranslationTooltip;
data[InstructionsEnum.translationChoices.toString()] =
showedTranslationChoicesTooltip;
data[InstructionsEnum.speechToText.toString()] = showedSpeechToTextTooltip;
data[InstructionsEnum.clickAgainToDeselect.toString()] =
showedClickAgainToDeselect;
data[InstructionsEnum.missingVoice.toString()] = showedMissingVoice;
data[InstructionsEnum.clickBestOption.toString()] = showedClickBestOption;
data[InstructionsEnum.unlockedLanguageTools.toString()] =
showedUnlockedLanguageTools;
return data;
}
factory UserInstructions.migrateFromAccountData() {
final accountData =
MatrixState.pangeaController.matrixState.client.accountData;
return UserInstructions(
showedItInstructions:
(accountData[InstructionsEnum.itInstructions.toString()]
?.content[InstructionsEnum.itInstructions.toString()]
as bool?) ??
false,
showedClickMessage: (accountData[InstructionsEnum.clickMessage.toString()]
?.content[InstructionsEnum.clickMessage.toString()] as bool?) ??
false,
showedBlurMeansTranslate:
(accountData[InstructionsEnum.blurMeansTranslate.toString()]
?.content[InstructionsEnum.blurMeansTranslate.toString()]
as bool?) ??
false,
showedTooltipInstructions:
(accountData[InstructionsEnum.tooltipInstructions.toString()]
?.content[InstructionsEnum.tooltipInstructions.toString()]
as bool?) ??
false,
showedL1TranslationTooltip:
(accountData[InstructionsEnum.l1Translation.toString()]
?.content[InstructionsEnum.l1Translation.toString()]
as bool?) ??
false,
showedTranslationChoicesTooltip:
(accountData[InstructionsEnum.translationChoices.toString()]
?.content[InstructionsEnum.translationChoices.toString()]
as bool?) ??
false,
showedSpeechToTextTooltip:
(accountData[InstructionsEnum.speechToText.toString()]
?.content[InstructionsEnum.speechToText.toString()]
as bool?) ??
false,
showedClickAgainToDeselect: (accountData[
InstructionsEnum.clickAgainToDeselect.toString()]
?.content[InstructionsEnum.clickAgainToDeselect.toString()]
as bool?) ??
false,
);
}
}
/// A wrapper around the matrix account data for the user profile.
/// Enables easy access to the profile data and saving new data.
class Profile {
late UserSettings userSettings;
late UserToolSettings toolSettings;
late UserInstructions instructionSettings;
late InstructionSettings instructionSettings;
Profile({
required this.userSettings,
UserToolSettings? toolSettings,
UserInstructions? instructionSettings,
InstructionSettings? instructionSettings,
}) {
this.toolSettings = toolSettings ?? UserToolSettings();
this.instructionSettings = instructionSettings ?? UserInstructions();
this.instructionSettings = instructionSettings ?? InstructionSettings();
}
/// Load an instance of profile from the client's account data.
@ -347,10 +225,10 @@ class Profile {
)
: UserToolSettings(),
instructionSettings: instructionSettingsContent != null
? UserInstructions.fromJson(
? InstructionSettings.fromJson(
instructionSettingsContent as Map<String, dynamic>,
)
: UserInstructions(),
: InstructionSettings(),
);
}
@ -370,7 +248,7 @@ class Profile {
if (userSettings == null) return null;
final toolSettings = UserToolSettings.migrateFromAccountData();
final instructionSettings = UserInstructions.migrateFromAccountData();
final instructionSettings = InstructionSettings.migrateFromAccountData();
return Profile(
userSettings: userSettings,
toolSettings: toolSettings,
@ -417,7 +295,7 @@ class Profile {
return Profile(
userSettings: UserSettings(),
toolSettings: UserToolSettings(),
instructionSettings: UserInstructions(),
instructionSettings: InstructionSettings(),
);
}
}

View file

@ -1,7 +1,3 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:fluffychat/pangea/enum/activity_type_enum.dart';
import 'package:fluffychat/pangea/enum/construct_type_enum.dart';
import 'package:fluffychat/pangea/models/practice_activities.dart/message_activity_request.dart';
@ -9,6 +5,8 @@ import 'package:fluffychat/pangea/models/practice_activities.dart/multiple_choic
import 'package:fluffychat/pangea/models/practice_activities.dart/practice_activity_model.dart';
import 'package:fluffychat/pangea/repo/lemma_info/lemma_info_repo.dart';
import 'package:fluffychat/pangea/repo/lemma_info/lemma_info_request.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
class WordMeaningActivityGenerator {
Future<MessageActivityResponse> get(
@ -48,7 +46,8 @@ class WordMeaningActivityGenerator {
langCode: req.userL2,
activityType: ActivityTypeEnum.wordMeaning,
content: ActivityContent(
question: L10n.of(context).chooseBestDefinition,
question:
L10n.of(context).whatIsMeaning(lemmaId.lemma, lemmaId.category),
choices: choices,
answers: [res.meaning],
spanDisplayDetails: null,

View file

@ -1,173 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:fluffychat/pangea/enum/instructions_enum.dart';
import '../../config/app_config.dart';
import '../../widgets/matrix.dart';
import '../controllers/pangea_controller.dart';
import '../widgets/common/bot_face_svg.dart';
import '../widgets/igc/card_header.dart';
import 'bot_style.dart';
import 'overlay.dart';
class InstructionsController {
late PangeaController _pangeaController;
// We have these three methods to make sure that the instructions are not shown too much
/// Instruction popup was closed by the user
final Map<String, bool> _instructionsClosed = {};
/// Instruction popup has already been shown this session
final Map<String, bool> _instructionsShown = {};
InstructionsController(PangeaController pangeaController) {
_pangeaController = pangeaController;
}
void setToggledOff(
InstructionsEnum key,
bool value,
) {
_pangeaController.userController.updateProfile((profile) {
switch (key) {
case InstructionsEnum.speechToText:
profile.instructionSettings.showedSpeechToTextTooltip = value;
break;
case InstructionsEnum.l1Translation:
profile.instructionSettings.showedL1TranslationTooltip = value;
break;
case InstructionsEnum.translationChoices:
profile.instructionSettings.showedTranslationChoicesTooltip = value;
break;
case InstructionsEnum.tooltipInstructions:
profile.instructionSettings.showedTooltipInstructions = value;
break;
case InstructionsEnum.itInstructions:
profile.instructionSettings.showedItInstructions = value;
break;
case InstructionsEnum.clickMessage:
profile.instructionSettings.showedClickMessage = value;
break;
case InstructionsEnum.blurMeansTranslate:
profile.instructionSettings.showedBlurMeansTranslate = value;
break;
case InstructionsEnum.clickAgainToDeselect:
profile.instructionSettings.showedClickAgainToDeselect = value;
break;
case InstructionsEnum.missingVoice:
profile.instructionSettings.showedMissingVoice = value;
break;
case InstructionsEnum.clickBestOption:
profile.instructionSettings.showedClickBestOption = value;
break;
case InstructionsEnum.unlockedLanguageTools:
profile.instructionSettings.showedUnlockedLanguageTools = value;
break;
}
return profile;
});
}
/// Instruction Card gives users tips on
/// how to use Pangea Chat's features
Future<void> showInstructionsPopup(
BuildContext context,
InstructionsEnum key,
String transformTargetKey, {
bool showToggle = true,
Widget? customContent,
bool forceShow = false,
}) async {
final bool userLangsSet =
await _pangeaController.userController.areUserLanguagesSet;
if (!userLangsSet) {
return;
}
if ((_instructionsShown[key.toString()] ?? false) && !forceShow) {
return;
}
_instructionsShown[key.toString()] = true;
if (key.toggledOff() && !forceShow) {
return;
}
final botStyle = BotStyle.text(context);
Future.delayed(
const Duration(seconds: 1),
() {
if (!context.mounted) return;
OverlayUtil.showPositionedCard(
context: context,
backDropToDismiss: false,
cardToShow: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
CardHeader(
text: key.title(L10n.of(context)),
botExpression: BotExpression.idle,
onClose: () => {_instructionsClosed[key.toString()] = true},
),
const SizedBox(height: 10.0),
Padding(
padding: const EdgeInsets.all(6.0),
child: Text(
key.body(L10n.of(context)),
style: botStyle,
),
),
if (customContent != null) customContent,
if (showToggle) InstructionsToggle(instructionsKey: key),
],
),
maxHeight: 300,
maxWidth: 300,
transformTargetId: transformTargetKey,
closePrevOverlay: false,
);
},
);
}
}
/// User can toggle on to prevent Instruction Card
/// from appearing in future sessions
class InstructionsToggle extends StatefulWidget {
const InstructionsToggle({
super.key,
required this.instructionsKey,
});
final InstructionsEnum instructionsKey;
@override
InstructionsToggleState createState() => InstructionsToggleState();
}
class InstructionsToggleState extends State<InstructionsToggle> {
PangeaController pangeaController = MatrixState.pangeaController;
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return SwitchListTile.adaptive(
activeColor: AppConfig.activeToggleColor,
title: Text(L10n.of(context).doNotShowAgain),
value: widget.instructionsKey.toggledOff(),
onChanged: ((value) async {
pangeaController.instructions.setToggledOff(
widget.instructionsKey,
value,
);
setState(() {});
}),
);
}
}

View file

@ -1,22 +1,21 @@
import 'dart:developer';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pangea/enum/instructions_enum.dart';
import 'package:fluffychat/pangea/instructions/instructions_enum.dart';
import 'package:fluffychat/pangea/instructions/instructions_inline_tooltip.dart';
import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart';
import 'package:fluffychat/pangea/models/speech_to_text_models.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:fluffychat/pangea/utils/inline_tooltip.dart';
import 'package:fluffychat/pangea/widgets/chat/toolbar_content_loading_indicator.dart';
import 'package:fluffychat/pangea/widgets/common/icon_number_widget.dart';
import 'package:fluffychat/pangea/widgets/igc/card_error_widget.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:material_symbols_icons/symbols.dart';
import '../../utils/bot_style.dart';
class MessageSpeechToTextCard extends StatefulWidget {
@ -214,7 +213,7 @@ class MessageSpeechToTextCardState extends State<MessageSpeechToTextCard> {
mainAxisSize: MainAxisSize.min,
children: [
Expanded(
child: InlineTooltip(
child: InstructionsInlineTooltip(
instructionsEnum: InstructionsEnum.speechToText,
),
),

View file

@ -1,16 +1,15 @@
import 'package:flutter/material.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pangea/enum/instructions_enum.dart';
import 'package:fluffychat/pangea/instructions/instructions_enum.dart';
import 'package:fluffychat/pangea/instructions/instructions_inline_tooltip.dart';
import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart';
import 'package:fluffychat/pangea/models/pangea_token_text_model.dart';
import 'package:fluffychat/pangea/models/representation_content_model.dart';
import 'package:fluffychat/pangea/repo/full_text_translation_repo.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:fluffychat/pangea/utils/inline_tooltip.dart';
import 'package:fluffychat/pangea/widgets/chat/toolbar_content_loading_indicator.dart';
import 'package:fluffychat/pangea/widgets/igc/card_error_widget.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/material.dart';
class MessageTranslationCard extends StatefulWidget {
final PangeaMessageEvent messageEvent;
@ -172,24 +171,24 @@ class MessageTranslationCardState extends State<MessageTranslationCard> {
),
if (notGoingToTranslate &&
widget.selection == null &&
!InstructionsEnum.l1Translation.toggledOff())
!InstructionsEnum.l1Translation.isToggledOff)
const Row(
mainAxisSize: MainAxisSize.min,
children: [
Expanded(
child: InlineTooltip(
child: InstructionsInlineTooltip(
instructionsEnum: InstructionsEnum.l1Translation,
),
),
],
),
if (widget.selection != null &&
!InstructionsEnum.clickAgainToDeselect.toggledOff())
!InstructionsEnum.clickAgainToDeselect.isToggledOff)
const Row(
mainAxisSize: MainAxisSize.min,
children: [
Expanded(
child: InlineTooltip(
child: InstructionsInlineTooltip(
instructionsEnum: InstructionsEnum.clickAgainToDeselect,
),
),

View file

@ -1,19 +1,18 @@
import 'dart:async';
import 'dart:developer';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_tts/flutter_tts.dart' as flutter_tts;
import 'package:matrix/matrix_api_lite/utils/logs.dart';
import 'package:text_to_speech/text_to_speech.dart';
import 'package:fluffychat/pangea/controllers/user_controller.dart';
import 'package:fluffychat/pangea/enum/instructions_enum.dart';
import 'package:fluffychat/pangea/instructions/instructions_enum.dart';
import 'package:fluffychat/pangea/instructions/instructions_show_popup.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:fluffychat/pangea/widgets/chat/missing_voice_button.dart';
import 'package:fluffychat/utils/platform_infos.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_tts/flutter_tts.dart' as flutter_tts;
import 'package:matrix/matrix_api_lite/utils/logs.dart';
import 'package:text_to_speech/text_to_speech.dart';
class TtsController {
String? get targetLanguage =>
@ -163,7 +162,7 @@ class TtsController {
BuildContext context,
String eventID,
) async {
await MatrixState.pangeaController.instructions.showInstructionsPopup(
await instructionsShowPopup(
context,
InstructionsEnum.missingVoice,
eventID,

View file

@ -1,20 +1,20 @@
import 'dart:developer';
import 'dart:ui';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pages/chat/chat.dart';
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
import 'package:fluffychat/pangea/enum/instructions_enum.dart';
import 'package:fluffychat/pangea/instructions/instructions_enum.dart';
import 'package:fluffychat/pangea/instructions/instructions_show_popup.dart';
import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart';
import 'package:fluffychat/pangea/models/representation_content_model.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:fluffychat/pangea/widgets/chat/message_toolbar_selection_area.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:matrix/matrix.dart';
import '../../models/pangea_match_model.dart';
class PangeaRichText extends StatefulWidget {
@ -131,7 +131,7 @@ class PangeaRichTextState extends State<PangeaRichText> {
@override
Widget build(BuildContext context) {
if (blur > 0) {
pangeaController.instructions.showInstructionsPopup(
instructionsShowPopup(
context,
InstructionsEnum.blurMeansTranslate,
widget.pangeaMessageEvent.eventId,

View file

@ -1,8 +1,7 @@
import 'package:flutter/material.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pangea/enum/instructions_enum.dart';
import 'package:fluffychat/pangea/utils/inline_tooltip.dart';
import 'package:fluffychat/pangea/instructions/instructions_enum.dart';
import 'package:fluffychat/pangea/instructions/instructions_inline_tooltip.dart';
import 'package:flutter/material.dart';
class StarAnimationWidget extends StatefulWidget {
const StarAnimationWidget({super.key});
@ -79,7 +78,7 @@ class GamifiedTextWidget extends StatelessWidget {
child: Column(
children: [
StarAnimationWidget(),
InlineTooltip(
InstructionsInlineTooltip(
instructionsEnum: InstructionsEnum.unlockedLanguageTools,
),
],

View file

@ -1,11 +1,7 @@
import 'dart:async';
import 'dart:developer';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:collection/collection.dart';
import 'package:fluffychat/pangea/controllers/message_analytics_controller.dart';
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
import 'package:fluffychat/pangea/controllers/put_analytics_controller.dart';
@ -26,6 +22,8 @@ import 'package:fluffychat/pangea/widgets/igc/card_error_widget.dart';
import 'package:fluffychat/pangea/widgets/practice_activity/multiple_choice_activity.dart';
import 'package:fluffychat/pangea/widgets/word_zoom/word_zoom_widget.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
/// The wrapper for practice activity content.
/// Handles the activities associated with a message,
@ -70,6 +68,8 @@ class PracticeActivityCardState extends State<PracticeActivityCard> {
PangeaController get pangeaController => MatrixState.pangeaController;
String? _error;
String? activityQuestion;
@override
void initState() {
super.initState();
@ -122,7 +122,9 @@ class PracticeActivityCardState extends State<PracticeActivityCard> {
}
currentCompletionRecord = PracticeActivityRecordModel(
question: activity.question,
question: mounted
? currentActivity?.question(context, widget.morphFeature)
: currentActivity?.content.question,
);
} catch (e, s) {
ErrorHandler.logError(

View file

@ -96,7 +96,7 @@ class LemmaMeaningWidgetState extends State<LemmaMeaningWidget> {
child: Column(
children: [
Text(
L10n.of(context).editLemmaMeaning,
"${L10n.of(context).pangeaBotIsFallible} ${L10n.of(context).whatIsMeaning(widget.lemma, widget.pos)}",
textAlign: TextAlign.center,
style: const TextStyle(fontStyle: FontStyle.italic),
),
@ -138,9 +138,13 @@ class LemmaMeaningWidgetState extends State<LemmaMeaningWidget> {
return GestureDetector(
onLongPress: () => _toggleEditMode(true),
onDoubleTap: () => _toggleEditMode(true),
child: Text(
snapshot.data!.meaning,
textAlign: TextAlign.center,
child: Tooltip(
message: L10n.of(context).doubleClickToEdit,
waitDuration: const Duration(milliseconds: 2000),
child: Text(
snapshot.data!.meaning,
textAlign: TextAlign.center,
),
),
);
},

View file

@ -3,6 +3,7 @@
import 'package:fluffychat/pangea/constants/model_keys.dart';
import 'package:fluffychat/pangea/constants/morph_categories_and_labels.dart';
import 'package:fluffychat/pangea/enum/analytics/morph_categories_enum.dart';
import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart';
import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart';
import 'package:fluffychat/pangea/models/pangea_token_model.dart';
@ -67,7 +68,12 @@ class MorphologicalCenterWidgetState extends State<MorphologicalCenterWidget> {
PangeaMessageEvent get pm => widget.pangeaMessageEvent;
Future<void> confirmChanges() async {
/// 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
@ -85,7 +91,7 @@ class MorphologicalCenterWidgetState extends State<MorphologicalCenterWidget> {
if (tokenIndex == -1) {
throw Exception("Token not found in message");
}
existingTokens[tokenIndex].morph[widget.morphFeature] = selectedMorphTag;
existingTokens[tokenIndex] = changeCallback(existingTokens[tokenIndex]);
// send a new message as an edit to original message to the server
// including the new tokens
@ -123,22 +129,27 @@ class MorphologicalCenterWidgetState extends State<MorphologicalCenterWidget> {
}
}
List<String> get allMorphTagsForEdit {
final List<String> tags = getLabelsForMorphCategory(widget.morphFeature)
.where(
(tag) => !["punct", "space", "sym", "x", "other"]
.contains(tag.toLowerCase()),
)
.toList();
/// all morphological tags for the selected morphological category
/// that are eligible for setting as the morphological tag
List<String> get allMorphTagsForEdit =>
getLabelsForMorphCategory(widget.morphFeature)
.where(
(tag) => !["punct", "space", "sym", "x", "other"]
.contains(tag.toLowerCase()),
)
.toList();
// as long as the feature is not POS, add a nan tag
// this will allow the user to remove the feature from the tags
if (widget.morphFeature.toLowerCase() != "pos") {
tags.add(L10n.of(context).constructUseNanDesc);
}
String get morphCopy =>
getMorphologicalCategoryCopy(widget.morphFeature, context) ??
widget.morphFeature;
return tags;
}
String get tagCopy =>
getGrammarCopy(
category: widget.morphFeature,
lemma: selectedMorphTag,
context: context,
) ??
selectedMorphTag;
@override
Widget build(BuildContext context) {
@ -146,14 +157,13 @@ class MorphologicalCenterWidgetState extends State<MorphologicalCenterWidget> {
return GestureDetector(
onLongPress: enterEditMode,
onDoubleTap: enterEditMode,
child: Text(
getGrammarCopy(
category: widget.morphFeature,
lemma: widget.token.morph[widget.morphFeature],
context: context,
) ??
widget.token.morph[widget.morphFeature]!,
textAlign: TextAlign.center,
child: Tooltip(
message: L10n.of(context).doubleClickToEdit,
waitDuration: const Duration(milliseconds: 2000),
child: Text(
"$morphCopy: $tagCopy",
textAlign: TextAlign.center,
),
),
);
}
@ -161,7 +171,10 @@ class MorphologicalCenterWidgetState extends State<MorphologicalCenterWidget> {
return Column(
children: [
Text(
L10n.of(context).editMorphologicalLabel,
"${L10n.of(context).pangeaBotIsFallible} ${L10n.of(context).whatIsTheMorphTag(
morphCopy,
widget.token.text.content,
)}",
textAlign: TextAlign.center,
style: const TextStyle(fontStyle: FontStyle.italic),
),
@ -228,8 +241,20 @@ class MorphologicalCenterWidgetState extends State<MorphologicalCenterWidget> {
),
const SizedBox(height: 10),
Row(
spacing: 10,
children: [
//cancel button
// this would allow the user to totally remove the morphological feature from the token
// disabled and let's see if someone asks for it
// if (widget.morphFeature.toLowerCase() != "pos")
// TextButton(
// onPressed: () => saveChanges(
// (token) {
// token.morph.remove(widget.morphFeature);
// return token;
// },
// ),
// child: Text(L10n.of(context).removeFeature(morphCopy)),
// ),
ElevatedButton(
onPressed: () {
setState(() {
@ -238,15 +263,19 @@ class MorphologicalCenterWidgetState extends State<MorphologicalCenterWidget> {
},
child: Text(L10n.of(context).cancel),
),
const SizedBox(width: 10),
ElevatedButton(
onPressed:
selectedMorphTag == widget.token.morph[widget.morphFeature]
? null
: () => showFutureLoadingDialog(
context: context,
future: confirmChanges,
),
onPressed: selectedMorphTag ==
widget.token.morph[widget.morphFeature]
? null
: () => showFutureLoadingDialog(
context: context,
future: () => saveChanges(
(token) {
token.morph[widget.morphFeature] = selectedMorphTag;
return token;
},
),
),
child: Text(L10n.of(context).saveChanges),
),
],