Merge pull request #5352 from pangeachat/5244-grammar-practice-ui-updates
5244 grammar practice UI updates
This commit is contained in:
commit
c454dc152c
8 changed files with 343 additions and 63 deletions
|
|
@ -16,12 +16,14 @@ class MorphMeaningWidget extends StatefulWidget {
|
|||
final MorphFeaturesEnum feature;
|
||||
final String tag;
|
||||
final TextStyle? style;
|
||||
final bool blankErrorFeedback;
|
||||
|
||||
const MorphMeaningWidget({
|
||||
super.key,
|
||||
required this.feature,
|
||||
required this.tag,
|
||||
this.style,
|
||||
this.blankErrorFeedback = false,
|
||||
});
|
||||
|
||||
@override
|
||||
|
|
@ -91,12 +93,13 @@ class MorphMeaningWidgetState extends State<MorphMeaningWidget> {
|
|||
);
|
||||
|
||||
if (result.isError) {
|
||||
return L10n.of(context).meaningNotFound;
|
||||
return widget.blankErrorFeedback ? '' : L10n.of(context).meaningNotFound;
|
||||
}
|
||||
|
||||
final morph = result.result!.getFeatureByCode(widget.feature.name);
|
||||
final data = morph?.getTagByCode(widget.tag);
|
||||
return data?.l1Description ?? L10n.of(context).meaningNotFound;
|
||||
return data?.l1Description ??
|
||||
(widget.blankErrorFeedback ? '' : L10n.of(context).meaningNotFound);
|
||||
}
|
||||
|
||||
void _toggleEditMode(bool value) => setState(() => _editMode = value);
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import 'package:fluffychat/pangea/analytics_practice/analytics_practice_view.dar
|
|||
import 'package:fluffychat/pangea/common/utils/async_state.dart';
|
||||
import 'package:fluffychat/pangea/constructs/construct_identifier.dart';
|
||||
import 'package:fluffychat/pangea/lemmas/lemma_info_repo.dart';
|
||||
import 'package:fluffychat/pangea/morphs/morph_features_enum.dart';
|
||||
import 'package:fluffychat/pangea/practice_activities/message_activity_request.dart';
|
||||
import 'package:fluffychat/pangea/practice_activities/practice_activity_model.dart';
|
||||
import 'package:fluffychat/pangea/practice_activities/practice_generation_repo.dart';
|
||||
|
|
@ -26,6 +27,16 @@ 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 SelectedMorphChoice {
|
||||
final MorphFeaturesEnum feature;
|
||||
final String tag;
|
||||
|
||||
const SelectedMorphChoice({
|
||||
required this.feature,
|
||||
required this.tag,
|
||||
});
|
||||
}
|
||||
|
||||
class VocabPracticeChoice {
|
||||
final String choiceId;
|
||||
final String choiceText;
|
||||
|
|
@ -85,6 +96,9 @@ class AnalyticsPracticeState extends State<AnalyticsPractice>
|
|||
final ValueNotifier<double> progressNotifier = ValueNotifier<double>(0.0);
|
||||
final ValueNotifier<bool> enableChoicesNotifier = ValueNotifier<bool>(true);
|
||||
|
||||
final ValueNotifier<SelectedMorphChoice?> selectedMorphChoice =
|
||||
ValueNotifier<SelectedMorphChoice?>(null);
|
||||
|
||||
final Map<String, Map<String, String>> _choiceTexts = {};
|
||||
final Map<String, Map<String, String?>> _choiceEmojis = {};
|
||||
|
||||
|
|
@ -108,6 +122,7 @@ class AnalyticsPracticeState extends State<AnalyticsPractice>
|
|||
activityTarget.dispose();
|
||||
progressNotifier.dispose();
|
||||
enableChoicesNotifier.dispose();
|
||||
selectedMorphChoice.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
|
@ -192,8 +207,8 @@ class AnalyticsPracticeState extends State<AnalyticsPractice>
|
|||
void _clearState() {
|
||||
activityState.value = const AsyncState.loading();
|
||||
activityTarget.value = null;
|
||||
selectedMorphChoice.value = null;
|
||||
enableChoicesNotifier.value = true;
|
||||
|
||||
progressNotifier.value = 0.0;
|
||||
_queue.clear();
|
||||
_choiceTexts.clear();
|
||||
|
|
@ -256,6 +271,25 @@ class AnalyticsPracticeState extends State<AnalyticsPractice>
|
|||
await _startSession();
|
||||
}
|
||||
|
||||
Future<void> reloadCurrentActivity() async {
|
||||
if (activityTarget.value == null) return;
|
||||
|
||||
try {
|
||||
activityState.value = const AsyncState.loading();
|
||||
selectedMorphChoice.value = null;
|
||||
|
||||
final req = activityTarget.value!;
|
||||
final res = await _fetchActivity(req);
|
||||
|
||||
if (!mounted) return;
|
||||
activityState.value = AsyncState.loaded(res);
|
||||
_playAudio();
|
||||
} catch (e) {
|
||||
if (!mounted) return;
|
||||
activityState.value = AsyncState.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _completeSession() async {
|
||||
_sessionLoader.value!.finishSession();
|
||||
setState(() {});
|
||||
|
|
@ -284,6 +318,7 @@ class AnalyticsPracticeState extends State<AnalyticsPractice>
|
|||
await _completeSession();
|
||||
} else {
|
||||
activityState.value = const AsyncState.loading();
|
||||
selectedMorphChoice.value = null;
|
||||
final nextActivityCompleter = _queue.removeFirst();
|
||||
|
||||
activityTarget.value = nextActivityCompleter.request;
|
||||
|
|
@ -410,6 +445,14 @@ class AnalyticsPracticeState extends State<AnalyticsPractice>
|
|||
) async {
|
||||
if (_currentActivity == null) return;
|
||||
final activity = _currentActivity!;
|
||||
|
||||
// Track the selection for display
|
||||
if (activity is MorphPracticeActivityModel) {
|
||||
selectedMorphChoice.value = SelectedMorphChoice(
|
||||
feature: activity.morphFeature,
|
||||
tag: choiceContent,
|
||||
);
|
||||
}
|
||||
final isCorrect = activity.multipleChoiceContent.isCorrect(choiceContent);
|
||||
if (isCorrect) {
|
||||
enableChoicesNotifier.value = false;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ 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/common/network/requests.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.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';
|
||||
|
|
@ -64,7 +65,7 @@ class AnalyticsPracticeSessionRepo {
|
|||
AnalyticsActivityTarget(
|
||||
target: PracticeTarget(
|
||||
tokens: [entry.key],
|
||||
activityType: types[targets.length],
|
||||
activityType: ActivityTypeEnum.grammarCategory,
|
||||
morphFeature: entry.value,
|
||||
),
|
||||
),
|
||||
|
|
@ -243,18 +244,39 @@ class AnalyticsPracticeSessionRepo {
|
|||
}
|
||||
|
||||
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),
|
||||
),
|
||||
);
|
||||
final choiceTokens = tokens
|
||||
.where(
|
||||
(token) =>
|
||||
token.lemma.saveVocab &&
|
||||
choices.any(
|
||||
(choice) => choice.contains(token.text.content),
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
|
||||
// Skip if no valid tokens found for this grammar error
|
||||
if (choiceTokens.isEmpty) continue;
|
||||
|
||||
String? translation;
|
||||
try {
|
||||
translation = await event.requestRespresentationByL1();
|
||||
} catch (e, s) {
|
||||
ErrorHandler.logError(
|
||||
e: e,
|
||||
s: s,
|
||||
data: {
|
||||
'context': 'AnalyticsPracticeSessionRepo._fetchErrors',
|
||||
'message': 'Failed to fetch translation for analytics practice',
|
||||
'event_id': event.eventId,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (translation == null) continue;
|
||||
targets.add(
|
||||
AnalyticsActivityTarget(
|
||||
target: PracticeTarget(
|
||||
tokens: choiceTokens.toList(),
|
||||
tokens: choiceTokens,
|
||||
activityType: ActivityTypeEnum.grammarError,
|
||||
morphFeature: null,
|
||||
),
|
||||
|
|
@ -262,6 +284,7 @@ class AnalyticsPracticeSessionRepo {
|
|||
choreo: choreo,
|
||||
stepIndex: i,
|
||||
eventID: event.eventId,
|
||||
translation: translation,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/analytics_details_popup/morph_meaning_widget.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/analytics_practice/analytics_practice_page.dart';
|
||||
import 'package:fluffychat/pangea/analytics_practice/analytics_practice_session_model.dart';
|
||||
|
|
@ -74,8 +76,7 @@ class AnalyticsPracticeView extends StatelessWidget {
|
|||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16.0,
|
||||
vertical: 24.0,
|
||||
horizontal: 8.0,
|
||||
),
|
||||
child: MaxWidthBody(
|
||||
withScrolling: false,
|
||||
|
|
@ -123,25 +124,36 @@ class _AnalyticsActivityView extends StatelessWidget {
|
|||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
spacing: 16.0,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: controller.activityTarget,
|
||||
builder: (context, target, __) => target != null
|
||||
? Column(
|
||||
ValueListenableBuilder(
|
||||
valueListenable: controller.activityTarget,
|
||||
builder: (context, target, __) => target != null
|
||||
? Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 16.0,
|
||||
right: 16.0,
|
||||
top: 16.0,
|
||||
),
|
||||
child: Column(
|
||||
spacing: 12.0,
|
||||
children: [
|
||||
Text(
|
||||
target.promptText(context),
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleLarge
|
||||
?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
style: FluffyThemes.isColumnMode(context)
|
||||
? Theme.of(context)
|
||||
.textTheme
|
||||
.titleLarge
|
||||
?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
)
|
||||
: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium
|
||||
?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
if (controller.widget.type ==
|
||||
ConstructTypeEnum.vocab)
|
||||
|
|
@ -153,22 +165,56 @@ class _AnalyticsActivityView extends StatelessWidget {
|
|||
style: const TextStyle(fontSize: 14.0),
|
||||
),
|
||||
],
|
||||
)
|
||||
: const SizedBox(),
|
||||
),
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Center(
|
||||
Flexible(
|
||||
fit: FlexFit.loose,
|
||||
child: SingleChildScrollView(
|
||||
child: _AnalyticsPracticeCenterContent(
|
||||
controller: controller,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 6,
|
||||
child: _ActivityChoicesWidget(controller),
|
||||
),
|
||||
//reserve space for grammar category morph meaning to avoid shifting, but only in those questions
|
||||
AnimatedBuilder(
|
||||
animation: Listenable.merge([
|
||||
controller.activityState,
|
||||
controller.selectedMorphChoice,
|
||||
]),
|
||||
builder: (context, _) {
|
||||
final activityState = controller.activityState.value;
|
||||
final selectedChoice = controller.selectedMorphChoice.value;
|
||||
|
||||
final isGrammarCategory = activityState
|
||||
is AsyncLoaded<MultipleChoicePracticeActivityModel> &&
|
||||
activityState.value.activityType ==
|
||||
ActivityTypeEnum.grammarCategory;
|
||||
|
||||
if (!isGrammarCategory) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
minHeight: 80,
|
||||
),
|
||||
child: selectedChoice == null
|
||||
? const SizedBox.shrink()
|
||||
: SingleChildScrollView(
|
||||
child: MorphMeaningWidget(
|
||||
feature: selectedChoice.feature,
|
||||
tag: selectedChoice.tag,
|
||||
blankErrorFeedback: true,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
@ -193,8 +239,23 @@ class _AnalyticsPracticeCenterContent extends StatelessWidget {
|
|||
ActivityTypeEnum.grammarError => ValueListenableBuilder(
|
||||
valueListenable: controller.activityState,
|
||||
builder: (context, state, __) => switch (state) {
|
||||
AsyncLoaded(value: final activity) => _ErrorBlankWidget(
|
||||
activity: activity as GrammarErrorPracticeActivityModel,
|
||||
AsyncLoaded(
|
||||
value: final GrammarErrorPracticeActivityModel activity
|
||||
) =>
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_ErrorBlankWidget(
|
||||
activity: activity,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
_GrammarErrorTranslationButton(
|
||||
key: ValueKey(
|
||||
'${activity.eventID}_${activity.errorOffset}_${activity.errorLength}',
|
||||
),
|
||||
translation: activity.translation,
|
||||
),
|
||||
],
|
||||
),
|
||||
_ => const SizedBox(),
|
||||
},
|
||||
|
|
@ -332,7 +393,7 @@ class _ActivityChoicesWidget extends StatelessWidget {
|
|||
ErrorIndicator(message: error.toString()),
|
||||
const SizedBox(height: 16),
|
||||
TextButton.icon(
|
||||
onPressed: controller.reloadSession,
|
||||
onPressed: controller.reloadCurrentActivity,
|
||||
icon: const Icon(Icons.refresh),
|
||||
label: Text(L10n.of(context).tryAgain),
|
||||
),
|
||||
|
|
@ -347,31 +408,34 @@ class _ActivityChoicesWidget extends StatelessWidget {
|
|||
final cardHeight = (constrainedHeight / (choices.length + 1))
|
||||
.clamp(50.0, 80.0);
|
||||
|
||||
return Container(
|
||||
constraints: const BoxConstraints(maxHeight: 400.0),
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: controller.enableChoicesNotifier,
|
||||
builder: (context, enabled, __) => Column(
|
||||
spacing: 4.0,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: choices
|
||||
.map(
|
||||
(choice) => _ChoiceCard(
|
||||
activity: value,
|
||||
targetId:
|
||||
controller.choiceTargetId(choice.choiceId),
|
||||
choiceId: choice.choiceId,
|
||||
onPressed: () => controller.onSelectChoice(
|
||||
choice.choiceId,
|
||||
),
|
||||
cardHeight: cardHeight,
|
||||
choiceText: choice.choiceText,
|
||||
choiceEmoji: choice.choiceEmoji,
|
||||
enabled: enabled,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: controller.enableChoicesNotifier,
|
||||
builder: (context, enabled, __) => Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
spacing: 4.0,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: choices
|
||||
.map(
|
||||
(choice) => _ChoiceCard(
|
||||
activity: value,
|
||||
targetId: controller
|
||||
.choiceTargetId(choice.choiceId),
|
||||
choiceId: choice.choiceId,
|
||||
onPressed: () => controller.onSelectChoice(
|
||||
choice.choiceId,
|
||||
),
|
||||
cardHeight: cardHeight,
|
||||
choiceText: choice.choiceText,
|
||||
choiceEmoji: choice.choiceEmoji,
|
||||
enabled: enabled,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
|
|
@ -456,6 +520,7 @@ class _ChoiceCard extends StatelessWidget {
|
|||
tag: choiceText,
|
||||
onPressed: onPressed,
|
||||
isCorrect: isCorrect,
|
||||
height: cardHeight,
|
||||
enabled: enabled,
|
||||
);
|
||||
|
||||
|
|
@ -490,3 +555,85 @@ class _ChoiceCard extends StatelessWidget {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _GrammarErrorTranslationButton extends StatefulWidget {
|
||||
final String translation;
|
||||
|
||||
const _GrammarErrorTranslationButton({
|
||||
super.key,
|
||||
required this.translation,
|
||||
});
|
||||
|
||||
@override
|
||||
State<_GrammarErrorTranslationButton> createState() =>
|
||||
_GrammarErrorTranslationButtonState();
|
||||
}
|
||||
|
||||
class _GrammarErrorTranslationButtonState
|
||||
extends State<_GrammarErrorTranslationButton> {
|
||||
bool _showTranslation = false;
|
||||
|
||||
void _toggleTranslation() {
|
||||
if (_showTranslation) {
|
||||
setState(() {
|
||||
_showTranslation = false;
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
_showTranslation = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Center(
|
||||
child: GestureDetector(
|
||||
onTap: _toggleTranslation,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
spacing: 8.0,
|
||||
children: [
|
||||
if (_showTranslation)
|
||||
Flexible(
|
||||
child: 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: Text(
|
||||
widget.translation,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onPrimaryFixed,
|
||||
fontSize:
|
||||
AppConfig.fontSizeFactor * AppConfig.messageFontSize,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (!_showTranslation)
|
||||
ElevatedButton(
|
||||
onPressed: _toggleTranslation,
|
||||
style: ElevatedButton.styleFrom(
|
||||
shape: const CircleBorder(),
|
||||
padding: const EdgeInsets.all(8),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.lightbulb_outline,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
|||
import 'package:fluffychat/pangea/analytics_practice/choice_cards/game_choice_card.dart';
|
||||
import 'package:fluffychat/pangea/morphs/get_grammar_copy.dart';
|
||||
import 'package:fluffychat/pangea/morphs/morph_features_enum.dart';
|
||||
import 'package:fluffychat/pangea/morphs/morph_icon.dart';
|
||||
|
||||
/// Choice card for meaning activity with emoji, and alt text on flip
|
||||
class GrammarChoiceCard extends StatelessWidget {
|
||||
|
|
@ -31,6 +32,10 @@ class GrammarChoiceCard extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final baseTextSize =
|
||||
(Theme.of(context).textTheme.titleMedium?.fontSize ?? 16) *
|
||||
(height / 72.0).clamp(1.0, 1.4);
|
||||
final emojiSize = baseTextSize * 1.2;
|
||||
final copy = getGrammarCopy(
|
||||
category: feature.name,
|
||||
lemma: tag,
|
||||
|
|
@ -45,7 +50,33 @@ class GrammarChoiceCard extends StatelessWidget {
|
|||
isCorrect: isCorrect,
|
||||
height: height,
|
||||
isEnabled: enabled,
|
||||
child: Text(copy),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: height * .7,
|
||||
height: height,
|
||||
child: Center(
|
||||
child: MorphIcon(
|
||||
morphFeature: feature,
|
||||
morphTag: tag,
|
||||
size: Size(emojiSize, emojiSize),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
copy,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 2,
|
||||
textAlign: TextAlign.left,
|
||||
style: TextStyle(
|
||||
fontSize: baseTextSize,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ class GrammarErrorPracticeGenerator {
|
|||
errorOffset: igcMatch.offset,
|
||||
errorLength: igcMatch.length,
|
||||
eventID: eventID,
|
||||
translation: req.grammarErrorInfo!.translation,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,11 +45,13 @@ class GrammarErrorRequestInfo {
|
|||
final ChoreoRecordModel choreo;
|
||||
final int stepIndex;
|
||||
final String eventID;
|
||||
final String translation;
|
||||
|
||||
const GrammarErrorRequestInfo({
|
||||
required this.choreo,
|
||||
required this.stepIndex,
|
||||
required this.eventID,
|
||||
required this.translation,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
|
|
@ -57,6 +59,7 @@ class GrammarErrorRequestInfo {
|
|||
'choreo': choreo.toJson(),
|
||||
'step_index': stepIndex,
|
||||
'event_id': eventID,
|
||||
'translation': translation,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -65,6 +68,7 @@ class GrammarErrorRequestInfo {
|
|||
choreo: ChoreoRecordModel.fromJson(json['choreo']),
|
||||
stepIndex: json['step_index'] as int,
|
||||
eventID: json['event_id'] as String,
|
||||
translation: json['translation'] as String,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -187,6 +187,21 @@ sealed class PracticeActivityModel {
|
|||
tokens: tokens,
|
||||
matchContent: matchContent!,
|
||||
);
|
||||
case ActivityTypeEnum.grammarError:
|
||||
assert(
|
||||
multipleChoiceContent != null,
|
||||
"multipleChoiceContent is null in PracticeActivityModel.fromJson for grammarError",
|
||||
);
|
||||
return GrammarErrorPracticeActivityModel(
|
||||
langCode: langCode,
|
||||
tokens: tokens,
|
||||
multipleChoiceContent: multipleChoiceContent!,
|
||||
text: json['text'] as String,
|
||||
errorOffset: json['error_offset'] as int,
|
||||
errorLength: json['error_length'] as int,
|
||||
eventID: json['event_id'] as String,
|
||||
translation: json['translation'] as String,
|
||||
);
|
||||
default:
|
||||
throw ("Unsupported activity type in PracticeActivityModel.fromJson: $type");
|
||||
}
|
||||
|
|
@ -358,6 +373,7 @@ class GrammarErrorPracticeActivityModel
|
|||
final int errorOffset;
|
||||
final int errorLength;
|
||||
final String eventID;
|
||||
final String translation;
|
||||
|
||||
GrammarErrorPracticeActivityModel({
|
||||
required super.tokens,
|
||||
|
|
@ -367,7 +383,19 @@ class GrammarErrorPracticeActivityModel
|
|||
required this.errorOffset,
|
||||
required this.errorLength,
|
||||
required this.eventID,
|
||||
required this.translation,
|
||||
});
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = super.toJson();
|
||||
json['text'] = text;
|
||||
json['error_offset'] = errorOffset;
|
||||
json['error_length'] = errorLength;
|
||||
json['event_id'] = eventID;
|
||||
json['translation'] = translation;
|
||||
return json;
|
||||
}
|
||||
}
|
||||
|
||||
class EmojiPracticeActivityModel extends MatchPracticeActivityModel {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue