fluffychat/lib/pangea/practice_activities/practice_activity_model.dart
ggurdin e8428783e6
Fluffychat merge 2 (#5590)
* build: Reenable shrink resources and minify in gradle

* build: (deps): bump image from 4.6.0 to 4.7.1

Bumps [image](https://github.com/brendan-duncan/image) from 4.6.0 to 4.7.1.
- [Changelog](https://github.com/brendan-duncan/image/blob/main/CHANGELOG.md)
- [Commits](https://github.com/brendan-duncan/image/commits)

---
updated-dependencies:
- dependency-name: image
  dependency-version: 4.7.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* build: (deps): bump file_picker from 10.3.7 to 10.3.8

Bumps [file_picker](https://github.com/miguelpruivo/flutter_file_picker) from 10.3.7 to 10.3.8.
- [Release notes](https://github.com/miguelpruivo/flutter_file_picker/releases)
- [Changelog](https://github.com/miguelpruivo/flutter_file_picker/blob/master/CHANGELOG.md)
- [Commits](https://github.com/miguelpruivo/flutter_file_picker/compare/v10.3.7...v10.3.8)

---
updated-dependencies:
- dependency-name: file_picker
  dependency-version: 10.3.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* feat: Improved search

* build: Use matrix sdk vom pub.dev again

* chore: Follow up better search

* build: (deps): bump image from 4.7.1 to 4.7.2

Bumps [image](https://github.com/brendan-duncan/image) from 4.7.1 to 4.7.2.
- [Changelog](https://github.com/brendan-duncan/image/blob/main/CHANGELOG.md)
- [Commits](https://github.com/brendan-duncan/image/commits)

---
updated-dependencies:
- dependency-name: image
  dependency-version: 4.7.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore: Make cross signing self sign mandatory for bootstrap

* chore: Update user device keys before creating bootstrap

* fix: Better wait for secrets after verification bootstrap

* refactor: Remove native imaging and enable web worker

* refactor: Remove unused html onfocus streams

* build: (deps): bump flutter_foreground_task from 9.1.0 to 9.2.0

Bumps [flutter_foreground_task](https://github.com/Dev-hwang/flutter_foreground_task) from 9.1.0 to 9.2.0.
- [Changelog](https://github.com/Dev-hwang/flutter_foreground_task/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Dev-hwang/flutter_foreground_task/commits)

---
updated-dependencies:
- dependency-name: flutter_foreground_task
  dependency-version: 9.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore(translations): Translated using Weblate (Uzbek)

Currently translated at 99.7% (823 of 825 strings)

Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/uz/

* chore(translations): Translated using Weblate (Russian)

Currently translated at 99.8% (824 of 825 strings)

Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ru/

* chore(translations): Translated using Weblate (Norwegian Bokmål)

Currently translated at 90.9% (750 of 825 strings)

Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/nb_NO/

* chore(translations): Translated using Weblate (Galician)

Currently translated at 100.0% (825 of 825 strings)

Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/gl/

* chore(translations): Translated using Weblate (Basque)

Currently translated at 99.7% (823 of 825 strings)

Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/eu/

* chore(translations): Translated using Weblate (Ukrainian)

Currently translated at 100.0% (825 of 825 strings)

Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/uk/

* chore(translations): Translated using Weblate (Estonian)

Currently translated at 100.0% (825 of 825 strings)

Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/et/

* chore(translations): Translated using Weblate (Dutch)

Currently translated at 100.0% (825 of 825 strings)

Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/nl/

* chore(translations): Translated using Weblate (Russian)

Currently translated at 100.0% (825 of 825 strings)

Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ru/

* chore(translations): Translated using Weblate (Spanish)

Currently translated at 95.2% (788 of 827 strings)

Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/es/

* chore(translations): Translated using Weblate (Spanish)

Currently translated at 96.3% (797 of 827 strings)

Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/es/

* chore(translations): Translated using Weblate (Russian)

Currently translated at 100.0% (825 of 825 strings)

Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ru/

* chore(translations): Translated using Weblate (Russian)

Currently translated at 100.0% (825 of 825 strings)

Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/ru/

* fix: Broken ruzzian plurals

* chore(translations): Translated using Weblate (Norwegian Bokmål)

Currently translated at 91.2% (753 of 825 strings)

Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/nb_NO/

* chore(translations): Translated using Weblate (Bengali)

Currently translated at 4.5% (38 of 827 strings)

Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/bn/

* chore(translations): Translated using Weblate (French)

Currently translated at 82.3% (679 of 825 strings)

Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/fr/

* build: (deps): bump translations_cleaner from 0.0.5 to 0.1.0

Bumps [translations_cleaner](https://github.com/Chinmay-KB/translations_cleaner) from 0.0.5 to 0.1.0.
- [Changelog](https://github.com/Chinmay-KB/translations_cleaner/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Chinmay-KB/translations_cleaner/commits)

---
updated-dependencies:
- dependency-name: translations_cleaner
  dependency-version: 0.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore(translations): Translated using Weblate (German)

Currently translated at 99.2% (821 of 827 strings)

Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/de/

* chore(translations): Translated using Weblate (Estonian)

Currently translated at 100.0% (827 of 827 strings)

Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/et/

* build: Bump version to 2.4.0

* build: (deps): bump sqflite_common_ffi from 2.3.6 to 2.3.7+1

Bumps [sqflite_common_ffi](https://github.com/tekartik/sqflite) from 2.3.6 to 2.3.7+1.
- [Commits](https://github.com/tekartik/sqflite/compare/sqflite_common_ffi_v2.3.6...sqflite_common_ffi/v2.3.7)

---
updated-dependencies:
- dependency-name: sqflite_common_ffi
  dependency-version: 2.3.7+1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore(translations): Translated using Weblate (Czech)

Currently translated at 66.1% (547 of 827 strings)

Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/cs/

* chore(translations): Translated using Weblate (Czech)

Currently translated at 72.7% (602 of 827 strings)

Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/cs/

* chore(translations): Translated using Weblate (German)

Currently translated at 99.8% (826 of 827 strings)

Translation: FluffyChat/Translations
Translate-URL: https://hosted.weblate.org/projects/fluffychat/translations/de/

* chore: Add security.md file

* fix: Locale unlocalized strings

* build: (deps): bump matrix from 4.1.0 to 5.0.0

Bumps [matrix](https://github.com/famedly/matrix-dart-sdk) from 4.1.0 to 5.0.0.
- [Release notes](https://github.com/famedly/matrix-dart-sdk/releases)
- [Changelog](https://github.com/famedly/matrix-dart-sdk/blob/main/CHANGELOG.md)
- [Commits](https://github.com/famedly/matrix-dart-sdk/compare/v4.1.0...v5.0.0)

---
updated-dependencies:
- dependency-name: matrix
  dependency-version: 5.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* fix: Notifications on web correctly managed when tab not focused

* chore: Add changelog for android

* chore: Remove duplicated localization

* fix: Sign in label

* chore: Versionize fcm shared isolate

* build: Remove unused packag

* build: (deps): bump package_info_plus from 8.3.1 to 9.0.0

Bumps [package_info_plus](https://github.com/fluttercommunity/plus_plugins/tree/main/packages/package_info_plus) from 8.3.1 to 9.0.0.
- [Release notes](https://github.com/fluttercommunity/plus_plugins/releases)
- [Commits](https://github.com/fluttercommunity/plus_plugins/commits/package_info_plus-v9.0.0/packages/package_info_plus)

---
updated-dependencies:
- dependency-name: package_info_plus
  dependency-version: 9.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* feat: Display particle animation on login page

* chore: Use fixed version of fcm shared isolate

* fix: apk crash on some platforms due new flutter version

* chore: Correct kotlin format

* fix iOS notifications

* fluffychat merge

* fluffychat merge

* fluffychat merge

* fluffychat merge

* fluffychat merge

* fluffychat merge

* add missing type annotations

* update matrix version

* fluffychat merge

* fluffychat merge

* fix notification on click actions

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Christian Kußowski <c.kussowski@famedly.com>
Co-authored-by: Krille-chan <christian-kussowski@posteo.de>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: BeMeritus <bemerituss@gmail.com>
Co-authored-by: Frank Paul Silye <frankps@gmail.com>
Co-authored-by: josé m. <correoxm@disroot.org>
Co-authored-by: xabirequejo <xabi.rn@gmail.com>
Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com>
Co-authored-by: Priit Jõerüüt <jrthwlate@users.noreply.hosted.weblate.org>
Co-authored-by: Jelv <post@jelv.nl>
Co-authored-by: Дмитрий Михирев <bizdelnick@gmail.com>
Co-authored-by: Kimby <kimbyqs@gmail.com>
Co-authored-by: Christian <christian-pauly@posteo.de>
Co-authored-by: Kom nake <kominak310@svcache.com>
Co-authored-by: hugues de keyzer <komputilisto@hugues.info>
Co-authored-by: nautilusx <translate@disroot.org>
Co-authored-by: Šebestová <ka.sebestova.cz@gmail.com>
2026-02-10 08:01:12 -05:00

441 lines
14 KiB
Dart

import 'package:sentry_flutter/sentry_flutter.dart';
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_misc/constructs_model.dart';
import 'package:fluffychat/pangea/analytics_practice/analytics_practice_session_model.dart';
import 'package:fluffychat/pangea/events/models/pangea_token_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/multiple_choice_activity_model.dart';
import 'package:fluffychat/pangea/practice_activities/practice_match.dart';
import 'package:fluffychat/pangea/practice_activities/practice_target.dart';
sealed class PracticeActivityModel {
final List<PangeaToken> tokens;
final String langCode;
const PracticeActivityModel({required this.tokens, required this.langCode});
String get storageKey =>
'${activityType.name}-${tokens.map((e) => e.text.content).join("-")}';
PracticeTarget get practiceTarget => PracticeTarget(
activityType: activityType,
tokens: tokens,
morphFeature: this is MorphPracticeActivityModel
? (this as MorphPracticeActivityModel).morphFeature
: null,
);
ActivityTypeEnum get activityType {
switch (this) {
case MorphCategoryPracticeActivityModel():
return ActivityTypeEnum.grammarCategory;
case VocabAudioPracticeActivityModel():
return ActivityTypeEnum.lemmaAudio;
case VocabMeaningPracticeActivityModel():
return ActivityTypeEnum.lemmaMeaning;
case EmojiPracticeActivityModel():
return ActivityTypeEnum.emoji;
case LemmaPracticeActivityModel():
return ActivityTypeEnum.lemmaId;
case LemmaMeaningPracticeActivityModel():
return ActivityTypeEnum.wordMeaning;
case MorphMatchPracticeActivityModel():
return ActivityTypeEnum.morphId;
case WordListeningPracticeActivityModel():
return ActivityTypeEnum.wordFocusListening;
case GrammarErrorPracticeActivityModel():
return ActivityTypeEnum.grammarError;
}
}
factory PracticeActivityModel.fromJson(Map<String, dynamic> json) {
if (json['lang_code'] is! String) {
Sentry.addBreadcrumb(Breadcrumb(data: {"json": json}));
throw ("lang_code is not a string in PracticeActivityModel.fromJson");
}
final targetConstructsEntry =
json['tgt_constructs'] ?? json['target_constructs'];
if (targetConstructsEntry is! List) {
Sentry.addBreadcrumb(Breadcrumb(data: {"json": json}));
throw ("tgt_constructs is not a list in PracticeActivityModel.fromJson");
}
final type = ActivityTypeEnum.fromString(json['activity_type']);
final morph = json['morph_feature'] != null
? MorphFeaturesEnumExtension.fromString(json['morph_feature'] as String)
: null;
final tokens = (json['target_tokens'] as List)
.map((e) => PangeaToken.fromJson(e as Map<String, dynamic>))
.toList();
final langCode = json['lang_code'] as String;
final multipleChoiceContent = json['content'] != null
? MultipleChoiceActivity.fromJson(
json['content'] as Map<String, dynamic>,
)
: null;
final matchContent = json['match_content'] != null
? PracticeMatchActivity.fromJson(
json['match_content'] as Map<String, dynamic>,
)
: null;
switch (type) {
case ActivityTypeEnum.grammarCategory:
assert(
morph != null,
"morphFeature is null in PracticeActivityModel.fromJson for grammarCategory",
);
assert(
multipleChoiceContent != null,
"multipleChoiceContent is null in PracticeActivityModel.fromJson for grammarCategory",
);
return MorphCategoryPracticeActivityModel(
langCode: langCode,
tokens: tokens,
morphFeature: morph!,
multipleChoiceContent: multipleChoiceContent!,
exampleMessageInfo: json['example_message_info'] != null
? ExampleMessageInfo.fromJson(json['example_message_info'])
: const ExampleMessageInfo(exampleMessage: []),
);
case ActivityTypeEnum.lemmaAudio:
assert(
multipleChoiceContent != null,
"multipleChoiceContent is null in PracticeActivityModel.fromJson for lemmaAudio",
);
return VocabAudioPracticeActivityModel(
langCode: langCode,
tokens: tokens,
multipleChoiceContent: multipleChoiceContent!,
roomId: json['room_id'] as String?,
eventId: json['event_id'] as String?,
exampleMessage: json['example_message'] != null
? ExampleMessageInfo.fromJson(json['example_message'])
: const ExampleMessageInfo(exampleMessage: []),
);
case ActivityTypeEnum.lemmaMeaning:
assert(
multipleChoiceContent != null,
"multipleChoiceContent is null in PracticeActivityModel.fromJson for lemmaMeaning",
);
return VocabMeaningPracticeActivityModel(
langCode: langCode,
tokens: tokens,
multipleChoiceContent: multipleChoiceContent!,
);
case ActivityTypeEnum.emoji:
assert(
matchContent != null,
"matchContent is null in PracticeActivityModel.fromJson for emoji",
);
return EmojiPracticeActivityModel(
langCode: langCode,
tokens: tokens,
matchContent: matchContent!,
);
case ActivityTypeEnum.lemmaId:
assert(
multipleChoiceContent != null,
"multipleChoiceContent is null in PracticeActivityModel.fromJson for lemmaId",
);
return LemmaPracticeActivityModel(
langCode: langCode,
tokens: tokens,
multipleChoiceContent: multipleChoiceContent!,
);
case ActivityTypeEnum.wordMeaning:
assert(
matchContent != null,
"matchContent is null in PracticeActivityModel.fromJson for wordMeaning",
);
return LemmaMeaningPracticeActivityModel(
langCode: langCode,
tokens: tokens,
matchContent: matchContent!,
);
case ActivityTypeEnum.morphId:
assert(
morph != null,
"morphFeature is null in PracticeActivityModel.fromJson for morphId",
);
assert(
multipleChoiceContent != null,
"multipleChoiceContent is null in PracticeActivityModel.fromJson for morphId",
);
return MorphMatchPracticeActivityModel(
langCode: langCode,
tokens: tokens,
morphFeature: morph!,
multipleChoiceContent: multipleChoiceContent!,
);
case ActivityTypeEnum.wordFocusListening:
assert(
matchContent != null,
"matchContent is null in PracticeActivityModel.fromJson for wordFocusListening",
);
return WordListeningPracticeActivityModel(
langCode: langCode,
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");
}
}
Map<String, dynamic> toJson() {
return {
'lang_code': langCode,
'activity_type': activityType.name,
'target_tokens': tokens.map((e) => e.toJson()).toList(),
};
}
}
sealed class MultipleChoicePracticeActivityModel extends PracticeActivityModel {
final MultipleChoiceActivity multipleChoiceContent;
MultipleChoicePracticeActivityModel({
required super.tokens,
required super.langCode,
required this.multipleChoiceContent,
});
bool isCorrect(String choice) => multipleChoiceContent.isCorrect(choice);
OneConstructUse constructUse(String choiceContent) {
final correct = multipleChoiceContent.isCorrect(choiceContent);
final useType = correct
? activityType.correctUse
: activityType.incorrectUse;
final token = tokens.first;
return OneConstructUse(
useType: useType,
constructType: ConstructTypeEnum.vocab,
metadata: ConstructUseMetaData(roomId: null, timeStamp: DateTime.now()),
category: token.pos,
lemma: token.lemma.text,
form: token.lemma.text,
xp: useType.pointValue,
);
}
@override
Map<String, dynamic> toJson() {
final json = super.toJson();
json['content'] = multipleChoiceContent.toJson();
return json;
}
}
sealed class MatchPracticeActivityModel extends PracticeActivityModel {
final PracticeMatchActivity matchContent;
MatchPracticeActivityModel({
required super.tokens,
required super.langCode,
required this.matchContent,
});
bool isCorrect(PangeaToken token, String choice) =>
matchContent.matchInfo[token.vocabForm]!.contains(choice);
@override
Map<String, dynamic> toJson() {
final json = super.toJson();
json['match_content'] = matchContent.toJson();
return json;
}
}
sealed class MorphPracticeActivityModel
extends MultipleChoicePracticeActivityModel {
final MorphFeaturesEnum morphFeature;
MorphPracticeActivityModel({
required super.tokens,
required super.langCode,
required super.multipleChoiceContent,
required this.morphFeature,
});
@override
String get storageKey =>
'${activityType.name}-${tokens.map((e) => e.text.content).join("-")}-${morphFeature.name}';
@override
Map<String, dynamic> toJson() {
final json = super.toJson();
json['morph_feature'] = morphFeature.name;
return json;
}
}
class MorphCategoryPracticeActivityModel extends MorphPracticeActivityModel {
final ExampleMessageInfo exampleMessageInfo;
MorphCategoryPracticeActivityModel({
required super.tokens,
required super.langCode,
required super.morphFeature,
required super.multipleChoiceContent,
required this.exampleMessageInfo,
});
@override
OneConstructUse constructUse(String choiceContent) {
final correct = multipleChoiceContent.isCorrect(choiceContent);
final token = tokens.first;
final useType = correct
? activityType.correctUse
: activityType.incorrectUse;
final tag = token.getMorphTag(morphFeature)!;
return OneConstructUse(
useType: useType,
constructType: ConstructTypeEnum.morph,
metadata: ConstructUseMetaData(roomId: null, timeStamp: DateTime.now()),
category: morphFeature.name,
lemma: tag,
form: token.lemma.form,
xp: useType.pointValue,
);
}
@override
Map<String, dynamic> toJson() {
final json = super.toJson();
json['example_message_info'] = exampleMessageInfo.toJson();
return json;
}
}
class MorphMatchPracticeActivityModel extends MorphPracticeActivityModel {
MorphMatchPracticeActivityModel({
required super.tokens,
required super.langCode,
required super.morphFeature,
required super.multipleChoiceContent,
});
}
class VocabAudioPracticeActivityModel
extends MultipleChoicePracticeActivityModel {
final String? roomId;
final String? eventId;
final ExampleMessageInfo exampleMessage;
VocabAudioPracticeActivityModel({
required super.tokens,
required super.langCode,
required super.multipleChoiceContent,
this.roomId,
this.eventId,
required this.exampleMessage,
});
@override
Map<String, dynamic> toJson() {
final json = super.toJson();
json['room_id'] = roomId;
json['event_id'] = eventId;
json['example_message'] = exampleMessage.toJson();
return json;
}
}
class VocabMeaningPracticeActivityModel
extends MultipleChoicePracticeActivityModel {
VocabMeaningPracticeActivityModel({
required super.tokens,
required super.langCode,
required super.multipleChoiceContent,
});
}
class LemmaPracticeActivityModel extends MultipleChoicePracticeActivityModel {
LemmaPracticeActivityModel({
required super.tokens,
required super.langCode,
required super.multipleChoiceContent,
});
}
class GrammarErrorPracticeActivityModel
extends MultipleChoicePracticeActivityModel {
final String text;
final int errorOffset;
final int errorLength;
final String eventID;
final String translation;
GrammarErrorPracticeActivityModel({
required super.tokens,
required super.langCode,
required super.multipleChoiceContent,
required this.text,
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 {
EmojiPracticeActivityModel({
required super.tokens,
required super.langCode,
required super.matchContent,
});
}
class LemmaMeaningPracticeActivityModel extends MatchPracticeActivityModel {
LemmaMeaningPracticeActivityModel({
required super.tokens,
required super.langCode,
required super.matchContent,
});
}
class WordListeningPracticeActivityModel extends MatchPracticeActivityModel {
WordListeningPracticeActivityModel({
required super.tokens,
required super.langCode,
required super.matchContent,
});
}