You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
fluffychat/lib/pangea/models/practice_activities.dart/message_activity_request.dart

337 lines
9.7 KiB
Dart

import 'package:collection/collection.dart';
import 'package:fluffychat/pangea/enum/activity_type_enum.dart';
import 'package:fluffychat/pangea/enum/construct_type_enum.dart';
import 'package:fluffychat/pangea/models/analytics/construct_use_model.dart';
import 'package:fluffychat/pangea/models/pangea_token_model.dart';
import 'package:fluffychat/pangea/models/practice_activities.dart/practice_activity_model.dart';
import 'package:fluffychat/widgets/matrix.dart';
class TokenWithXP {
final PangeaToken token;
late List<ActivityTypeEnum> targetTypes;
TokenWithXP({
required this.token,
}) {
targetTypes = [];
}
List<ConstructIdentifier> get _constructIDs {
final List<ConstructIdentifier> ids = [];
ids.add(
ConstructIdentifier(
lemma: token.lemma.text,
type: ConstructTypeEnum.vocab,
category: token.pos,
),
);
for (final morph in token.morph.entries) {
ids.add(
ConstructIdentifier(
lemma: morph.value,
type: ConstructTypeEnum.morph,
category: morph.key,
),
);
}
return ids;
}
List<ConstructUses> get constructs => _constructIDs
.map(
(id) =>
MatrixState.pangeaController.getAnalytics.constructListModel
.getConstructUses(id) ??
ConstructUses(
lemma: id.lemma,
constructType: id.type,
category: id.category,
uses: [],
),
)
.toList();
factory TokenWithXP.fromJson(Map<String, dynamic> json) {
return TokenWithXP(
token: PangeaToken.fromJson(json['token'] as Map<String, dynamic>),
);
}
Map<String, dynamic> toJson() {
return {
'token': token.toJson(),
'constructs_with_xp': constructs.map((e) => e.toJson()).toList(),
'target_types': targetTypes.map((e) => e.string).toList(),
};
}
bool eligibleForActivity(ActivityTypeEnum a) {
switch (a) {
case ActivityTypeEnum.wordMeaning:
return token.isContentWord;
case ActivityTypeEnum.wordFocusListening:
case ActivityTypeEnum.hiddenWordListening:
return token.canBeHeard;
}
}
bool didActivity(ActivityTypeEnum a) {
switch (a) {
case ActivityTypeEnum.wordMeaning:
return vocabConstruct.uses
.map((u) => u.useType)
.any((u) => a.associatedUseTypes.contains(u));
case ActivityTypeEnum.wordFocusListening:
return vocabConstruct.uses
// TODO - double-check that form is going to be available here
// .where((u) =>
// u.form?.toLowerCase() == token.text.content.toLowerCase(),)
.map((u) => u.useType)
.any((u) => a.associatedUseTypes.contains(u));
case ActivityTypeEnum.hiddenWordListening:
return vocabConstruct.uses
// TODO - double-check that form is going to be available here
// .where((u) =>
// u.form?.toLowerCase() == token.text.content.toLowerCase(),)
.map((u) => u.useType)
.any((u) => a.associatedUseTypes.contains(u));
}
}
ConstructUses get vocabConstruct {
final vocab = constructs.firstWhereOrNull(
(element) => element.id.type == ConstructTypeEnum.vocab,
);
if (vocab == null) {
return ConstructUses(
lemma: token.lemma.text,
constructType: ConstructTypeEnum.vocab,
category: token.pos,
uses: [],
);
}
return vocab;
}
int get xp {
return constructs.fold<int>(
0,
(previousValue, element) => previousValue + element.points,
);
}
///
DateTime? get lastUsed => constructs.fold<DateTime?>(
null,
(previousValue, element) {
if (previousValue == null) return element.lastUsed;
if (element.lastUsed == null) return previousValue;
return element.lastUsed!.isAfter(previousValue)
? element.lastUsed
: previousValue;
},
);
/// daysSinceLastUse
int get daysSinceLastUse {
if (lastUsed == null) return 1000;
return DateTime.now().difference(lastUsed!).inDays;
}
//override operator == and hashCode
// check that the list of constructs are the same
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is TokenWithXP &&
const ListEquality().equals(other.constructs, constructs);
}
@override
int get hashCode {
return const ListEquality().hash(constructs);
}
}
class ExistingActivityMetaData {
final String activityEventId;
final List<ConstructIdentifier> tgtConstructs;
final ActivityTypeEnum activityType;
ExistingActivityMetaData({
required this.activityEventId,
required this.tgtConstructs,
required this.activityType,
});
factory ExistingActivityMetaData.fromJson(Map<String, dynamic> json) {
return ExistingActivityMetaData(
activityEventId: json['activity_event_id'] as String,
tgtConstructs: ((json['tgt_constructs'] ?? json['target_constructs'])
as List)
.map((e) => ConstructIdentifier.fromJson(e as Map<String, dynamic>))
.toList(),
activityType: ActivityTypeEnum.values.firstWhere(
(element) =>
element.string == json['activity_type'] as String ||
element.string.split('.').last == json['activity_type'] as String,
),
);
}
Map<String, dynamic> toJson() {
return {
'activity_event_id': activityEventId,
'target_constructs': tgtConstructs.map((e) => e.toJson()).toList(),
'activity_type': activityType.string,
};
}
}
// 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 String messageText;
final ActivityQualityFeedback? activityQualityFeedback;
/// tokens with their associated constructs and xp
final List<TokenWithXP> tokensWithXP;
/// make the server aware of existing activities for potential reuse
final List<ExistingActivityMetaData> existingActivities;
final String messageId;
final List<ActivityTypeEnum> clientCompatibleActivities;
final ActivityTypeEnum clientTypeRequest;
final TokenWithXP clientTokenRequest;
MessageActivityRequest({
required this.userL1,
required this.userL2,
required this.messageText,
required this.tokensWithXP,
required this.messageId,
required this.existingActivities,
required this.activityQualityFeedback,
required this.clientCompatibleActivities,
required this.clientTokenRequest,
required this.clientTypeRequest,
});
Map<String, dynamic> toJson() {
return {
'user_l1': userL1,
'user_l2': userL2,
'message_text': messageText,
'tokens_with_xp': tokensWithXP.map((e) => e.toJson()).toList(),
'message_id': messageId,
'existing_activities': existingActivities.map((e) => e.toJson()).toList(),
'activity_quality_feedback': activityQualityFeedback?.toJson(),
'iso_8601_time_of_req': DateTime.now().toIso8601String(),
// this is a list of activity types that the client can handle
// the server will only return activities of these types
// this for backwards compatibility with old clients
'client_version_compatible_activity_types':
clientCompatibleActivities.map((e) => e.string).toList(),
'client_type_request': clientTypeRequest.string,
'client_token_request': clientTokenRequest.toJson(),
};
}
// equals accounts for message_id and last_used of each token
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is MessageActivityRequest &&
other.messageId == messageId &&
const ListEquality().equals(other.tokensWithXP, tokensWithXP);
}
@override
int get hashCode {
return messageId.hashCode ^
const ListEquality().hash(tokensWithXP) ^
activityQualityFeedback.hashCode;
}
}
class MessageActivityResponse {
final PracticeActivityModel? activity;
final bool finished;
final String? existingActivityEventId;
MessageActivityResponse({
required this.activity,
required this.finished,
required this.existingActivityEventId,
});
factory MessageActivityResponse.fromJson(Map<String, dynamic> json) {
return MessageActivityResponse(
activity: json['activity'] != null
? PracticeActivityModel.fromJson(
json['activity'] as Map<String, dynamic>,
)
: null,
finished: json['finished'] as bool,
existingActivityEventId: json['existing_activity_event_id'] as String?,
);
}
Map<String, dynamic> toJson() {
return {
'activity': activity?.toJson(),
'finished': finished,
'existing_activity_event_id': existingActivityEventId,
};
}
}