* chore: move logic for lastUsedByActivityType into ConstructIdentifier * feat: vocab practice * add vocab activity progress bar * fix: shuffle audio practice choices * update UI of vocab practice Added buttons, increased text size and change position, cards flip over and turn red/green on click and respond to hover input * add xp sparkle, shimmering choice card placeholder * spacing changes fix padding, make choice cards spacing/sizing responsive to screen size, replace shimmer cards with stationary circle indicator * don't include duplicate lemma choices * use constructID and show lemma/emoji on choice cards add method to clear cache in case the results was an error, and add a retry button on error * gain xp immediately and take out continue session also refactor the choice cards to have separate widgets for each type and a parent widget to give each an id for xp sparkle * add practice finished page with analytics * Color tweaks on completed page and time card placeholder * add timer * give XP for bonuses and change timer to use stopwatch * simplify card logic, lock practice when few vocab words * merge analytics changes and fix bugs * reload on language change - derive XP data from new analytics - Don't allow any clicks after correct answer selected * small fixes, added tooltip, added copy to l10 * small tweaks and comments * formatting and import sorting --------- Co-authored-by: avashilling <165050625+avashilling@users.noreply.github.com>
129 lines
3.6 KiB
Dart
129 lines
3.6 KiB
Dart
import 'package:collection/collection.dart';
|
|
import 'package:sentry_flutter/sentry_flutter.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/practice_activity_model.dart';
|
|
|
|
// includes feedback text and the bad activity model
|
|
class ActivityQualityFeedback {
|
|
final String feedbackText;
|
|
final PracticeActivityModel badActivity;
|
|
|
|
ActivityQualityFeedback({
|
|
required this.feedbackText,
|
|
required this.badActivity,
|
|
});
|
|
|
|
factory ActivityQualityFeedback.fromJson(Map<String, dynamic> json) {
|
|
return ActivityQualityFeedback(
|
|
feedbackText: json['feedback_text'] as String,
|
|
badActivity: PracticeActivityModel.fromJson(
|
|
json['bad_activity'] as Map<String, dynamic>,
|
|
),
|
|
);
|
|
}
|
|
|
|
Map<String, dynamic> toJson() {
|
|
return {
|
|
'feedback_text': feedbackText,
|
|
'bad_activity': badActivity.toJson(),
|
|
};
|
|
}
|
|
|
|
@override
|
|
bool operator ==(Object other) {
|
|
if (identical(this, other)) return true;
|
|
|
|
return other is ActivityQualityFeedback &&
|
|
other.feedbackText == feedbackText &&
|
|
other.badActivity == badActivity;
|
|
}
|
|
|
|
@override
|
|
int get hashCode {
|
|
return feedbackText.hashCode ^ badActivity.hashCode;
|
|
}
|
|
}
|
|
|
|
class MessageActivityRequest {
|
|
final String userL1;
|
|
final String userL2;
|
|
|
|
final List<PangeaToken> targetTokens;
|
|
final ActivityTypeEnum targetType;
|
|
final MorphFeaturesEnum? targetMorphFeature;
|
|
|
|
final ActivityQualityFeedback? activityQualityFeedback;
|
|
|
|
MessageActivityRequest({
|
|
required this.userL1,
|
|
required this.userL2,
|
|
required this.activityQualityFeedback,
|
|
required this.targetTokens,
|
|
required this.targetType,
|
|
required this.targetMorphFeature,
|
|
}) {
|
|
if (targetTokens.isEmpty) {
|
|
throw Exception('Target tokens must not be empty');
|
|
}
|
|
}
|
|
|
|
Map<String, dynamic> toJson() {
|
|
return {
|
|
'user_l1': userL1,
|
|
'user_l2': userL2,
|
|
'activity_quality_feedback': activityQualityFeedback?.toJson(),
|
|
'target_tokens': targetTokens.map((e) => e.toJson()).toList(),
|
|
'target_type': targetType.name,
|
|
'target_morph_feature': targetMorphFeature,
|
|
};
|
|
}
|
|
|
|
@override
|
|
bool operator ==(Object other) {
|
|
if (identical(this, other)) return true;
|
|
|
|
return other is MessageActivityRequest &&
|
|
other.targetType == targetType &&
|
|
other.activityQualityFeedback?.feedbackText ==
|
|
activityQualityFeedback?.feedbackText &&
|
|
const ListEquality().equals(other.targetTokens, targetTokens) &&
|
|
other.targetMorphFeature == targetMorphFeature;
|
|
}
|
|
|
|
@override
|
|
int get hashCode {
|
|
return targetType.hashCode ^
|
|
activityQualityFeedback.hashCode ^
|
|
targetTokens.hashCode ^
|
|
targetMorphFeature.hashCode;
|
|
}
|
|
}
|
|
|
|
class MessageActivityResponse {
|
|
final PracticeActivityModel activity;
|
|
|
|
MessageActivityResponse({
|
|
required this.activity,
|
|
});
|
|
|
|
factory MessageActivityResponse.fromJson(Map<String, dynamic> json) {
|
|
if (!json.containsKey('activity')) {
|
|
Sentry.addBreadcrumb(Breadcrumb(data: {"json": json}));
|
|
throw Exception('Activity not found in message activity response');
|
|
}
|
|
|
|
if (json['activity'] is! Map<String, dynamic>) {
|
|
Sentry.addBreadcrumb(Breadcrumb(data: {"json": json}));
|
|
throw Exception('Activity is not a map in message activity response');
|
|
}
|
|
|
|
return MessageActivityResponse(
|
|
activity: PracticeActivityModel.fromJson(
|
|
json['activity'] as Map<String, dynamic>,
|
|
),
|
|
);
|
|
}
|
|
}
|