fluffychat/lib/pangea/analytics_practice/analytics_practice_session_controller.dart
ggurdin 117a03089e
5720 vocab practice should have feedback flag (#5761)
* chore: split up analytics activity page widgets into separate files

* started analytics practice refactor

* refactor how UI updates are triggered in analytics practice page

* some fixes
2026-02-20 13:25:21 -05:00

176 lines
5.2 KiB
Dart

import 'dart:async';
import 'dart:collection';
import 'package:flutter/material.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_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/analytics_practice/analytics_practice_session_repo.dart';
import 'package:fluffychat/pangea/common/utils/error_handler.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';
import 'package:fluffychat/widgets/future_loading_dialog.dart';
import 'package:fluffychat/widgets/matrix.dart';
class _PracticeQueueEntry {
final MessageActivityRequest request;
final Completer<MultipleChoicePracticeActivityModel> completer;
_PracticeQueueEntry({required this.request, required this.completer});
}
class PracticeSessionController {
PracticeSessionController();
AnalyticsPracticeSessionModel? session;
bool isLoadingSession = false;
Object? sessionError;
final Queue<_PracticeQueueEntry> _queue = Queue();
void clear() {
_queue.clear();
}
List<MessageActivityRequest> get activityRequests =>
session?.activityRequests ?? [];
List<OneConstructUse> get bonusUses => session?.state.allBonusUses ?? [];
int get hintsUsed => session?.state.hintsUsed ?? 0;
double get progress => session?.progress ?? 0;
String getCompletionMessage(BuildContext context) =>
session?.getCompletionMessage(context) ??
L10n.of(context).youveCompletedPractice;
void updateElapsedTime(int seconds) {
session?.setElapsedSeconds(seconds);
}
void updateHintsPressed() {
session?.useHint();
}
void updateElapsedSeconds(int seconds) {
session?.setElapsedSeconds(seconds);
}
void completeActivity() {
session?.completeActivity();
}
void skipActivity() {
session?.incrementSkippedActivities();
}
void submitAnswer(List<OneConstructUse> uses) {
session?.submitAnswer(uses);
}
Future<void> startSession(ConstructTypeEnum type) async {
try {
isLoadingSession = true;
sessionError = null;
session = null;
final l2 =
MatrixState.pangeaController.userController.userL2?.langCodeShort;
if (l2 == null) throw Exception('User L2 language not set');
session = await AnalyticsPracticeSessionRepo.get(type, l2);
} catch (e, s) {
ErrorHandler.logError(e: e, s: s, data: {});
sessionError = e;
} finally {
isLoadingSession = false;
}
}
Future<void> completeSession() async {
session?.finishSession();
}
Future<MultipleChoicePracticeActivityModel?> _initActivityData(
Future Function(MessageActivityRequest) onSkip,
Future Function(MultipleChoicePracticeActivityModel) onFetch,
) async {
final requests = activityRequests;
for (var i = 0; i < requests.length; i++) {
try {
final req = requests[i];
final res = await _fetchActivity(req, onFetch);
_fillActivityQueue(requests.skip(i + 1).toList(), onSkip, onFetch);
return res;
} catch (e) {
await onSkip(requests[i]);
// Try next request
continue;
}
}
return null;
}
Future<void> _fillActivityQueue(
List<MessageActivityRequest> requests,
Future Function(MessageActivityRequest) onSkip,
Future Function(MultipleChoicePracticeActivityModel) onFetch,
) async {
for (final request in requests) {
final completer = Completer<MultipleChoicePracticeActivityModel>();
_queue.add(_PracticeQueueEntry(request: request, completer: completer));
try {
final res = await _fetchActivity(request, onFetch);
completer.complete(res);
} catch (e) {
completer.completeError(e);
await onSkip(request);
}
}
}
Future<MultipleChoicePracticeActivityModel> _fetchActivity(
MessageActivityRequest req,
Future Function(MultipleChoicePracticeActivityModel) onFetch,
) async {
final result = await PracticeRepo.getPracticeActivity(req, messageInfo: {});
if (result.isError ||
result.result is! MultipleChoicePracticeActivityModel) {
throw Exception();
}
final activityModel = result.result as MultipleChoicePracticeActivityModel;
await onFetch(activityModel);
return activityModel;
}
Future<MultipleChoicePracticeActivityModel?> getNextActivity(
Future Function(MessageActivityRequest) onSkip,
Future Function(MultipleChoicePracticeActivityModel) onFetch,
) async {
if (session == null) {
throw Exception("Called getNextActivity without loading session");
}
if (!session!.isComplete && _queue.isEmpty) {
return _initActivityData(onSkip, onFetch);
}
while (_queue.isNotEmpty) {
final nextActivityCompleter = _queue.removeFirst();
try {
final activity = await nextActivityCompleter.completer.future;
return activity;
} catch (e) {
// Completer failed, skip to next
continue;
}
}
return null;
}
}