grammar error practice UI elements

This commit is contained in:
ggurdin 2026-01-20 11:39:48 -05:00
parent 33b05f6f24
commit 8fb41cdc7a
No known key found for this signature in database
GPG key ID: A01CB41737CBB478
12 changed files with 374 additions and 77 deletions

View file

@ -5053,5 +5053,6 @@
"constructUseCorGCDesc": "Correct grammar category practice",
"constructUseIncGCDesc": "Incorrect grammar category practice",
"constructUseCorGEDesc": "Correct grammar error practice",
"constructUseIncGEDesc": "Incorrect grammar error practice"
"constructUseIncGEDesc": "Incorrect grammar error practice",
"fillInBlank": "Fill in the blank with the correct choice"
}

View file

@ -14,6 +14,7 @@ import 'package:fluffychat/pangea/analytics_data/level_up_analytics_service.dart
import 'package:fluffychat/pangea/analytics_misc/client_analytics_extension.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_use_model.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_use_type_enum.dart';
import 'package:fluffychat/pangea/analytics_misc/constructs_event.dart';
import 'package:fluffychat/pangea/analytics_misc/constructs_model.dart';
import 'package:fluffychat/pangea/analytics_settings/analytics_settings_extension.dart';
@ -233,12 +234,14 @@ class AnalyticsDataService {
int? count,
String? roomId,
DateTime? since,
ConstructUseTypeEnum? type,
}) async {
await _ensureInitialized();
final uses = await _analyticsClientGetter.database.getUses(
count: count,
roomId: roomId,
since: since,
type: type,
);
final blocked = blockedConstructs;

View file

@ -10,6 +10,7 @@ import 'package:synchronized/synchronized.dart';
import 'package:fluffychat/pangea/analytics_data/derived_analytics_data_model.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_use_model.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_use_type_enum.dart';
import 'package:fluffychat/pangea/analytics_misc/constructs_event.dart';
import 'package:fluffychat/pangea/analytics_misc/constructs_model.dart';
import 'package:fluffychat/pangea/constructs/construct_identifier.dart';
@ -197,6 +198,7 @@ class AnalyticsDatabase with DatabaseFileStorage {
int? count,
String? roomId,
DateTime? since,
ConstructUseTypeEnum? type,
}) async {
final stopwatch = Stopwatch()..start();
final results = <OneConstructUse>[];
@ -208,6 +210,9 @@ class AnalyticsDatabase with DatabaseFileStorage {
if (roomId != null && use.metadata.roomId != roomId) {
return true; // skip but continue
}
if (type != null && use.useType != type) {
return true; // skip but continue
}
results.add(use);
return count == null || results.length < count;

View file

@ -26,18 +26,28 @@ import 'package:fluffychat/pangea/toolbar/message_practice/practice_record_contr
import 'package:fluffychat/widgets/future_loading_dialog.dart';
import 'package:fluffychat/widgets/matrix.dart';
class PracticeChoice {
class VocabPracticeChoice {
final String choiceId;
final String choiceText;
final String? choiceEmoji;
const PracticeChoice({
const VocabPracticeChoice({
required this.choiceId,
required this.choiceText,
this.choiceEmoji,
});
}
class _PracticeQueueEntry {
final MessageActivityRequest request;
final Completer<MultipleChoicePracticeActivityModel> completer;
_PracticeQueueEntry({
required this.request,
required this.completer,
});
}
class SessionLoader extends AsyncLoader<AnalyticsPracticeSessionModel> {
final ConstructTypeEnum type;
SessionLoader({required this.type});
@ -67,12 +77,10 @@ class AnalyticsPracticeState extends State<AnalyticsPractice>
final ValueNotifier<AsyncState<MultipleChoicePracticeActivityModel>>
activityState = ValueNotifier(const AsyncState.idle());
final Queue<
MapEntry<PracticeTarget,
Completer<MultipleChoicePracticeActivityModel>>> _queue = Queue();
final Queue<_PracticeQueueEntry> _queue = Queue();
final ValueNotifier<PracticeTarget?> activityTarget =
ValueNotifier<PracticeTarget?>(null);
final ValueNotifier<MessageActivityRequest?> activityTarget =
ValueNotifier<MessageActivityRequest?>(null);
final ValueNotifier<double> progressNotifier = ValueNotifier<double>(0.0);
@ -116,13 +124,13 @@ class AnalyticsPracticeState extends State<AnalyticsPractice>
AnalyticsDataService get _analyticsService =>
Matrix.of(context).analyticsDataService;
List<PracticeChoice> filteredChoices(
List<VocabPracticeChoice> filteredChoices(
MultipleChoicePracticeActivityModel activity,
) {
final content = activity.multipleChoiceContent;
final choices = content.choices.toList();
final answer = content.answers.first;
final filtered = <PracticeChoice>[];
final filtered = <VocabPracticeChoice>[];
final seenTexts = <String>{};
for (final id in choices) {
@ -137,7 +145,7 @@ class AnalyticsPracticeState extends State<AnalyticsPractice>
(choice) => choice.choiceText == text,
);
if (index != -1) {
filtered[index] = PracticeChoice(
filtered[index] = VocabPracticeChoice(
choiceId: id,
choiceText: text,
choiceEmoji: getChoiceEmoji(activity.storageKey, id),
@ -148,7 +156,7 @@ class AnalyticsPracticeState extends State<AnalyticsPractice>
seenTexts.add(text);
filtered.add(
PracticeChoice(
VocabPracticeChoice(
choiceId: id,
choiceText: text,
choiceEmoji: getChoiceEmoji(activity.storageKey, id),
@ -202,7 +210,7 @@ class AnalyticsPracticeState extends State<AnalyticsPractice>
if (activityTarget.value == null) return;
if (widget.type != ConstructTypeEnum.vocab) return;
TtsController.tryToSpeak(
activityTarget.value!.tokens.first.vocabConstructID.lemma,
activityTarget.value!.target.tokens.first.vocabConstructID.lemma,
langCode: MatrixState.pangeaController.userController.userL2!.langCode,
);
}
@ -275,10 +283,10 @@ class AnalyticsPracticeState extends State<AnalyticsPractice>
activityState.value = const AsyncState.loading();
final nextActivityCompleter = _queue.removeFirst();
activityTarget.value = nextActivityCompleter.key;
activityTarget.value = nextActivityCompleter.request;
_playAudio();
final activity = await nextActivityCompleter.value.future;
final activity = await nextActivityCompleter.completer.future;
activityState.value = AsyncState.loaded(activity);
}
} catch (e) {
@ -298,7 +306,7 @@ class AnalyticsPracticeState extends State<AnalyticsPractice>
activityState.value = const AsyncState.loading();
final req = requests.first;
activityTarget.value = req.target;
activityTarget.value = req;
_playAudio();
final res = await _fetchActivity(req);
@ -314,10 +322,17 @@ class AnalyticsPracticeState extends State<AnalyticsPractice>
_fillActivityQueue(requests.skip(1).toList());
}
Future<void> _fillActivityQueue(List<MessageActivityRequest> requests) async {
Future<void> _fillActivityQueue(
List<MessageActivityRequest> requests,
) async {
for (final request in requests) {
final completer = Completer<MultipleChoicePracticeActivityModel>();
_queue.add(MapEntry(request.target, completer));
_queue.add(
_PracticeQueueEntry(
request: request,
completer: completer,
),
);
try {
final res = await _fetchActivity(request);

View file

@ -6,9 +6,32 @@ import 'package:fluffychat/pangea/analytics_practice/analytics_practice_constant
import 'package:fluffychat/pangea/practice_activities/message_activity_request.dart';
import 'package:fluffychat/pangea/practice_activities/practice_target.dart';
class AnalyticsActivityTarget {
final PracticeTarget target;
final GrammarErrorRequestInfo? grammarErrorInfo;
AnalyticsActivityTarget({
required this.target,
this.grammarErrorInfo,
});
Map<String, dynamic> toJson() => {
'target': target.toJson(),
'grammarErrorInfo': grammarErrorInfo?.toJson(),
};
factory AnalyticsActivityTarget.fromJson(Map<String, dynamic> json) =>
AnalyticsActivityTarget(
target: PracticeTarget.fromJson(json['target']),
grammarErrorInfo: json['grammarErrorInfo'] != null
? GrammarErrorRequestInfo.fromJson(json['grammarErrorInfo'])
: null,
);
}
class AnalyticsPracticeSessionModel {
final DateTime startedAt;
final List<PracticeTarget> practiceTargets;
final List<AnalyticsActivityTarget> practiceTargets;
final String userL1;
final String userL2;
@ -38,7 +61,8 @@ class AnalyticsPracticeSessionModel {
userL1: userL1,
userL2: userL2,
activityQualityFeedback: null,
target: target,
target: target.target,
grammarErrorInfo: target.grammarErrorInfo,
);
}).toList();
}
@ -59,8 +83,8 @@ class AnalyticsPracticeSessionModel {
return AnalyticsPracticeSessionModel(
startedAt: DateTime.parse(json['startedAt'] as String),
practiceTargets: (json['practiceTargets'] as List<dynamic>)
.map((e) => PracticeTarget.fromJson(e))
.whereType<PracticeTarget>()
.map((e) => AnalyticsActivityTarget.fromJson(e))
.whereType<AnalyticsActivityTarget>()
.toList(),
userL1: json['userL1'] as String,
userL2: json['userL2'] as String,

View file

@ -1,14 +1,17 @@
import 'dart:math';
import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_use_type_enum.dart';
import 'package:fluffychat/pangea/analytics_practice/analytics_practice_constants.dart';
import 'package:fluffychat/pangea/analytics_practice/analytics_practice_session_model.dart';
import 'package:fluffychat/pangea/constructs/construct_identifier.dart';
import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart';
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
import 'package:fluffychat/pangea/events/models/pangea_token_text_model.dart';
import 'package:fluffychat/pangea/lemmas/lemma.dart';
import 'package:fluffychat/pangea/morphs/morph_features_enum.dart';
import 'package:fluffychat/pangea/practice_activities/activity_type_enum.dart';
import 'package:fluffychat/pangea/practice_activities/message_activity_request.dart';
import 'package:fluffychat/pangea/practice_activities/practice_target.dart';
import 'package:fluffychat/widgets/matrix.dart';
@ -24,28 +27,23 @@ class AnalyticsPracticeSessionRepo {
(_) => activityTypes[r.nextInt(activityTypes.length)],
);
final List<PracticeTarget> targets = [];
final List<AnalyticsActivityTarget> targets = [];
if (type == ConstructTypeEnum.vocab) {
final constructs = await _fetchVocab();
final targetCount = min(constructs.length, types.length);
targets.addAll([
for (var i = 0; i < targetCount; i++)
PracticeTarget(
tokens: [constructs[i].asToken],
activityType: types[i],
AnalyticsActivityTarget(
target: PracticeTarget(
tokens: [constructs[i].asToken],
activityType: types[i],
),
),
]);
} else {
final morphs = await _fetchMorphs();
targets.addAll([
for (final entry in morphs.entries)
PracticeTarget(
tokens: [entry.key],
activityType: types[targets.length],
morphFeature: entry.value,
),
]);
final errorTargets = await _fetchErrors();
targets.addAll(errorTargets);
}
final session = AnalyticsPracticeSessionModel(
@ -144,4 +142,99 @@ class AnalyticsPracticeSessionRepo {
return targets;
}
static Future<List<AnalyticsActivityTarget>> _fetchErrors() async {
final uses = await MatrixState
.pangeaController.matrixState.analyticsDataService
.getUses(count: 100, type: ConstructUseTypeEnum.ga);
final client = MatrixState.pangeaController.matrixState.client;
final Map<String, PangeaMessageEvent?> idsToEvents = {};
for (final use in uses) {
final eventID = use.metadata.eventId;
if (eventID == null || idsToEvents.containsKey(eventID)) continue;
final roomID = use.metadata.roomId;
if (roomID == null) {
idsToEvents[eventID] = null;
continue;
}
final room = client.getRoomById(roomID);
final event = await room?.getEventById(eventID);
if (event == null || event.redacted) {
idsToEvents[eventID] = null;
continue;
}
final timeline = await room!.getTimeline();
idsToEvents[eventID] = PangeaMessageEvent(
event: event,
timeline: timeline,
ownMessage: event.senderId == client.userID,
);
}
final l2Code =
MatrixState.pangeaController.userController.userL2!.langCodeShort;
final events = idsToEvents.values.whereType<PangeaMessageEvent>().toList();
final eventsWithContent = events.where((e) {
final originalSent = e.originalSent;
final choreo = originalSent?.choreo;
final tokens = originalSent?.tokens;
return originalSent?.langCode.split("-").first == l2Code &&
choreo != null &&
tokens != null &&
tokens.isNotEmpty &&
choreo.choreoSteps.any(
(step) =>
step.acceptedOrIgnoredMatch?.isGrammarMatch == true &&
step.acceptedOrIgnoredMatch?.match.bestChoice != null,
);
});
final targets = <AnalyticsActivityTarget>[];
for (final event in eventsWithContent) {
final originalSent = event.originalSent!;
final choreo = originalSent.choreo!;
final tokens = originalSent.tokens!;
for (int i = 0; i < choreo.choreoSteps.length; i++) {
final step = choreo.choreoSteps[i];
final igcMatch = step.acceptedOrIgnoredMatch;
if (igcMatch?.isGrammarMatch != true ||
igcMatch?.match.bestChoice == null) {
continue;
}
final choices = igcMatch!.match.choices!.map((c) => c.value).toList();
final choiceTokens = tokens.where(
(token) =>
token.lemma.saveVocab &&
choices.any(
(choice) => choice.contains(token.text.content),
),
);
targets.add(
AnalyticsActivityTarget(
target: PracticeTarget(
tokens: choiceTokens.toList(),
activityType: ActivityTypeEnum.grammarError,
morphFeature: null,
),
grammarErrorInfo: GrammarErrorRequestInfo(
choreo: choreo,
stepIndex: i,
eventID: event.eventId,
),
),
);
}
}
return targets;
}
}

View file

@ -143,8 +143,8 @@ class _AnalyticsActivityView extends StatelessWidget {
if (controller.widget.type ==
ConstructTypeEnum.vocab)
PhoneticTranscriptionWidget(
text:
target.tokens.first.vocabConstructID.lemma,
text: target
.target.tokens.first.vocabConstructID.lemma,
textLanguage: MatrixState
.pangeaController.userController.userL2!,
style: const TextStyle(fontSize: 14.0),
@ -157,13 +157,8 @@ class _AnalyticsActivityView extends StatelessWidget {
Expanded(
flex: 2,
child: Center(
child: ValueListenableBuilder(
valueListenable: controller.activityTarget,
builder: (context, target, __) => target != null
? _ExampleMessageWidget(
controller.getExampleMessage(target),
)
: const SizedBox(),
child: _AnalyticsPracticeCenterContent(
controller: controller,
),
),
),
@ -179,6 +174,36 @@ class _AnalyticsActivityView extends StatelessWidget {
}
}
class _AnalyticsPracticeCenterContent extends StatelessWidget {
final AnalyticsPracticeState controller;
const _AnalyticsPracticeCenterContent({
required this.controller,
});
@override
Widget build(BuildContext context) {
return ValueListenableBuilder(
valueListenable: controller.activityTarget,
builder: (context, target, __) => switch (target?.target.activityType) {
null => const SizedBox(),
ActivityTypeEnum.grammarError => ValueListenableBuilder(
valueListenable: controller.activityState,
builder: (context, state, __) => switch (state) {
AsyncLoaded(value: final activity) => _ErrorBlankWidget(
activity: activity as GrammarErrorPracticeActivityModel,
),
_ => const SizedBox(),
},
),
_ => _ExampleMessageWidget(
controller.getExampleMessage(target!.target),
),
},
);
}
}
class _ExampleMessageWidget extends StatelessWidget {
final Future<List<InlineSpan>?> future;
@ -220,6 +245,62 @@ class _ExampleMessageWidget extends StatelessWidget {
}
}
class _ErrorBlankWidget extends StatelessWidget {
final GrammarErrorPracticeActivityModel activity;
const _ErrorBlankWidget({
required this.activity,
});
@override
Widget build(BuildContext context) {
final text = activity.text;
final errorOffset = activity.errorOffset;
final errorLength = activity.errorLength;
return Container(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 8,
),
decoration: BoxDecoration(
color: Color.alphaBlend(
Colors.white.withAlpha(180),
ThemeData.dark().colorScheme.primary,
),
borderRadius: BorderRadius.circular(16),
),
child: RichText(
text: TextSpan(
style: TextStyle(
color: Theme.of(context).colorScheme.onPrimaryFixed,
fontSize: AppConfig.fontSizeFactor * AppConfig.messageFontSize,
),
children: [
if (errorOffset > 0)
TextSpan(text: text.characters.take(errorOffset).toString()),
WidgetSpan(
child: Container(
height: 4.0,
width: (errorLength * 8).toDouble(),
padding: const EdgeInsets.only(bottom: 2.0),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primary,
),
),
),
if (errorOffset + errorLength < text.length)
TextSpan(
text:
text.characters.skip(errorOffset + errorLength).toString(),
),
],
),
),
);
}
}
class _ActivityChoicesWidget extends StatelessWidget {
final AnalyticsPracticeState controller;
@ -366,6 +447,20 @@ class _ChoiceCard extends StatelessWidget {
isCorrect: isCorrect,
);
case ActivityTypeEnum.grammarError:
final activity = this.activity as GrammarErrorPracticeActivityModel;
return GameChoiceCard(
key: ValueKey(
'${activity.errorLength}_${activity.errorOffset}_${activity.eventID}_${activityType.name}_grammar_error_$choiceId',
),
shouldFlip: false,
targetId: targetId,
onPressed: onPressed,
isCorrect: isCorrect,
height: cardHeight,
child: Text(choiceText),
);
default:
return GameChoiceCard(
key: ValueKey(

View file

@ -1,41 +1,50 @@
import 'package:flutter/material.dart';
import 'package:fluffychat/pangea/practice_activities/message_activity_request.dart';
import 'package:fluffychat/pangea/practice_activities/multiple_choice_activity_model.dart';
import 'package:fluffychat/pangea/practice_activities/practice_activity_model.dart';
import 'package:fluffychat/widgets/matrix.dart';
class GrammarErrorPracticeGenerator {
static Future<MessageActivityResponse> get(
MessageActivityRequest req,
) async {
final igcMatch = target.igcMatch;
assert(igcMatch.bestChoice != null, 'IGC match must have a best choice');
assert(igcMatch.choices != null, 'IGC match must have choices');
assert(
req.grammarErrorInfo != null,
'Grammar error info must be provided for grammar error practice',
);
final errorSpan = igcMatch.errorSpan;
final correctChoice = igcMatch.bestChoice!.value;
final choreo = req.grammarErrorInfo!.choreo;
final stepIndex = req.grammarErrorInfo!.stepIndex;
final eventID = req.grammarErrorInfo!.eventID;
final igcMatch =
choreo.choreoSteps[stepIndex].acceptedOrIgnoredMatch?.match;
assert(igcMatch?.choices != null, 'IGC match must have choices');
assert(igcMatch?.bestChoice != null, 'IGC match must have a best choice');
final correctChoice = igcMatch!.bestChoice!.value;
final choices = igcMatch.choices!.map((c) => c.value).toList();
final choiceTokens = target.tokens.where(
(token) => choices.any(
(choice) => choice.contains(token.text.content),
),
);
assert(
choiceTokens.isNotEmpty,
'At least one token should match the error choices',
);
final stepText = choreo.stepText(stepIndex: stepIndex - 1);
final errorSpan = stepText.characters
.skip(igcMatch.offset)
.take(igcMatch.length)
.toString();
choices.add(errorSpan);
choices.shuffle();
return MessageActivityResponse(
activity: GrammarErrorPracticeActivityModel(
tokens: choiceTokens.toList(),
tokens: req.target.tokens,
langCode: req.userL2,
multipleChoiceContent: MultipleChoiceActivity(
choices: choices.toSet(),
answers: {correctChoice},
),
text: stepText,
errorOffset: igcMatch.offset,
errorLength: igcMatch.length,
eventID: eventID,
),
);
}

View file

@ -1,5 +1,11 @@
import 'package:flutter/material.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/pangea/choreographer/choreo_record_model.dart';
import 'package:fluffychat/pangea/morphs/morph_features_enum.dart';
import 'package:fluffychat/pangea/practice_activities/activity_type_enum.dart';
import 'package:fluffychat/pangea/practice_activities/practice_activity_model.dart';
import 'package:fluffychat/pangea/practice_activities/practice_target.dart';
@ -35,23 +41,67 @@ class ActivityQualityFeedback {
}
}
class GrammarErrorRequestInfo {
final ChoreoRecordModel choreo;
final int stepIndex;
final String eventID;
const GrammarErrorRequestInfo({
required this.choreo,
required this.stepIndex,
required this.eventID,
});
Map<String, dynamic> toJson() {
return {
'choreo': choreo.toJson(),
'step_index': stepIndex,
'event_id': eventID,
};
}
factory GrammarErrorRequestInfo.fromJson(Map<String, dynamic> json) {
return GrammarErrorRequestInfo(
choreo: ChoreoRecordModel.fromJson(json['choreo']),
stepIndex: json['step_index'] as int,
eventID: json['event_id'] as String,
);
}
}
class MessageActivityRequest {
final String userL1;
final String userL2;
final PracticeTarget target;
final ActivityQualityFeedback? activityQualityFeedback;
final GrammarErrorRequestInfo? grammarErrorInfo;
MessageActivityRequest({
required this.userL1,
required this.userL2,
required this.activityQualityFeedback,
required this.target,
this.grammarErrorInfo,
}) {
if (target.tokens.isEmpty) {
throw Exception('Target tokens must not be empty');
}
}
String promptText(BuildContext context) {
switch (target.activityType) {
case ActivityTypeEnum.grammarCategory:
return L10n.of(context).whatIsTheMorphTag(
target.morphFeature!.getDisplayCopy(context),
target.tokens.first.text.content,
);
case ActivityTypeEnum.grammarError:
return L10n.of(context).fillInBlank;
default:
return target.tokens.first.vocabConstructID.lemma;
}
}
Map<String, dynamic> toJson() {
return {
'user_l1': userL1,
@ -60,6 +110,7 @@ class MessageActivityRequest {
'target_tokens': target.tokens.map((e) => e.toJson()).toList(),
'target_type': target.activityType.name,
'target_morph_feature': target.morphFeature,
'grammar_error_info': grammarErrorInfo?.toJson(),
};
}
@ -72,7 +123,8 @@ class MessageActivityRequest {
other.userL2 == userL2 &&
other.target == target &&
other.activityQualityFeedback?.feedbackText ==
activityQualityFeedback?.feedbackText;
activityQualityFeedback?.feedbackText &&
other.grammarErrorInfo == grammarErrorInfo;
}
@override
@ -80,7 +132,8 @@ class MessageActivityRequest {
return activityQualityFeedback.hashCode ^
target.hashCode ^
userL1.hashCode ^
userL2.hashCode;
userL2.hashCode ^
grammarErrorInfo.hashCode;
}
}

View file

@ -354,10 +354,19 @@ class LemmaPracticeActivityModel extends MultipleChoicePracticeActivityModel {
class GrammarErrorPracticeActivityModel
extends MultipleChoicePracticeActivityModel {
final String text;
final int errorOffset;
final int errorLength;
final String eventID;
GrammarErrorPracticeActivityModel({
required super.tokens,
required super.langCode,
required super.multipleChoiceContent,
required this.text,
required this.errorOffset,
required this.errorLength,
required this.eventID,
});
}

View file

@ -130,6 +130,10 @@ class PracticeRepo {
case ActivityTypeEnum.grammarCategory:
return MorphCategoryActivityGenerator.get(req);
case ActivityTypeEnum.grammarError:
assert(
req.grammarErrorInfo != null,
'Grammar error info must be provided for grammar error activities',
);
return GrammarErrorPracticeGenerator.get(req);
case ActivityTypeEnum.morphId:
return MorphActivityGenerator.get(req);

View file

@ -1,9 +1,7 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:collection/collection.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/pangea/constructs/construct_identifier.dart';
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
import 'package:fluffychat/pangea/morphs/morph_features_enum.dart';
@ -85,18 +83,6 @@ class PracticeTarget {
(morphFeature?.name ?? "");
}
String promptText(BuildContext context) {
switch (activityType) {
case ActivityTypeEnum.grammarCategory:
return L10n.of(context).whatIsTheMorphTag(
morphFeature!.getDisplayCopy(context),
tokens.first.text.content,
);
default:
return tokens.first.vocabConstructID.lemma;
}
}
ConstructIdentifier targetTokenConstructID(PangeaToken token) {
final defaultID = token.vocabConstructID;
final ConstructIdentifier? cId = morphFeature == null