feat: moving to lemma def over contextual translation
This commit is contained in:
parent
0368ce3ea7
commit
f626d5fe7e
14 changed files with 303 additions and 303 deletions
|
|
@ -1,3 +1,9 @@
|
|||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
|
||||
const Map<String, List<String>> morphCategoriesAndLabels = {
|
||||
"Pos": [
|
||||
"ADJ",
|
||||
|
|
@ -165,3 +171,50 @@ const Map<String, List<String>> morphCategoriesAndLabels = {
|
|||
"Voice": ["Act", "Mid", "Pass", "Antip", "Cau", "Dir", "Inv", "Rcp", "Caus"],
|
||||
"X": ["X"],
|
||||
};
|
||||
|
||||
// TODO Use the icons that Khue is creating
|
||||
IconData getIconForMorphFeature(String feature) {
|
||||
// Define a function to get the icon based on the universal dependency morphological feature (key)
|
||||
switch (feature.toLowerCase()) {
|
||||
case 'number':
|
||||
// google material 123 icon
|
||||
return Icons.format_list_numbered;
|
||||
case 'gender':
|
||||
return Icons.wc;
|
||||
case 'tense':
|
||||
return Icons.access_time;
|
||||
case 'mood':
|
||||
return Icons.mood;
|
||||
case 'person':
|
||||
return Icons.person;
|
||||
case 'case':
|
||||
return Icons.format_list_bulleted;
|
||||
case 'degree':
|
||||
return Icons.trending_up;
|
||||
case 'verbform':
|
||||
return Icons.text_format;
|
||||
case 'voice':
|
||||
return Icons.record_voice_over;
|
||||
case 'aspect':
|
||||
return Icons.aspect_ratio;
|
||||
case 'prontype':
|
||||
return Icons.text_fields;
|
||||
case 'numtype':
|
||||
return Icons.format_list_numbered;
|
||||
case 'poss':
|
||||
return Icons.account_balance;
|
||||
case 'reflex':
|
||||
return Icons.refresh;
|
||||
case 'foreign':
|
||||
return Icons.language;
|
||||
case 'abbr':
|
||||
return Icons.text_format;
|
||||
case 'nountype':
|
||||
return Symbols.abc;
|
||||
case 'pos':
|
||||
return Symbols.toys_and_games;
|
||||
default:
|
||||
debugger(when: kDebugMode);
|
||||
return Icons.help_outline;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
3
lib/pangea/enum/morphs_enum.dart
Normal file
3
lib/pangea/enum/morphs_enum.dart
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
// this should be an enum eventually though we're using a class for now
|
||||
|
||||
class Morphs {}
|
||||
|
|
@ -229,12 +229,13 @@ class ConstructListModel {
|
|||
final List<ConstructUses> morphConstructs = constructList(
|
||||
type: ConstructTypeEnum.morph,
|
||||
);
|
||||
|
||||
final List<String> possibleDistractors = morphConstructs
|
||||
.where(
|
||||
(c) =>
|
||||
c.category == morphFeature.toLowerCase() &&
|
||||
c.lemma.toLowerCase() != morphTag.toLowerCase(),
|
||||
c.lemma.toLowerCase() != morphTag.toLowerCase() &&
|
||||
c.lemma.isNotEmpty &&
|
||||
c.lemma != "X",
|
||||
)
|
||||
.map((c) => c.lemma)
|
||||
.toList();
|
||||
|
|
|
|||
|
|
@ -322,7 +322,7 @@ class PangeaToken {
|
|||
}
|
||||
}
|
||||
|
||||
bool get shouldDoPosActivity => shouldDoMorphActivity("Pos");
|
||||
bool get shouldDoPosActivity => shouldDoMorphActivity("pos");
|
||||
|
||||
bool shouldDoMorphActivity(String feature) {
|
||||
return shouldDoActivity(
|
||||
|
|
@ -582,11 +582,14 @@ class PangeaToken {
|
|||
}
|
||||
|
||||
String get xpEmoji {
|
||||
if (xp < 5) {
|
||||
if (xp < 30) {
|
||||
// bean emoji
|
||||
return "🫛";
|
||||
} else if (xp < 100) {
|
||||
// sprout emoji
|
||||
return "🌱";
|
||||
} else if (xp < 10) {
|
||||
return "🌿";
|
||||
} else {
|
||||
// flower emoji
|
||||
return "🌺";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -132,6 +132,23 @@ class LemmaDictionaryRepo {
|
|||
return response;
|
||||
}
|
||||
|
||||
/// From the cache, get a random set of cached definitions that are not for a specific lemma
|
||||
static List<String> getDistractorDefinitions(
|
||||
LemmaDefinitionRequest req, int count) {
|
||||
_clearExpiredEntries();
|
||||
|
||||
final List<String> definitions = [];
|
||||
for (final entry in _cache.entries) {
|
||||
if (entry.key.lemma != req.lemma) {
|
||||
definitions.add(entry.value.definition);
|
||||
}
|
||||
}
|
||||
|
||||
definitions.shuffle();
|
||||
|
||||
return definitions.take(count).toList();
|
||||
}
|
||||
|
||||
static void _clearExpiredEntries() {
|
||||
final now = DateTime.now();
|
||||
final expiredKeys = _cacheTimestamps.entries
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import 'dart:developer';
|
||||
|
||||
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/message_activity_request.dart';
|
||||
|
|
@ -30,14 +31,15 @@ class MorphActivityGenerator {
|
|||
"SCONJ": [],
|
||||
"PUNCT": [],
|
||||
"VERB": ["Tense", "Aspect"],
|
||||
"X": [],
|
||||
},
|
||||
};
|
||||
|
||||
/// Get the sequence of activities for a given part of speech
|
||||
/// The sequence is a list of morphological features that should be practiced
|
||||
/// in order for the given part of speech
|
||||
Future<POSActivitySequence> getSequence(String langCode, String pos) async {
|
||||
if (!sequence.containsKey(langCode)) {
|
||||
POSActivitySequence getSequence(String? langCode, String pos) {
|
||||
if (langCode == null || !sequence.containsKey(langCode)) {
|
||||
langCode = "en";
|
||||
}
|
||||
final MorphActivitySequence morphActivitySequence = sequence[langCode]!;
|
||||
|
|
@ -89,7 +91,8 @@ class MorphActivityGenerator {
|
|||
langCode: req.userL2,
|
||||
activityType: ActivityTypeEnum.morphId,
|
||||
content: ActivityContent(
|
||||
question: "",
|
||||
question:
|
||||
"${getMorphologicalCategoryCopy(morphFeature, MatrixState.pangeaController.matrixState.context) ?? ""}?",
|
||||
choices: distractors + [morphTag],
|
||||
answers: [morphTag],
|
||||
spanDisplayDetails: null,
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import 'package:fluffychat/pangea/network/urls.dart';
|
|||
import 'package:fluffychat/pangea/repo/practice/emoji_activity_generator.dart';
|
||||
import 'package:fluffychat/pangea/repo/practice/lemma_activity_generator.dart';
|
||||
import 'package:fluffychat/pangea/repo/practice/morph_activity_generator.dart';
|
||||
import 'package:fluffychat/pangea/repo/practice/word_meaning_activity_generator.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:http/http.dart';
|
||||
|
|
@ -40,9 +41,10 @@ class PracticeGenerationController {
|
|||
|
||||
late PangeaController _pangeaController;
|
||||
|
||||
final MorphActivityGenerator _morph = MorphActivityGenerator();
|
||||
final EmojiActivityGenerator _emoji = EmojiActivityGenerator();
|
||||
final LemmaActivityGenerator _lemma = LemmaActivityGenerator();
|
||||
final _morph = MorphActivityGenerator();
|
||||
final _emoji = EmojiActivityGenerator();
|
||||
final _lemma = LemmaActivityGenerator();
|
||||
final _wordMeaning = WordMeaningActivityGenerator();
|
||||
|
||||
PracticeGenerationController() {
|
||||
_pangeaController = MatrixState.pangeaController;
|
||||
|
|
@ -126,10 +128,9 @@ class PracticeGenerationController {
|
|||
return _lemma.get(req);
|
||||
case ActivityTypeEnum.morphId:
|
||||
return _morph.get(req);
|
||||
case ActivityTypeEnum.wordFocusListening:
|
||||
// TODO bring clientside because more efficient
|
||||
case ActivityTypeEnum.wordMeaning:
|
||||
// TODO get correct answer with translation and distractors with distractor service
|
||||
return _wordMeaning.get(req);
|
||||
case ActivityTypeEnum.wordFocusListening:
|
||||
case ActivityTypeEnum.hiddenWordListening:
|
||||
return _fetchFromServer(
|
||||
accessToken: accessToken,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
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';
|
||||
import 'package:fluffychat/pangea/models/practice_activities.dart/multiple_choice_activity_model.dart';
|
||||
import 'package:fluffychat/pangea/models/practice_activities.dart/practice_activity_model.dart';
|
||||
import 'package:fluffychat/pangea/repo/lemma_definition_repo.dart';
|
||||
|
||||
class WordMeaningActivityGenerator {
|
||||
Future<MessageActivityResponse> get(
|
||||
MessageActivityRequest req,
|
||||
) async {
|
||||
final ConstructIdentifier lemmaId = ConstructIdentifier(
|
||||
lemma: req.targetTokens[0].lemma.text,
|
||||
type: ConstructTypeEnum.vocab,
|
||||
category: req.targetTokens[0].pos,
|
||||
);
|
||||
|
||||
final LemmaDefinitionRequest lemmaDefReq = LemmaDefinitionRequest(
|
||||
lemma: lemmaId.lemma,
|
||||
partOfSpeech: lemmaId.category,
|
||||
|
||||
/// This assumes that the user's L2 is the language of the lemma
|
||||
lemmaLang: req.userL2,
|
||||
userL1: req.userL1,
|
||||
);
|
||||
|
||||
final res = await LemmaDictionaryRepo.get(lemmaDefReq);
|
||||
|
||||
final choices =
|
||||
LemmaDictionaryRepo.getDistractorDefinitions(lemmaDefReq, 3);
|
||||
|
||||
return MessageActivityResponse(
|
||||
activity: PracticeActivityModel(
|
||||
tgtConstructs: [lemmaId],
|
||||
targetTokens: req.targetTokens,
|
||||
langCode: req.userL2,
|
||||
activityType: ActivityTypeEnum.wordMeaning,
|
||||
content: ActivityContent(
|
||||
question: "?",
|
||||
choices: choices,
|
||||
answers: [res.definition],
|
||||
spanDisplayDetails: null,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -30,14 +30,11 @@ class EmojiPracticeButtonState extends State<EmojiPracticeButton> {
|
|||
}
|
||||
}
|
||||
|
||||
bool get _canDoActivity {
|
||||
final canDo = widget.token.shouldDoActivity(
|
||||
a: ActivityTypeEnum.emoji,
|
||||
feature: null,
|
||||
tag: null,
|
||||
);
|
||||
return canDo;
|
||||
}
|
||||
bool get _shouldDoActivity => widget.token.shouldDoActivity(
|
||||
a: ActivityTypeEnum.emoji,
|
||||
feature: null,
|
||||
tag: null,
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
|
@ -45,17 +42,20 @@ class EmojiPracticeButtonState extends State<EmojiPracticeButton> {
|
|||
return SizedBox(
|
||||
height: 40,
|
||||
width: 40,
|
||||
child: _canDoActivity || emoji != null
|
||||
? IconButton(
|
||||
onPressed: () {
|
||||
widget.onPressed();
|
||||
if (widget.emoji == null && emoji != null) {
|
||||
widget.setEmoji(emoji);
|
||||
}
|
||||
},
|
||||
icon: emoji == null
|
||||
? const Icon(Icons.add_reaction_outlined)
|
||||
: Text(emoji),
|
||||
child: _shouldDoActivity || emoji != null
|
||||
? Opacity(
|
||||
opacity: _shouldDoActivity ? 0.5 : 1,
|
||||
child: IconButton(
|
||||
onPressed: () {
|
||||
widget.onPressed();
|
||||
if (widget.emoji == null && emoji != null) {
|
||||
widget.setEmoji(emoji);
|
||||
}
|
||||
},
|
||||
icon: emoji == null
|
||||
? const Icon(Icons.add_reaction_outlined)
|
||||
: Text(emoji),
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -84,12 +84,14 @@ class WordAudioButtonState extends State<WordTextWithAudioButton> {
|
|||
color: _isPlaying
|
||||
? Theme.of(context).colorScheme.secondary
|
||||
: null,
|
||||
fontSize:
|
||||
Theme.of(context).textTheme.titleLarge?.fontSize,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Icon(
|
||||
_isPlaying ? Icons.play_arrow : Icons.play_arrow_outlined,
|
||||
size: Theme.of(context).textTheme.bodyMedium?.fontSize,
|
||||
size: Theme.of(context).textTheme.titleLarge?.fontSize,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
import 'package:fluffychat/pangea/constants/language_constants.dart';
|
||||
import 'package:fluffychat/pangea/models/pangea_token_model.dart';
|
||||
import 'package:fluffychat/pangea/repo/full_text_translation_repo.dart';
|
||||
import 'package:fluffychat/pangea/repo/lemma_definition_repo.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
|
||||
class ContextualTranslationWidget extends StatefulWidget {
|
||||
final PangeaToken token;
|
||||
|
|
@ -48,26 +47,40 @@ class ContextualTranslationWidgetState
|
|||
}
|
||||
|
||||
Future<void> _fetchDefinition() async {
|
||||
final FullTextTranslationResponseModel response =
|
||||
await FullTextTranslationRepo.translate(
|
||||
accessToken: MatrixState.pangeaController.userController.accessToken,
|
||||
request: FullTextTranslationRequestModel(
|
||||
text: widget.fullText,
|
||||
tgtLang:
|
||||
MatrixState.pangeaController.languageController.userL1?.langCode ??
|
||||
LanguageKeys.defaultLanguage,
|
||||
userL2:
|
||||
MatrixState.pangeaController.languageController.userL2?.langCode ??
|
||||
LanguageKeys.defaultLanguage,
|
||||
userL1:
|
||||
MatrixState.pangeaController.languageController.userL1?.langCode ??
|
||||
LanguageKeys.defaultLanguage,
|
||||
offset: widget.token.text.offset,
|
||||
length: widget.token.text.length,
|
||||
deepL: false,
|
||||
),
|
||||
// final FullTextTranslationResponseModel response =
|
||||
// await FullTextTranslationRepo.translate(
|
||||
// accessToken: MatrixState.pangeaController.userController.accessToken,
|
||||
// request: FullTextTranslationRequestModel(
|
||||
// text: widget.fullText,
|
||||
// tgtLang:
|
||||
// MatrixState.pangeaController.languageController.userL1?.langCode ??
|
||||
// LanguageKeys.defaultLanguage,
|
||||
// userL2:
|
||||
// MatrixState.pangeaController.languageController.userL2?.langCode ??
|
||||
// LanguageKeys.defaultLanguage,
|
||||
// userL1:
|
||||
// MatrixState.pangeaController.languageController.userL1?.langCode ??
|
||||
// LanguageKeys.defaultLanguage,
|
||||
// offset: widget.token.text.offset,
|
||||
// length: widget.token.text.length,
|
||||
// deepL: false,
|
||||
// ),
|
||||
// );
|
||||
// widget.setDefinition(response.bestTranslation);
|
||||
final LemmaDefinitionRequest lemmaDefReq = LemmaDefinitionRequest(
|
||||
lemma: widget.token.lemma.text,
|
||||
partOfSpeech: widget.token.pos,
|
||||
|
||||
/// This assumes that the user's L2 is the language of the lemma
|
||||
lemmaLang: widget.langCode,
|
||||
userL1:
|
||||
MatrixState.pangeaController.languageController.userL1?.langCode ??
|
||||
LanguageKeys.defaultLanguage,
|
||||
);
|
||||
widget.setDefinition(response.bestTranslation);
|
||||
|
||||
final res = await LemmaDictionaryRepo.get(lemmaDefReq);
|
||||
|
||||
widget.setDefinition(res.definition);
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -75,12 +88,12 @@ class ContextualTranslationWidgetState
|
|||
return Center(
|
||||
child: SizedBox(
|
||||
height: 60,
|
||||
width: 60,
|
||||
child: IconButton(
|
||||
iconSize: 30,
|
||||
onPressed: widget.onPressed,
|
||||
icon: const Icon(Symbols.dictionary),
|
||||
),
|
||||
// child: IconButton(
|
||||
// iconSize: 30,
|
||||
// onPressed: widget.onPressed,
|
||||
// icon: const Icon(Symbols.dictionary),
|
||||
// ),
|
||||
child: Text(widget.definition ?? "..."),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
import 'dart:developer';
|
||||
|
||||
import 'package:fluffychat/pangea/enum/activity_type_enum.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/models/pangea_token_model.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:fluffychat/pangea/repo/practice/morph_activity_generator.dart';
|
||||
import 'package:fluffychat/pangea/utils/error_handler.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
|
||||
class ActivityMorph {
|
||||
final String morphFeature;
|
||||
|
|
@ -19,7 +18,7 @@ class ActivityMorph {
|
|||
});
|
||||
}
|
||||
|
||||
class MorphologicalListWidget extends StatefulWidget {
|
||||
class MorphologicalListWidget extends StatelessWidget {
|
||||
final PangeaToken token;
|
||||
final String? selectedMorphFeature;
|
||||
final Function(String?) setMorphFeature;
|
||||
|
|
@ -33,147 +32,53 @@ class MorphologicalListWidget extends StatefulWidget {
|
|||
required this.completedActivities,
|
||||
});
|
||||
|
||||
@override
|
||||
MorphologicalListWidgetState createState() => MorphologicalListWidgetState();
|
||||
}
|
||||
List<ActivityMorph> get _visibleMorphs {
|
||||
// we always start with the part of speech
|
||||
final visibleMorphs = [
|
||||
ActivityMorph(
|
||||
morphFeature: "pos",
|
||||
morphTag: token.pos,
|
||||
revealed: !token.shouldDoPosActivity,
|
||||
// revealed: !shouldDoActivity || !canGenerateDistractors,
|
||||
),
|
||||
];
|
||||
|
||||
class MorphologicalListWidgetState extends State<MorphologicalListWidget> {
|
||||
// TODO: make this is a list of morphological features icons based on MorphActivityGenerator.getSequence
|
||||
// For each item in the sequence,
|
||||
// if shouldDoActivity is true, show the template icon then stop
|
||||
// if shouldDoActivity is false, show the actual icon and value then go to the next item
|
||||
// each pos has a defined set of morphological features to display and do activities for
|
||||
final List<String> seq = MorphActivityGenerator().getSequence(
|
||||
MatrixState.pangeaController.languageController.userL2?.langCode,
|
||||
token.pos,
|
||||
);
|
||||
|
||||
final List<ActivityMorph> _morphs = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_setMorphs();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant MorphologicalListWidget oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (widget.token != oldWidget.token) {
|
||||
_setMorphs();
|
||||
}
|
||||
|
||||
if (widget.completedActivities != oldWidget.completedActivities &&
|
||||
oldWidget.selectedMorphFeature != null) {
|
||||
final oldSelectedMorphIndex = _morphs.indexWhere(
|
||||
(morph) => morph.morphFeature == oldWidget.selectedMorphFeature,
|
||||
);
|
||||
if (oldSelectedMorphIndex != -1 &&
|
||||
oldSelectedMorphIndex < _morphs.length) {
|
||||
setState(
|
||||
() => _morphs[oldSelectedMorphIndex].revealed = true,
|
||||
);
|
||||
|
||||
final nextIndex = oldSelectedMorphIndex + 1;
|
||||
if (nextIndex < _morphs.length) {
|
||||
widget.setMorphFeature(_morphs[nextIndex].morphFeature);
|
||||
} else {
|
||||
widget.setMorphFeature(null);
|
||||
}
|
||||
for (final String feature in seq) {
|
||||
// don't add any more if the last one is not revealed yet
|
||||
if (!visibleMorphs.last.revealed) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _setMorphs() async {
|
||||
_morphs.clear();
|
||||
final morphEntries = widget.token.morph.entries.toList();
|
||||
for (final morphEntry in morphEntries) {
|
||||
final morphFeature = morphEntry.key;
|
||||
final morphTag = morphEntry.value;
|
||||
final shouldDoActivity = widget.token.shouldDoMorphActivity(morphFeature);
|
||||
final canGenerateDistractors = await widget.token.canGenerateDistractors(
|
||||
ActivityTypeEnum.morphId,
|
||||
morphFeature: morphFeature,
|
||||
morphTag: morphTag,
|
||||
);
|
||||
_morphs.add(
|
||||
// check that the feature is in token.morph
|
||||
if (!token.morph.containsKey(feature)) {
|
||||
ErrorHandler.logError(
|
||||
m: "Morphological feature suggested for pos but not found in token",
|
||||
data: {
|
||||
"feature": feature,
|
||||
"token": token,
|
||||
"lang_code": MatrixState
|
||||
.pangeaController.languageController.userL2?.langCode,
|
||||
},
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
visibleMorphs.add(
|
||||
ActivityMorph(
|
||||
morphFeature: morphFeature,
|
||||
morphTag: morphTag,
|
||||
revealed: !shouldDoActivity || !canGenerateDistractors,
|
||||
morphFeature: feature,
|
||||
morphTag: token.morph[feature],
|
||||
revealed: !token.shouldDoMorphActivity(feature),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_morphs.sort((a, b) {
|
||||
if (a.revealed && !b.revealed) {
|
||||
return -1;
|
||||
} else if (!a.revealed && b.revealed) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (a.morphFeature.toLowerCase() == "pos") {
|
||||
return -1;
|
||||
} else if (b.morphFeature.toLowerCase() == "pos") {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return a.morphFeature.compareTo(b.morphFeature);
|
||||
});
|
||||
}
|
||||
|
||||
List<ActivityMorph> get _visibleMorphs {
|
||||
final lastRevealedIndex = _morphs.lastIndexWhere((morph) => morph.revealed);
|
||||
|
||||
// if none of the morphs are revealed, show only the first one
|
||||
if (lastRevealedIndex == -1) {
|
||||
return _morphs.take(1).toList();
|
||||
}
|
||||
|
||||
// show all the revealed morphs + the first one with an activity
|
||||
return _morphs.take(lastRevealedIndex + 2).toList();
|
||||
}
|
||||
|
||||
// TODO Use the icons that Khue is creating
|
||||
IconData _getIconForMorphFeature(String feature) {
|
||||
// Define a function to get the icon based on the universal dependency morphological feature (key)
|
||||
switch (feature.toLowerCase()) {
|
||||
case 'number':
|
||||
// google material 123 icon
|
||||
return Icons.format_list_numbered;
|
||||
case 'gender':
|
||||
return Icons.wc;
|
||||
case 'tense':
|
||||
return Icons.access_time;
|
||||
case 'mood':
|
||||
return Icons.mood;
|
||||
case 'person':
|
||||
return Icons.person;
|
||||
case 'case':
|
||||
return Icons.format_list_bulleted;
|
||||
case 'degree':
|
||||
return Icons.trending_up;
|
||||
case 'verbform':
|
||||
return Icons.text_format;
|
||||
case 'voice':
|
||||
return Icons.record_voice_over;
|
||||
case 'aspect':
|
||||
return Icons.aspect_ratio;
|
||||
case 'prontype':
|
||||
return Icons.text_fields;
|
||||
case 'numtype':
|
||||
return Icons.format_list_numbered;
|
||||
case 'poss':
|
||||
return Icons.account_balance;
|
||||
case 'reflex':
|
||||
return Icons.refresh;
|
||||
case 'foreign':
|
||||
return Icons.language;
|
||||
case 'abbr':
|
||||
return Icons.text_format;
|
||||
case 'nountype':
|
||||
return Symbols.abc;
|
||||
case 'pos':
|
||||
return Symbols.toys_and_games;
|
||||
default:
|
||||
debugger(when: kDebugMode);
|
||||
return Icons.help_outline;
|
||||
}
|
||||
return visibleMorphs;
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -183,11 +88,11 @@ class MorphologicalListWidgetState extends State<MorphologicalListWidget> {
|
|||
return Padding(
|
||||
padding: const EdgeInsets.all(2.0),
|
||||
child: MorphologicalActivityButton(
|
||||
onPressed: widget.setMorphFeature,
|
||||
onPressed: setMorphFeature,
|
||||
morphCategory: morph.morphFeature,
|
||||
icon: _getIconForMorphFeature(morph.morphFeature),
|
||||
icon: getIconForMorphFeature(morph.morphFeature),
|
||||
isUnlocked: morph.revealed,
|
||||
isSelected: widget.selectedMorphFeature == morph.morphFeature,
|
||||
isSelected: selectedMorphFeature == morph.morphFeature,
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
|
|
@ -222,7 +127,7 @@ class MorphologicalActivityButton extends StatelessWidget {
|
|||
context,
|
||||
),
|
||||
child: Opacity(
|
||||
opacity: (isUnlocked && !isSelected) ? 0.75 : 1,
|
||||
opacity: isSelected ? 1 : 0.5,
|
||||
child: IconButton(
|
||||
onPressed: () => onPressed(morphCategory),
|
||||
icon: Icon(icon),
|
||||
|
|
|
|||
|
|
@ -1,51 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:fluffychat/pangea/models/pangea_token_model.dart';
|
||||
|
||||
class PartOfSpeechWidget extends StatefulWidget {
|
||||
final PangeaToken token;
|
||||
|
||||
const PartOfSpeechWidget({super.key, required this.token});
|
||||
|
||||
@override
|
||||
_PartOfSpeechWidgetState createState() => _PartOfSpeechWidgetState();
|
||||
}
|
||||
|
||||
class _PartOfSpeechWidgetState extends State<PartOfSpeechWidget> {
|
||||
late Future<String> _partOfSpeech;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_partOfSpeech = _fetchPartOfSpeech();
|
||||
}
|
||||
|
||||
Future<String> _fetchPartOfSpeech() async {
|
||||
if (widget.token.shouldDoPosActivity) {
|
||||
return '?';
|
||||
} else {
|
||||
return widget.token.pos;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FutureBuilder<String>(
|
||||
future: _partOfSpeech,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const CircularProgressIndicator();
|
||||
} else if (snapshot.hasError) {
|
||||
return Text('Error: ${snapshot.error}');
|
||||
} else {
|
||||
return ActionChip(
|
||||
avatar: const Icon(Icons.label),
|
||||
label: Text(snapshot.data ?? 'No part of speech found'),
|
||||
onPressed: () {
|
||||
// Handle chip click
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -38,7 +38,7 @@ class WordZoomWidget extends StatefulWidget {
|
|||
}
|
||||
|
||||
class WordZoomWidgetState extends State<WordZoomWidget> {
|
||||
ActivityTypeEnum? _activityType;
|
||||
ActivityTypeEnum _activityType = ActivityTypeEnum.wordMeaning;
|
||||
|
||||
// morphological activities
|
||||
String? _selectedMorphFeature;
|
||||
|
|
@ -72,6 +72,8 @@ class WordZoomWidgetState extends State<WordZoomWidget> {
|
|||
});
|
||||
}
|
||||
});
|
||||
|
||||
// if learner should do translation activity then setActivityType to wordMeaning
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -89,23 +91,10 @@ class WordZoomWidgetState extends State<WordZoomWidget> {
|
|||
}
|
||||
}
|
||||
|
||||
bool _showActivityCard(ActivityTypeEnum? activityType) {
|
||||
if (activityType == null) return false;
|
||||
final shouldDo = widget.token.shouldDoActivity(
|
||||
a: activityType,
|
||||
feature: _selectedMorphFeature,
|
||||
tag: _selectedMorphFeature == null
|
||||
? null
|
||||
: widget.token.morph[_selectedMorphFeature],
|
||||
);
|
||||
|
||||
return canGenerateActivity[activityType]! && shouldDo;
|
||||
}
|
||||
|
||||
void _clean() {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_activityType = null;
|
||||
_activityType = ActivityTypeEnum.wordMeaning;
|
||||
_selectedMorphFeature = null;
|
||||
_definition = null;
|
||||
_lemma = null;
|
||||
|
|
@ -117,11 +106,13 @@ class WordZoomWidgetState extends State<WordZoomWidget> {
|
|||
void _setSelectedMorphFeature(String? feature) {
|
||||
_selectedMorphFeature = _selectedMorphFeature == feature ? null : feature;
|
||||
_setActivityType(
|
||||
_selectedMorphFeature == null ? null : ActivityTypeEnum.morphId,
|
||||
_selectedMorphFeature == null
|
||||
? ActivityTypeEnum.wordMeaning
|
||||
: ActivityTypeEnum.morphId,
|
||||
);
|
||||
}
|
||||
|
||||
void _setActivityType(ActivityTypeEnum? activityType) {
|
||||
void _setActivityType(ActivityTypeEnum activityType) {
|
||||
if (mounted) setState(() => _activityType = activityType);
|
||||
}
|
||||
|
||||
|
|
@ -164,6 +155,45 @@ class WordZoomWidgetState extends State<WordZoomWidget> {
|
|||
}
|
||||
}
|
||||
|
||||
Widget get _wordZoomCenterWidget {
|
||||
if (widget.token.shouldDoActivity(
|
||||
a: _activityType,
|
||||
feature: _selectedMorphFeature,
|
||||
tag: _selectedMorphFeature == null
|
||||
? null
|
||||
: widget.token.morph[_selectedMorphFeature],
|
||||
) &&
|
||||
canGenerateActivity[_activityType]!) {
|
||||
PracticeActivityCard(
|
||||
pangeaMessageEvent: widget.messageEvent,
|
||||
targetTokensAndActivityType: TargetTokensAndActivityType(
|
||||
tokens: [widget.token],
|
||||
activityType: _activityType,
|
||||
),
|
||||
overlayController: widget.overlayController,
|
||||
morphFeature: _selectedMorphFeature,
|
||||
wordDetailsController: this,
|
||||
);
|
||||
}
|
||||
|
||||
if (_activityType == ActivityTypeEnum.wordMeaning) {
|
||||
return ContextualTranslationWidget(
|
||||
token: widget.token,
|
||||
fullText: widget.messageEvent.messageDisplayText,
|
||||
langCode: widget.messageEvent.messageDisplayLangCode,
|
||||
onPressed: () => _setActivityType(ActivityTypeEnum.wordMeaning),
|
||||
definition: _definition,
|
||||
setDefinition: _setDefinition,
|
||||
);
|
||||
}
|
||||
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [_activityAnswer],
|
||||
);
|
||||
}
|
||||
|
||||
Widget get _activityAnswer {
|
||||
switch (_activityType) {
|
||||
case ActivityTypeEnum.morphId:
|
||||
|
|
@ -180,7 +210,7 @@ class WordZoomWidgetState extends State<WordZoomWidget> {
|
|||
case ActivityTypeEnum.wordMeaning:
|
||||
return _definition != null
|
||||
? Text(_definition!)
|
||||
: const Text("defintion is null");
|
||||
: const Text("definition is null");
|
||||
case ActivityTypeEnum.lemmaId:
|
||||
return _lemma != null ? Text(_lemma!) : const Text("lemma is null");
|
||||
case ActivityTypeEnum.emoji:
|
||||
|
|
@ -215,7 +245,7 @@ class WordZoomWidgetState extends State<WordZoomWidget> {
|
|||
token: widget.token,
|
||||
onPressed: () => _setActivityType(
|
||||
_activityType == ActivityTypeEnum.emoji
|
||||
? null
|
||||
? ActivityTypeEnum.wordMeaning
|
||||
: ActivityTypeEnum.emoji,
|
||||
),
|
||||
setEmoji: _setEmoji,
|
||||
|
|
@ -229,7 +259,7 @@ class WordZoomWidgetState extends State<WordZoomWidget> {
|
|||
token: widget.token,
|
||||
onPressed: () => _setActivityType(
|
||||
_activityType == ActivityTypeEnum.lemmaId
|
||||
? null
|
||||
? ActivityTypeEnum.wordMeaning
|
||||
: ActivityTypeEnum.lemmaId,
|
||||
),
|
||||
lemma: _lemma,
|
||||
|
|
@ -238,34 +268,7 @@ class WordZoomWidgetState extends State<WordZoomWidget> {
|
|||
],
|
||||
),
|
||||
),
|
||||
if (_activityType != null)
|
||||
if (_showActivityCard(_activityType))
|
||||
PracticeActivityCard(
|
||||
pangeaMessageEvent: widget.messageEvent,
|
||||
targetTokensAndActivityType: TargetTokensAndActivityType(
|
||||
tokens: [widget.token],
|
||||
activityType: _activityType!,
|
||||
),
|
||||
overlayController: widget.overlayController,
|
||||
morphFeature: _selectedMorphFeature,
|
||||
wordDetailsController: this,
|
||||
)
|
||||
else
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [_activityAnswer],
|
||||
)
|
||||
else
|
||||
ContextualTranslationWidget(
|
||||
token: widget.token,
|
||||
fullText: widget.messageEvent.messageDisplayText,
|
||||
langCode: widget.messageEvent.messageDisplayLangCode,
|
||||
onPressed: () =>
|
||||
_setActivityType(ActivityTypeEnum.wordMeaning),
|
||||
definition: _definition,
|
||||
setDefinition: _setDefinition,
|
||||
),
|
||||
_wordZoomCenterWidget,
|
||||
MorphologicalListWidget(
|
||||
token: widget.token,
|
||||
setMorphFeature: _setSelectedMorphFeature,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue