import 'dart:math'; 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_constants.dart'; 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 toJson() => { 'target': target.toJson(), 'grammarErrorInfo': grammarErrorInfo?.toJson(), }; factory AnalyticsActivityTarget.fromJson(Map json) => AnalyticsActivityTarget( target: PracticeTarget.fromJson(json['target']), grammarErrorInfo: json['grammarErrorInfo'] != null ? GrammarErrorRequestInfo.fromJson(json['grammarErrorInfo']) : null, ); } class AnalyticsPracticeSessionModel { final DateTime startedAt; final List practiceTargets; final String userL1; final String userL2; AnalyticsPracticeSessionState state; AnalyticsPracticeSessionModel({ required this.startedAt, required this.practiceTargets, required this.userL1, required this.userL2, AnalyticsPracticeSessionState? state, }) : state = state ?? const AnalyticsPracticeSessionState(); int get _availableActivities => min( AnalyticsPracticeConstants.practiceGroupSize, practiceTargets.length, ); bool get isComplete => state.currentIndex >= _availableActivities; double get progress => (state.currentIndex / _availableActivities).clamp(0.0, 1.0); List get activityRequests { return practiceTargets.map((target) { return MessageActivityRequest( userL1: userL1, userL2: userL2, activityQualityFeedback: null, target: target.target, grammarErrorInfo: target.grammarErrorInfo, ); }).toList(); } void setElapsedSeconds(int seconds) => state = state.copyWith(elapsedSeconds: seconds); void finishSession() => state = state.copyWith(finished: true); void completeActivity() => state = state.copyWith(currentIndex: state.currentIndex + 1); void submitAnswer(OneConstructUse use) => state = state.copyWith( completedUses: [...state.completedUses, use], ); factory AnalyticsPracticeSessionModel.fromJson(Map json) { return AnalyticsPracticeSessionModel( startedAt: DateTime.parse(json['startedAt'] as String), practiceTargets: (json['practiceTargets'] as List) .map((e) => AnalyticsActivityTarget.fromJson(e)) .whereType() .toList(), userL1: json['userL1'] as String, userL2: json['userL2'] as String, state: AnalyticsPracticeSessionState.fromJson( json, ), ); } Map toJson() { return { 'startedAt': startedAt.toIso8601String(), 'practiceTargets': practiceTargets.map((e) => e.toJson()).toList(), 'userL1': userL1, 'userL2': userL2, ...state.toJson(), }; } } class AnalyticsPracticeSessionState { final List completedUses; final int currentIndex; final bool finished; final int elapsedSeconds; const AnalyticsPracticeSessionState({ this.completedUses = const [], this.currentIndex = 0, this.finished = false, this.elapsedSeconds = 0, }); int get totalXpGained => completedUses.fold(0, (sum, use) => sum + use.xp); double get accuracy { if (completedUses.isEmpty) return 0.0; final correct = completedUses.where((use) => use.xp > 0).length; final result = correct / completedUses.length; return (result * 100).truncateToDouble(); } bool get _giveAccuracyBonus => accuracy >= 100.0; bool get _giveTimeBonus => elapsedSeconds <= AnalyticsPracticeConstants.timeForBonus; int get bonusXP => accuracyBonusXP + timeBonusXP; int get accuracyBonusXP => _giveAccuracyBonus ? _bonusXP : 0; int get timeBonusXP => _giveTimeBonus ? _bonusXP : 0; int get _bonusXP => _bonusUses.fold(0, (sum, use) => sum + use.xp); int get allXPGained => totalXpGained + bonusXP; List get _bonusUses => completedUses.where((use) => use.xp > 0).map(_bonusUse).toList(); List get allBonusUses => [ if (_giveAccuracyBonus) ..._bonusUses, if (_giveTimeBonus) ..._bonusUses, ]; OneConstructUse _bonusUse(OneConstructUse originalUse) => OneConstructUse( useType: ConstructUseTypeEnum.bonus, constructType: originalUse.constructType, metadata: ConstructUseMetaData( roomId: originalUse.metadata.roomId, timeStamp: DateTime.now(), ), category: originalUse.category, lemma: originalUse.lemma, form: originalUse.form, xp: ConstructUseTypeEnum.bonus.pointValue, ); AnalyticsPracticeSessionState copyWith({ List? completedUses, int? currentIndex, bool? finished, int? elapsedSeconds, }) { return AnalyticsPracticeSessionState( completedUses: completedUses ?? this.completedUses, currentIndex: currentIndex ?? this.currentIndex, finished: finished ?? this.finished, elapsedSeconds: elapsedSeconds ?? this.elapsedSeconds, ); } Map toJson() { return { 'completedUses': completedUses.map((e) => e.toJson()).toList(), 'currentIndex': currentIndex, 'finished': finished, 'elapsedSeconds': elapsedSeconds, }; } factory AnalyticsPracticeSessionState.fromJson(Map json) { return AnalyticsPracticeSessionState( completedUses: (json['completedUses'] as List?) ?.map((e) => OneConstructUse.fromJson(e)) .whereType() .toList() ?? [], currentIndex: json['currentIndex'] as int? ?? 0, finished: json['finished'] as bool? ?? false, elapsedSeconds: json['elapsedSeconds'] as int? ?? 0, ); } }