diff --git a/lib/pages/chat/events/html_message.dart b/lib/pages/chat/events/html_message.dart index 393a1fa46..67263af7b 100644 --- a/lib/pages/chat/events/html_message.dart +++ b/lib/pages/chat/events/html_message.dart @@ -14,7 +14,7 @@ import 'package:fluffychat/pages/chat/chat.dart'; import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart'; import 'package:fluffychat/pangea/events/models/pangea_token_model.dart'; import 'package:fluffychat/pangea/message_token_text/message_token_button.dart'; -import 'package:fluffychat/pangea/message_token_text/token_position_model.dart'; +import 'package:fluffychat/pangea/message_token_text/tokens_util.dart'; import 'package:fluffychat/pangea/toolbar/enums/reading_assistance_mode_enum.dart'; import 'package:fluffychat/pangea/toolbar/utils/token_rendering_util.dart'; import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart'; @@ -396,6 +396,9 @@ class HtmlMessage extends StatelessWidget { ); final fontSize = renderer.fontSize(context) ?? this.fontSize; + final newTokens = pangeaMessageEvent != null + ? TokensUtil.getNewTokens(pangeaMessageEvent!) + : []; // Pangea# switch (node.localName) { @@ -415,10 +418,7 @@ class HtmlMessage extends StatelessWidget { ? isHighlighted!.call(token) : false; - final isNew = token != null && - overlayController != null && - overlayController!.isNewToken(token); - + final isNew = token != null && newTokens.contains(token.text); final tokenWidth = renderer.tokenTextWidthForContainer( context, node.text, diff --git a/lib/pangea/course_plans/course_plan_builder.dart b/lib/pangea/course_plans/course_plan_builder.dart index 8618dcaa1..e83847b02 100644 --- a/lib/pangea/course_plans/course_plan_builder.dart +++ b/lib/pangea/course_plans/course_plan_builder.dart @@ -68,9 +68,7 @@ class CoursePlanController extends State { widget.onNotFound?.call(); error = e; } finally { - setState(() { - loading = false; - }); + if (mounted) setState(() => loading = false); } } diff --git a/lib/pangea/message_token_text/token_position_model.dart b/lib/pangea/message_token_text/tokens_util.dart similarity index 69% rename from lib/pangea/message_token_text/token_position_model.dart rename to lib/pangea/message_token_text/tokens_util.dart index cd5a47355..1922f656d 100644 --- a/lib/pangea/message_token_text/token_position_model.dart +++ b/lib/pangea/message_token_text/tokens_util.dart @@ -1,4 +1,27 @@ +import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart'; import 'package:fluffychat/pangea/events/models/pangea_token_model.dart'; +import 'package:fluffychat/pangea/events/models/pangea_token_text_model.dart'; +import 'package:fluffychat/widgets/matrix.dart'; + +class _TokenPositionCacheItem { + final List positions; + final DateTime timestamp; + + _TokenPositionCacheItem( + this.positions, + this.timestamp, + ); +} + +class _NewTokenCacheItem { + final List tokens; + final DateTime timestamp; + + _NewTokenCacheItem( + this.tokens, + this.timestamp, + ); +} class TokenPosition { final PangeaToken? token; @@ -15,9 +38,74 @@ class TokenPosition { class TokensUtil { /// A cache of calculated adjacent token positions static final Map _tokenPositionCache = {}; + static final Map _newTokenCache = {}; static const Duration _cacheDuration = Duration(minutes: 1); + static List? _getCachedNewTokens(String eventID) { + final cacheItem = _newTokenCache[eventID]; + if (cacheItem == null) return null; + if (cacheItem.timestamp.isBefore(DateTime.now().subtract(_cacheDuration))) { + _newTokenCache.remove(eventID); + return null; + } + + return cacheItem.tokens; + } + + static void _setCachedNewTokens( + String eventID, + List tokens, + ) { + _newTokenCache[eventID] = _NewTokenCacheItem( + tokens, + DateTime.now(), + ); + } + + static List getNewTokens( + PangeaMessageEvent event, + ) { + final messageInUserL2 = event.messageDisplayLangCode.split("-")[0] == + MatrixState.pangeaController.languageController.userL2?.langCodeShort; + + final cached = _getCachedNewTokens(event.eventId); + if (cached != null) { + if (!messageInUserL2) { + _newTokenCache.remove(event.eventId); + return []; + } + return cached; + } + + final tokens = event.messageDisplayRepresentation?.tokens; + if (!messageInUserL2 || tokens == null || tokens.isEmpty) { + return []; + } + + final List newTokens = []; + for (final token in tokens) { + if (!token.lemma.saveVocab || !token.isContentWord) continue; + if (token.vocabConstruct.uses.isNotEmpty) continue; + if (newTokens.any((t) => t == token.text)) continue; + + newTokens.add(token.text); + if (newTokens.length >= 3) break; + } + + _setCachedNewTokens(event.eventId, newTokens); + return newTokens; + } + + static bool isNewToken(PangeaToken token, PangeaMessageEvent event) { + final newTokens = getNewTokens(event); + return newTokens.any((t) => t == token.text); + } + + static clearNewTokenCache(String eventID) { + _newTokenCache.remove(eventID); + } + static List? _getCachedTokenPositions(String eventID) { final cacheItem = _tokenPositionCache[eventID]; if (cacheItem == null) return null; @@ -158,13 +246,3 @@ class TokensUtil { return tokenPositions; } } - -class _TokenPositionCacheItem { - final List positions; - final DateTime timestamp; - - _TokenPositionCacheItem( - this.positions, - this.timestamp, - ); -} diff --git a/lib/pangea/toolbar/widgets/message_selection_overlay.dart b/lib/pangea/toolbar/widgets/message_selection_overlay.dart index 43bcbb1dd..ddd0f5c64 100644 --- a/lib/pangea/toolbar/widgets/message_selection_overlay.dart +++ b/lib/pangea/toolbar/widgets/message_selection_overlay.dart @@ -19,6 +19,7 @@ import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dar import 'package:fluffychat/pangea/events/event_wrappers/pangea_representation_event.dart'; import 'package:fluffychat/pangea/events/models/pangea_token_model.dart'; import 'package:fluffychat/pangea/events/models/pangea_token_text_model.dart'; +import 'package:fluffychat/pangea/message_token_text/tokens_util.dart'; import 'package:fluffychat/pangea/practice_activities/activity_type_enum.dart'; import 'package:fluffychat/pangea/practice_activities/practice_activity_model.dart'; import 'package:fluffychat/pangea/practice_activities/practice_choice.dart'; @@ -102,8 +103,6 @@ class MessageOverlayController extends State double maxWidth = AppConfig.toolbarMinWidth; - List newTokens = []; - ///////////////////////////////////// /// Lifecycle ///////////////////////////////////// @@ -148,14 +147,6 @@ class MessageOverlayController extends State MatrixState.pangeaController.languageController.userL2!.langCode, ); } - - newTokens = pangeaMessageEvent?.messageDisplayRepresentation?.tokens - ?.where((token) { - return token.lemma.saveVocab == true && - token.vocabConstruct.uses.isEmpty && - messageInUserL2; - }).toList() ?? - []; } catch (e, s) { debugger(when: kDebugMode); ErrorHandler.logError( @@ -554,14 +545,8 @@ class MessageOverlayController extends State ); if (mounted) { - setState(() { - newTokens.removeWhere( - (t) => - t.text.offset == token.text.offset && - t.text.length == token.text.length && - t.lemma.text.equals(token.lemma.text), - ); - }); + TokensUtil.clearNewTokenCache(event.eventId); + setState(() {}); } } @@ -579,14 +564,8 @@ class MessageOverlayController extends State return isSelected; } - bool isNewToken(PangeaToken token) { - if (newTokens.isEmpty) return false; - return newTokens.any( - (t) => - t.text.offset == token.text.offset && - t.text.length == token.text.length, - ); - } + bool isNewToken(PangeaToken token) => + TokensUtil.isNewToken(token, pangeaMessageEvent!); bool isTokenHighlighted(PangeaToken token) { if (_highlightedTokens == null) return false; diff --git a/lib/pangea/toolbar/widgets/stt_transcript_tokens.dart b/lib/pangea/toolbar/widgets/stt_transcript_tokens.dart index e63bcb7df..03247a35f 100644 --- a/lib/pangea/toolbar/widgets/stt_transcript_tokens.dart +++ b/lib/pangea/toolbar/widgets/stt_transcript_tokens.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:fluffychat/pangea/events/models/pangea_token_model.dart'; -import 'package:fluffychat/pangea/message_token_text/token_position_model.dart'; +import 'package:fluffychat/pangea/message_token_text/tokens_util.dart'; import 'package:fluffychat/pangea/toolbar/models/speech_to_text_models.dart'; class SttTranscriptTokens extends StatelessWidget {