diff --git a/lib/pangea/controllers/message_analytics_controller.dart b/lib/pangea/controllers/message_analytics_controller.dart index aecc073a3..ca2b2fe3c 100644 --- a/lib/pangea/controllers/message_analytics_controller.dart +++ b/lib/pangea/controllers/message_analytics_controller.dart @@ -90,7 +90,9 @@ class MessageAnalyticsEntry { // get all the eligible activity types for the token // based on the context of the message final eligibleTypesBasedOnContext = token.eligibleActivityTypes + // we want to filter hidden word types from this part of the process .where((type) => type != ActivityTypeEnum.hiddenWordListening) + // there have to be at least 4 tokens in the message that can be heard for word focus listening .where( (type) => canDoWordFocusListening || diff --git a/lib/pangea/models/pangea_token_model.dart b/lib/pangea/models/pangea_token_model.dart index fcca93a0f..17115694a 100644 --- a/lib/pangea/models/pangea_token_model.dart +++ b/lib/pangea/models/pangea_token_model.dart @@ -138,7 +138,24 @@ class PangeaToken { int get end => text.offset + text.length; bool get isContentWord => - ["NOUN", "VERB", "ADJ", "ADV", "AUX", "PRON"].contains(pos) && + ["NOUN", "VERB", "ADJ", "ADV"].contains(pos) && lemma.saveVocab; + + bool get canBeDefined => + [ + "ADJ", + "ADP", + "ADV", + "AUX", + "CCONJ", + "DET", + "INTJ", + "NOUN", + "NUM", + "PRON", + "PROPN", + "SCONJ", + "VERB", + ].contains(pos) && lemma.saveVocab; bool get canBeHeard => @@ -175,7 +192,7 @@ class PangeaToken { bool _isActivityBasicallyEligible(ActivityTypeEnum a) { switch (a) { case ActivityTypeEnum.wordMeaning: - return isContentWord; + return canBeDefined; case ActivityTypeEnum.wordFocusListening: case ActivityTypeEnum.hiddenWordListening: return canBeHeard; @@ -231,19 +248,31 @@ class PangeaToken { bool _isActivityProbablyLevelAppropriate(ActivityTypeEnum a) { switch (a) { case ActivityTypeEnum.wordMeaning: - return vocabConstruct.points < 15 || daysSinceLastUseByType(a) > 2; + if (isContentWord) { + return vocabConstruct.points < 15 || daysSinceLastUseByType(a) > 2; + } else { + return vocabConstruct.points < 5 || daysSinceLastUseByType(a) > 7; + } case ActivityTypeEnum.wordFocusListening: - return !_didActivitySuccessfully(a) || daysSinceLastUseByType(a) > 2; + return !_didActivitySuccessfully(a) || daysSinceLastUseByType(a) > 30; case ActivityTypeEnum.hiddenWordListening: return daysSinceLastUseByType(a) > 2; } } + // maybe for every 5 points of xp for a particular activity, increment the days between uses by 2 bool shouldDoActivity(ActivityTypeEnum a) => lemma.saveVocab && _isActivityBasicallyEligible(a) && _isActivityProbablyLevelAppropriate(a); + /// we try to guess if the user is click on a token specifically or clicking on a message in general + /// if we think the word might be new for the learner, then we'll assume they're clicking on the word + bool get shouldDoWordMeaningOnClick => + lemma.saveVocab && + canBeDefined && + daysSinceLastUseByType(ActivityTypeEnum.wordMeaning) > 1; + List get eligibleActivityTypes { final List eligibleActivityTypes = []; diff --git a/lib/pangea/widgets/chat/message_selection_overlay.dart b/lib/pangea/widgets/chat/message_selection_overlay.dart index b38bf72ad..276ab1997 100644 --- a/lib/pangea/widgets/chat/message_selection_overlay.dart +++ b/lib/pangea/widgets/chat/message_selection_overlay.dart @@ -84,17 +84,27 @@ class MessageOverlayController extends State return null; } + debugPrint( + "selected token: ${widget._initialSelectedToken?.text.content} total_xp:${widget._initialSelectedToken?.xp} vocab_construct_xp: ${widget._initialSelectedToken?.vocabConstruct.points} daysSincelastUseInWordMeaning ${widget._initialSelectedToken?.daysSinceLastUseByType(ActivityTypeEnum.wordMeaning)}", + ); + debugPrint( + "${widget._initialSelectedToken?.vocabConstruct.uses.map((u) => "${u.useType} ${u.timeStamp}").join(", ")}", + ); + // should not already be involved in a hidden word activity final isInHiddenWordActivity = messageAnalyticsEntry!.isTokenInHiddenWordActivity( widget._initialSelectedToken!, ); - // whether the activity should generally be involved in an activity final shouldDoActivity = widget._initialSelectedToken! .shouldDoActivity(ActivityTypeEnum.wordMeaning); - return !isInHiddenWordActivity && shouldDoActivity + return !isInHiddenWordActivity && + widget._initialSelectedToken!.eligibleActivityTypes.isNotEmpty && + widget._initialSelectedToken! + .daysSinceLastUseByType(ActivityTypeEnum.wordMeaning) > + 1 ? widget._initialSelectedToken : null; } @@ -103,21 +113,8 @@ class MessageOverlayController extends State void initState() { super.initState(); - _getTokens(); + _initializeTokensAndMode(); _setupSubscriptions(); - - if (_selectedTargetTokenForWordMeaning != null) { - messageAnalyticsEntry?.addForWordMeaning( - _selectedTargetTokenForWordMeaning!, - ); - } - - debugPrint( - "selected token: ${widget._initialSelectedToken?.text.content} total_xp:${widget._initialSelectedToken?.xp} vocab_construct_xp: ${widget._initialSelectedToken?.vocabConstruct.points} daysSincelastUseInWordMeaning ${widget._initialSelectedToken?.daysSinceLastUseByType(ActivityTypeEnum.wordMeaning)}", - ); - debugPrint( - "${widget._initialSelectedToken?.vocabConstruct.uses.map((u) => "${u.useType} ${u.timeStamp}").join(", ")}", - ); } void _setupSubscriptions() { @@ -157,7 +154,7 @@ class MessageOverlayController extends State ) : null; - Future _getTokens() async { + Future _initializeTokensAndMode() async { try { final repEvent = pangeaMessageEvent.messageDisplayRepresentation; if (repEvent != null) { @@ -169,6 +166,11 @@ class MessageOverlayController extends State } catch (e, s) { ErrorHandler.logError(e: e, s: s); } finally { + if (_selectedTargetTokenForWordMeaning != null) { + messageAnalyticsEntry?.addForWordMeaning( + _selectedTargetTokenForWordMeaning!, + ); + } _setInitialToolbarMode(); initialized = true; if (mounted) setState(() {});