From 652eb77dd5279c0452678be8e217d8e5f7be9a93 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Thu, 20 Jun 2024 10:05:49 -0400 Subject: [PATCH] include all choice list info in span caching, and moved span caching to a seperate file --- .../controllers/igc_controller.dart | 86 ++---------------- .../controllers/span_data_controller.dart | 89 +++++++++++++++++++ lib/pangea/models/span_data.dart | 24 +++++ lib/pangea/repo/span_data_repo.dart | 29 +++--- lib/pangea/widgets/igc/span_card.dart | 3 +- 5 files changed, 134 insertions(+), 97 deletions(-) create mode 100644 lib/pangea/controllers/span_data_controller.dart diff --git a/lib/pangea/choreographer/controllers/igc_controller.dart b/lib/pangea/choreographer/controllers/igc_controller.dart index d5fd0de95..b882e4087 100644 --- a/lib/pangea/choreographer/controllers/igc_controller.dart +++ b/lib/pangea/choreographer/controllers/igc_controller.dart @@ -3,9 +3,9 @@ import 'dart:developer'; import 'package:fluffychat/pangea/choreographer/controllers/choreographer.dart'; import 'package:fluffychat/pangea/choreographer/controllers/error_service.dart'; +import 'package:fluffychat/pangea/controllers/span_data_controller.dart'; import 'package:fluffychat/pangea/models/igc_text_data_model.dart'; import 'package:fluffychat/pangea/models/pangea_match_model.dart'; -import 'package:fluffychat/pangea/models/span_data.dart'; import 'package:fluffychat/pangea/repo/igc_repo.dart'; import 'package:fluffychat/pangea/widgets/igc/span_card.dart'; import 'package:flutter/foundation.dart'; @@ -14,41 +14,19 @@ import 'package:sentry_flutter/sentry_flutter.dart'; import '../../models/language_detection_model.dart'; import '../../models/span_card_model.dart'; -import '../../repo/span_data_repo.dart'; import '../../repo/tokens_repo.dart'; import '../../utils/error_handler.dart'; import '../../utils/overlay.dart'; -class _SpanDetailsCacheItem { - SpanDetailsRepoReqAndRes data; - - _SpanDetailsCacheItem({required this.data}); -} - class IgcController { Choreographer choreographer; IGCTextData? igcTextData; Object? igcError; - Completer igcCompleter = Completer(); - final Map _cache = {}; - Timer? _cacheClearTimer; + late SpanDataController spanDataController; IgcController(this.choreographer) { - _initializeCacheClearing(); - } - - void _initializeCacheClearing() { - const duration = Duration(minutes: 2); - _cacheClearTimer = Timer.periodic(duration, (Timer t) => _clearCache()); - } - - void _clearCache() { - _cache.clear(); - } - - void dispose() { - _cacheClearTimer?.cancel(); + spanDataController = SpanDataController(choreographer); } Future getIGCTextData({required bool tokensOnly}) async { @@ -56,10 +34,8 @@ class IgcController { if (choreographer.currentText.isEmpty) return clear(); // the error spans are going to be reloaded, so clear the cache - _clearCache(); - + spanDataController.clearCache(); debugPrint('getIGCTextData called with ${choreographer.currentText}'); - debugPrint('getIGCTextData called with tokensOnly = $tokensOnly'); final IGCRequestBody reqBody = IGCRequestBody( @@ -110,7 +86,7 @@ class IgcController { // This will make the loading of span details faster for the user if (igcTextData?.matches.isNotEmpty ?? false) { for (int i = 0; i < igcTextData!.matches.length; i++) { - getSpanDetails(i); + spanDataController.getSpanDetails(i); } } @@ -125,56 +101,6 @@ class IgcController { } } - Future getSpanDetails(int matchIndex) async { - if (igcTextData == null || - igcTextData!.matches.isEmpty || - matchIndex < 0 || - matchIndex >= igcTextData!.matches.length) { - debugger(when: kDebugMode); - return; - } - - /// Retrieves the span data from the `igcTextData` matches at the specified `matchIndex`. - /// Creates a `SpanDetailsRepoReqAndRes` object with the retrieved span data and other parameters. - /// Generates a cache key based on the created `SpanDetailsRepoReqAndRes` object. - final SpanData span = igcTextData!.matches[matchIndex].match; - final req = SpanDetailsRepoReqAndRes( - userL1: choreographer.l1LangCode!, - userL2: choreographer.l2LangCode!, - enableIGC: choreographer.igcEnabled, - enableIT: choreographer.itEnabled, - span: span, - ); - final int cacheKey = req.hashCode; - - /// Retrieves the [SpanDetailsRepoReqAndRes] response from the cache if it exists, - /// otherwise makes an API call to get the response and stores it in the cache. - SpanDetailsRepoReqAndRes response; - if (_cache.containsKey(cacheKey)) { - response = _cache[cacheKey]!.data; - } else { - response = await SpanDataRepo.getSpanDetails( - await choreographer.accessToken, - request: SpanDetailsRepoReqAndRes( - userL1: choreographer.l1LangCode!, - userL2: choreographer.l2LangCode!, - enableIGC: choreographer.igcEnabled, - enableIT: choreographer.itEnabled, - span: span, - ), - ); - _cache[cacheKey] = _SpanDetailsCacheItem(data: response); - } - - try { - igcTextData!.matches[matchIndex].match = response.span; - } catch (err, s) { - ErrorHandler.logError(e: err, s: s); - } - - choreographer.setState(); - } - Future justGetTokensAndAddThemToIGCTextData() async { try { if (igcTextData == null) { @@ -290,7 +216,7 @@ class IgcController { clear() { igcTextData = null; - _clearCache(); + spanDataController.clearCache(); // Not sure why this is here // MatrixState.pAnyState.closeOverlay(); } diff --git a/lib/pangea/controllers/span_data_controller.dart b/lib/pangea/controllers/span_data_controller.dart new file mode 100644 index 000000000..5f83f1a53 --- /dev/null +++ b/lib/pangea/controllers/span_data_controller.dart @@ -0,0 +1,89 @@ +import 'dart:async'; +import 'dart:developer'; + +import 'package:fluffychat/pangea/choreographer/controllers/choreographer.dart'; +import 'package:fluffychat/pangea/models/span_data.dart'; +import 'package:fluffychat/pangea/repo/span_data_repo.dart'; +import 'package:fluffychat/pangea/utils/error_handler.dart'; +import 'package:flutter/foundation.dart'; + +class _SpanDetailsCacheItem { + Future data; + + _SpanDetailsCacheItem({required this.data}); +} + +class SpanDataController { + late Choreographer choreographer; + final Map _cache = {}; + Timer? _cacheClearTimer; + + SpanDataController(this.choreographer) { + _initializeCacheClearing(); + } + + void _initializeCacheClearing() { + const duration = Duration(minutes: 2); + _cacheClearTimer = Timer.periodic(duration, (Timer t) => clearCache()); + } + + void clearCache() { + _cache.clear(); + } + + void dispose() { + _cacheClearTimer?.cancel(); + } + + Future getSpanDetails(int matchIndex) async { + if (choreographer.igc.igcTextData == null || + choreographer.igc.igcTextData!.matches.isEmpty || + matchIndex < 0 || + matchIndex >= choreographer.igc.igcTextData!.matches.length) { + debugger(when: kDebugMode); + return; + } + + /// Retrieves the span data from the `igcTextData` matches at the specified `matchIndex`. + /// Creates a `SpanDetailsRepoReqAndRes` object with the retrieved span data and other parameters. + /// Generates a cache key based on the created `SpanDetailsRepoReqAndRes` object. + final SpanData span = + choreographer.igc.igcTextData!.matches[matchIndex].match; + final req = SpanDetailsRepoReqAndRes( + userL1: choreographer.l1LangCode!, + userL2: choreographer.l2LangCode!, + enableIGC: choreographer.igcEnabled, + enableIT: choreographer.itEnabled, + span: span, + ); + final int cacheKey = req.hashCode; + + /// Retrieves the [SpanDetailsRepoReqAndRes] response from the cache if it exists, + /// otherwise makes an API call to get the response and stores it in the cache. + Future response; + if (_cache.containsKey(cacheKey)) { + response = _cache[cacheKey]!.data; + } else { + response = SpanDataRepo.getSpanDetails( + await choreographer.accessToken, + request: SpanDetailsRepoReqAndRes( + userL1: choreographer.l1LangCode!, + userL2: choreographer.l2LangCode!, + enableIGC: choreographer.igcEnabled, + enableIT: choreographer.itEnabled, + span: span, + ), + ); + _cache[cacheKey] = _SpanDetailsCacheItem(data: response); + } + + try { + choreographer.igc.igcTextData!.matches[matchIndex].match = + (await response).span; + } catch (err, s) { + ErrorHandler.logError(e: err, s: s); + } + + choreographer.setState(); + } +} diff --git a/lib/pangea/models/span_data.dart b/lib/pangea/models/span_data.dart index d420e1ffd..bf8ab8eca 100644 --- a/lib/pangea/models/span_data.dart +++ b/lib/pangea/models/span_data.dart @@ -148,6 +148,30 @@ class SpanChoice { bool get isBestCorrection => type == SpanChoiceType.bestCorrection; Color get color => type.color; + + // override == operator and hashcode + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is SpanChoice && + other.value == value && + other.type.toString() == type.toString() && + other.selected == selected && + other.feedback == feedback && + other.timestamp?.toIso8601String() == timestamp?.toIso8601String(); + } + + @override + int get hashCode { + return Object.hashAll([ + value.hashCode, + type.toString().hashCode, + selected.hashCode, + feedback.hashCode, + timestamp?.toIso8601String().hashCode, + ]); + } } class Rule { diff --git a/lib/pangea/repo/span_data_repo.dart b/lib/pangea/repo/span_data_repo.dart index f06363fd1..3c2eb2ab3 100644 --- a/lib/pangea/repo/span_data_repo.dart +++ b/lib/pangea/repo/span_data_repo.dart @@ -85,16 +85,13 @@ class SpanDetailsRepoReqAndRes { if (other.userL2 != userL2) return false; if (other.enableIT != enableIT) return false; if (other.enableIGC != enableIGC) return false; - if (other.span.choices - ?.firstWhere( - (choice) => choice.type == SpanChoiceType.bestCorrection, - ) - .value != - span.choices - ?.firstWhere( - (choice) => choice.type == SpanChoiceType.bestCorrection, - ) - .value) return false; + if (const ListEquality().equals( + other.span.choices?.sorted((a, b) => b.value.compareTo(a.value)), + span.choices?.sorted((a, b) => b.value.compareTo(a.value)), + ) == + false) { + return false; + } return true; } @@ -107,12 +104,12 @@ class SpanDetailsRepoReqAndRes { userL2.hashCode, enableIT.hashCode, enableIGC.hashCode, - span.choices - ?.firstWhereOrNull( - (choice) => choice.type == SpanChoiceType.bestCorrection, - ) - ?.value - .hashCode, + if (span.choices != null) + Object.hashAll( + span.choices! + .sorted((a, b) => b.value.compareTo(a.value)) + .map((choice) => choice.hashCode), + ), ]); } } diff --git a/lib/pangea/widgets/igc/span_card.dart b/lib/pangea/widgets/igc/span_card.dart index 0d149eaec..6fa4e17b3 100644 --- a/lib/pangea/widgets/igc/span_card.dart +++ b/lib/pangea/widgets/igc/span_card.dart @@ -94,7 +94,8 @@ class SpanCardState extends State { fetchingData = true; }); - await widget.scm.choreographer.igc.getSpanDetails(widget.scm.matchIndex); + await widget.scm.choreographer.igc.spanDataController + .getSpanDetails(widget.scm.matchIndex); if (mounted) { setState(() => fetchingData = false);