feat: show lemma under token, give lemma activity first if applicable (#1347)

pull/1593/head
ggurdin 10 months ago committed by GitHub
parent 317cf88aca
commit 14a1ae2287
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -4659,5 +4659,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": "Choose the best definition"
"chooseBestDefinition": "Choose the best definition",
"chooseBaseForm": "Choose the base form"
}

@ -217,6 +217,8 @@ IconData getIconForMorphFeature(String feature) {
return Icons.swap_vert;
case 'definite':
return Icons.check_circle_outline;
case 'prepcase':
return Icons.location_on_outlined;
default:
debugger(when: kDebugMode);
return Icons.help_outline;

@ -6,10 +6,13 @@ 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/widgets/matrix.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
class LemmaActivityGenerator {
Future<MessageActivityResponse> get(
MessageActivityRequest req,
BuildContext context,
) async {
debugger(when: kDebugMode && req.targetTokens.length != 1);
@ -26,7 +29,7 @@ class LemmaActivityGenerator {
tgtConstructs: [token.vocabConstructID],
langCode: req.userL2,
content: ActivityContent(
question: "",
question: L10n.of(context).chooseBaseForm,
choices: choices,
answers: [token.lemma.text],
spanDisplayDetails: null,

@ -127,7 +127,7 @@ class PracticeGenerationController {
case ActivityTypeEnum.emoji:
return _emoji.get(req);
case ActivityTypeEnum.lemmaId:
return _lemma.get(req);
return _lemma.get(req, context);
case ActivityTypeEnum.morphId:
return _morph.get(req);
case ActivityTypeEnum.wordMeaning:

@ -1,25 +1,41 @@
import 'package:fluffychat/pangea/models/pangea_token_model.dart';
import 'package:fluffychat/pangea/widgets/practice_activity/word_zoom_activity_button.dart';
import 'package:flutter/material.dart';
class LemmaWidget extends StatelessWidget {
final PangeaToken token;
final VoidCallback onPressed;
final bool isSelected;
const LemmaWidget({
super.key,
required this.token,
required this.onPressed,
this.isSelected = false,
});
@override
Widget build(BuildContext context) {
return WordZoomActivityButton(
icon: Text(token.xpEmoji),
isSelected: isSelected,
onPressed: onPressed,
return Padding(
padding: const EdgeInsets.all(4.0),
child: Text("${token.lemma.text} ${token.xpEmoji}"),
);
}
}
// class LemmaWidget extends StatelessWidget {
// final PangeaToken token;
// final VoidCallback onPressed;
// final bool isSelected;
// const LemmaWidget({
// super.key,
// required this.token,
// required this.onPressed,
// this.isSelected = false,
// });
// @override
// Widget build(BuildContext context) {
// return WordZoomActivityButton(
// icon: Text(token.xpEmoji),
// isSelected: isSelected,
// onPressed: onPressed,
// );
// }
// }

@ -1,3 +1,5 @@
import 'dart:async';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pangea/controllers/message_analytics_controller.dart';
import 'package:fluffychat/pangea/enum/activity_type_enum.dart';
@ -6,6 +8,7 @@ import 'package:fluffychat/pangea/models/pangea_token_model.dart';
import 'package:fluffychat/pangea/repo/lemma_definition_repo.dart';
import 'package:fluffychat/pangea/utils/grammar/get_grammar_copy.dart';
import 'package:fluffychat/pangea/widgets/chat/message_selection_overlay.dart';
import 'package:fluffychat/pangea/widgets/chat/toolbar_content_loading_indicator.dart';
import 'package:fluffychat/pangea/widgets/chat/tts_controller.dart';
import 'package:fluffychat/pangea/widgets/practice_activity/emoji_practice_button.dart';
import 'package:fluffychat/pangea/widgets/practice_activity/practice_activity_card.dart';
@ -59,17 +62,17 @@ class WordZoomWidgetState extends State<WordZoomWidget> {
/// The currently selected word zoom activity type.
/// If an activity should be shown for this type, shows that activity.
/// If not, shows the info related to that activity type.
/// Defaults to the lemma translation.
WordZoomSelection _selectionType = WordZoomSelection.translation;
/// Null if not set yet, since it takes a second to determine if the lemma activity can be shown.
WordZoomSelection? _selectionType;
/// If doing a morphological activity, this is the selected morph feature.
String? _selectedMorphFeature;
/// If true, the activity will be shown regardless of shouldDoActivity.
/// If non-null and not complete, the activity will be shown regardless of shouldDoActivity.
/// Used to show the practice activity card's savor the joy animation.
/// (Analytics sending triggers the point gain animation, do also
/// causes shouldDoActivity to be false. This is a workaround.)
bool _forceShowActivity = false;
Completer<void>? _activityLock;
// The function to determine if lemma distractors can be generated
// is computationally expensive, so we only do it once
@ -84,7 +87,7 @@ class WordZoomWidgetState extends State<WordZoomWidget> {
@override
void initState() {
super.initState();
_setCanGenerateLemmaActivity();
_setInitialSelectionType();
}
@override
@ -92,51 +95,74 @@ class WordZoomWidgetState extends State<WordZoomWidget> {
super.didUpdateWidget(oldWidget);
if (widget.token != oldWidget.token) {
_clean();
_setCanGenerateLemmaActivity();
_setInitialSelectionType();
}
}
void _clean() {
if (mounted) {
setState(() {
_selectionType = WordZoomSelection.translation;
_activityLock = null;
_selectionType = null;
_selectedMorphFeature = null;
_forceShowActivity = false;
});
}
}
void _setCanGenerateLemmaActivity() {
widget.token.canGenerateDistractors(ActivityTypeEnum.lemmaId).then((value) {
if (mounted) setState(() => _canGenerateLemmaActivity = value);
});
Future<void> _setCanGenerateLemmaActivity() async {
final canGenerate =
await widget.token.canGenerateDistractors(ActivityTypeEnum.lemmaId);
if (mounted) setState(() => _canGenerateLemmaActivity = canGenerate);
}
Future<void> _setInitialSelectionType() async {
if (_selectionType != null) return;
await _setCanGenerateLemmaActivity();
_setSelectionType(_defaultSelectionType);
}
void _setSelectionType(WordZoomSelection type, {String? feature}) {
WordZoomSelection get _defaultSelectionType =>
_shouldShowActivity(WordZoomSelection.lemma)
? WordZoomSelection.lemma
: WordZoomSelection.translation;
Future<void> _setSelectionType(
WordZoomSelection type, {
String? feature,
}) async {
WordZoomSelection newSelectedType = type;
String? newSelectedFeature;
if (type != WordZoomSelection.morph) {
// if setting selectionType to non-morph activity, either set it if it's not
// already selected, or reset to it the default type
newSelectedType =
_selectionType == type ? WordZoomSelection.translation : type;
newSelectedType = _selectionType == type ? _defaultSelectionType : type;
} else {
// otherwise (because there could be multiple different morph features), check
// if the feature is already selected, and if so, reset to the default type.
// if not, set the selectionType and feature
newSelectedFeature = _selectedMorphFeature == feature ? null : feature;
newSelectedType = newSelectedFeature == null
? WordZoomSelection.translation
? _defaultSelectionType
: WordZoomSelection.morph;
}
// wait for savor the joy animation to finish before changing the selection type
if (_activityLock != null) await _activityLock!.future;
_selectionType = newSelectedType;
_selectedMorphFeature = newSelectedFeature;
if (mounted) setState(() {});
}
void _setForceShowActivity(bool showActivity) {
if (mounted) setState(() => _forceShowActivity = showActivity);
void _lockActivity() {
if (mounted) setState(() => _activityLock = Completer());
}
void _unlockActivity() {
if (_activityLock == null) return;
_activityLock!.complete();
_activityLock = null;
if (mounted) setState(() {});
}
/// This function should be called before overlayController.onActivityFinish to
@ -145,78 +171,26 @@ class WordZoomWidgetState extends State<WordZoomWidget> {
void onActivityFinish({
Duration savorTheJoyDuration = const Duration(seconds: 1),
}) {
_setForceShowActivity(true);
_lockActivity();
Future.delayed(savorTheJoyDuration, () {
_setForceShowActivity(false);
if (_selectionType == WordZoomSelection.lemma) {
_setSelectionType(WordZoomSelection.translation);
}
_unlockActivity();
});
}
Widget get _wordZoomCenterWidget {
final showActivity = widget.token.shouldDoActivity(
a: _selectionType.activityType,
feature: _selectedMorphFeature,
tag: _selectedMorphFeature == null
? null
: widget.token.morph[_selectedMorphFeature],
) &&
(_selectionType != WordZoomSelection.lemma ||
_canGenerateLemmaActivity) &&
(_selectionType != WordZoomSelection.translation ||
_canGenerateDefintionActivity);
if (showActivity || _forceShowActivity) {
return PracticeActivityCard(
pangeaMessageEvent: widget.messageEvent,
targetTokensAndActivityType: TargetTokensAndActivityType(
tokens: [widget.token],
activityType: _selectionType.activityType,
),
overlayController: widget.overlayController,
morphFeature: _selectedMorphFeature,
wordDetailsController: this,
);
}
if (_selectionType == WordZoomSelection.translation) {
return ContextualTranslationWidget(
token: widget.token,
langCode: widget.messageEvent.messageDisplayLangCode,
);
}
return Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [_activityAnswer],
),
);
}
Widget get _activityAnswer {
switch (_selectionType) {
case WordZoomSelection.morph:
if (_selectedMorphFeature == null) {
return const Text("There should be a selected morph feature");
}
final String morphTag = widget.token.morph[_selectedMorphFeature!];
final copy = getGrammarCopy(
category: _selectedMorphFeature!,
lemma: morphTag,
context: context,
);
return Text(copy ?? morphTag, textAlign: TextAlign.center);
case WordZoomSelection.lemma:
return Text(widget.token.lemma.text, textAlign: TextAlign.center);
case WordZoomSelection.emoji:
return widget.token.getEmoji() != null
? Text(widget.token.getEmoji()!)
: const Text("emoji is null");
case WordZoomSelection.translation:
return const SizedBox();
}
}
bool _shouldShowActivity(WordZoomSelection selection) =>
widget.token.shouldDoActivity(
a: selection.activityType,
feature: _selectedMorphFeature,
tag: _selectedMorphFeature == null
? null
: widget.token.morph[_selectedMorphFeature],
) &&
(selection != WordZoomSelection.lemma || _canGenerateLemmaActivity) &&
(selection != WordZoomSelection.translation ||
_canGenerateDefintionActivity);
@override
Widget build(BuildContext context) {
@ -245,6 +219,7 @@ class WordZoomWidgetState extends State<WordZoomWidget> {
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
EmojiPracticeButton(
token: widget.token,
@ -252,21 +227,37 @@ class WordZoomWidgetState extends State<WordZoomWidget> {
_setSelectionType(WordZoomSelection.emoji),
isSelected: _selectionType == WordZoomSelection.emoji,
),
WordTextWithAudioButton(
text: widget.token.text.content,
ttsController: widget.tts,
eventID: widget.messageEvent.eventId,
),
LemmaWidget(
token: widget.token,
onPressed: () =>
_setSelectionType(WordZoomSelection.lemma),
isSelected: _selectionType == WordZoomSelection.lemma,
Column(
mainAxisSize: MainAxisSize.min,
children: [
WordTextWithAudioButton(
text: widget.token.text.content,
ttsController: widget.tts,
eventID: widget.messageEvent.eventId,
),
// if _selectionType is null, we don't know if the lemma activity
// can be shown yet, so we don't show the lemma definition
if (!_shouldShowActivity(WordZoomSelection.lemma) &&
_selectionType != null)
LemmaWidget(
token: widget.token,
),
],
),
const SizedBox(width: 40),
],
),
),
_wordZoomCenterWidget,
WordZoomCenterWidget(
selectionType: _selectionType,
selectedMorphFeature: _selectedMorphFeature,
shouldDoActivity: _selectionType != null
? _shouldShowActivity(_selectionType!)
: false,
locked:
_activityLock != null && !_activityLock!.isCompleted,
wordDetailsController: this,
),
MorphologicalListWidget(
token: widget.token,
setMorphFeature: (feature) => _setSelectionType(
@ -284,3 +275,101 @@ class WordZoomWidgetState extends State<WordZoomWidget> {
);
}
}
class ActivityAnswerWidget extends StatelessWidget {
final PangeaToken token;
final WordZoomSelection selectionType;
final String? selectedMorphFeature;
const ActivityAnswerWidget({
super.key,
required this.token,
required this.selectionType,
required this.selectedMorphFeature,
});
@override
Widget build(BuildContext context) {
switch (selectionType) {
case WordZoomSelection.morph:
if (selectedMorphFeature == null) {
return const Text("There should be a selected morph feature");
}
final String morphTag = token.morph[selectedMorphFeature!];
final copy = getGrammarCopy(
category: selectedMorphFeature!,
lemma: morphTag,
context: context,
);
return Text(copy ?? morphTag, textAlign: TextAlign.center);
case WordZoomSelection.lemma:
return Text(token.lemma.text, textAlign: TextAlign.center);
case WordZoomSelection.emoji:
return token.getEmoji() != null
? Text(token.getEmoji()!)
: const Text("emoji is null");
case WordZoomSelection.translation:
return const SizedBox();
}
}
}
class WordZoomCenterWidget extends StatelessWidget {
final WordZoomSelection? selectionType;
final String? selectedMorphFeature;
final bool shouldDoActivity;
final bool locked;
final WordZoomWidgetState wordDetailsController;
const WordZoomCenterWidget({
required this.selectionType,
required this.selectedMorphFeature,
required this.shouldDoActivity,
required this.locked,
required this.wordDetailsController,
super.key,
});
@override
Widget build(BuildContext context) {
if (selectionType == null) {
return const ToolbarContentLoadingIndicator();
}
if (shouldDoActivity || locked) {
return PracticeActivityCard(
pangeaMessageEvent: wordDetailsController.widget.messageEvent,
targetTokensAndActivityType: TargetTokensAndActivityType(
tokens: [wordDetailsController.widget.token],
activityType: selectionType!.activityType,
),
overlayController: wordDetailsController.widget.overlayController,
morphFeature: selectedMorphFeature,
wordDetailsController: wordDetailsController,
);
}
if (selectionType == WordZoomSelection.translation) {
return ContextualTranslationWidget(
token: wordDetailsController.widget.token,
langCode:
wordDetailsController.widget.messageEvent.messageDisplayLangCode,
);
}
return Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
ActivityAnswerWidget(
token: wordDetailsController.widget.token,
selectionType: selectionType!,
selectedMorphFeature: selectedMorphFeature,
),
],
),
);
}
}

Loading…
Cancel
Save