Skip to content

Commit

Permalink
tweaking selection criteria
Browse files Browse the repository at this point in the history
  • Loading branch information
wcjord committed Nov 15, 2024
1 parent 867c55b commit 1a4dc0b
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 50 deletions.
2 changes: 1 addition & 1 deletion lib/pages/chat/chat.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1694,7 +1694,7 @@ class ChatController extends State<ChatPageWithRoom>
chatController: this,
event: pangeaMessageEvent.event,
pangeaMessageEvent: pangeaMessageEvent,
selectedTokenOnInitialization: selectedToken,
initialSelectedToken: selectedToken,
nextEvent: nextEvent,
prevEvent: prevEvent,
);
Expand Down
16 changes: 14 additions & 2 deletions lib/pangea/controllers/message_analytics_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,14 @@ class TargetTokensAndActivityType {
// This is kind of complicated
// if it's causing problems,
// maybe we just verify that the target span of the activity is the same as the target span of the target?
final List<ConstructIdentifier> allTokenConstructs = tokens
final List<ConstructIdentifier> relevantConstructs = tokens
.map((t) => t.constructs)
.expand((e) => e)
.map((c) => c.id)
.where(activityType.constructFilter)
.toList();

return listEquals(activity.tgtConstructs, allTokenConstructs);
return listEquals(activity.tgtConstructs, relevantConstructs);
}

@override
Expand Down Expand Up @@ -74,6 +74,8 @@ class MessageAnalyticsEntry {
TargetTokensAndActivityType? get nextActivity =>
_activityQueue.isNotEmpty ? _activityQueue.first : null;

/// If there are more than 4 tokens that can be heard, we don't want to do word focus listening
/// Otherwise, we don't have enough distractors
bool get canDoWordFocusListening =>
_tokens.where((t) => t.canBeHeard).length > 4;

Expand Down Expand Up @@ -125,6 +127,16 @@ class MessageAnalyticsEntry {
return queue.take(3).toList();
}

/// Removes the last activity from the queue
/// This should only used when there is a startingToken in practice flow
/// and we want to go down to 2 activities + the activity with the startingToken
void goDownTo2Activities() {
if (_activityQueue.isNotEmpty && _activityQueue.length > 2) {
_activityQueue.removeLast();
}
}

/// Returns a hidden word activity if there is a sequence of tokens that have hiddenWordListening in their eligibleActivityTypes
TargetTokensAndActivityType? getHiddenWordActivity(int numOtherActivities) {
// don't do hidden word listening on own messages
if (!_includeHiddenWordActivities) {
Expand Down
40 changes: 19 additions & 21 deletions lib/pangea/models/pangea_token_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,8 @@ class PangeaToken {
/// alias for the end of the token ie offset + length
int get end => text.offset + text.length;

bool get isContentWord => ["NOUN", "VERB", "ADJ", "ADV"].contains(pos);
bool get isContentWord =>
["NOUN", "VERB", "ADJ", "ADV", "AUX", "PRON"].contains(pos);

bool get canBeHeard => [
"ADJ",
Expand Down Expand Up @@ -224,36 +225,31 @@ class PangeaToken {
}
}

bool isActivityProbablyLevelAppropriate(ActivityTypeEnum a) {
bool _isActivityProbablyLevelAppropriate(ActivityTypeEnum a) {
switch (a) {
case ActivityTypeEnum.wordMeaning:
return vocabConstruct.points < 15;
return vocabConstruct.points < 15 || daysSinceLastUseByType(a) > 2;
case ActivityTypeEnum.wordFocusListening:
return !_didActivitySuccessfully(a);
return !_didActivitySuccessfully(a) || daysSinceLastUseByType(a) > 2;
case ActivityTypeEnum.hiddenWordListening:
return true;
return daysSinceLastUseByType(a) > 2;
}
}

bool shouldDoActivity(ActivityTypeEnum a) {
final bool notEmpty = text.content.trim().isNotEmpty;
final bool isEligible = _isActivityBasicallyEligible(a);
final bool isProbablyLevelAppropriate =
isActivityProbablyLevelAppropriate(a);

return notEmpty && isEligible && isProbablyLevelAppropriate;
}
bool shouldDoActivity(ActivityTypeEnum a) =>
lemma.saveVocab &&
_isActivityBasicallyEligible(a) &&
_isActivityProbablyLevelAppropriate(a);

List<ActivityTypeEnum> get eligibleActivityTypes {
final List<ActivityTypeEnum> eligibleActivityTypes = [];

if (!lemma.saveVocab || daysSinceLastUse < 1) {
if (!lemma.saveVocab) {
return eligibleActivityTypes;
}

for (final type in ActivityTypeEnum.values) {
if (_isActivityBasicallyEligible(type) &&
!_didActivitySuccessfully(type)) {
if (shouldDoActivity(type)) {
eligibleActivityTypes.add(type);
}
}
Expand Down Expand Up @@ -283,8 +279,9 @@ class PangeaToken {
);
}

///
DateTime? get lastUsed => constructs.fold<DateTime?>(
/// lastUsed by activity type
DateTime? _lastUsedByActivityType(ActivityTypeEnum a) =>
constructs.where((c) => a.constructFilter(c.id)).fold<DateTime?>(
null,
(previousValue, element) {
if (previousValue == null) return element.lastUsed;
Expand All @@ -295,10 +292,11 @@ class PangeaToken {
},
);

/// daysSinceLastUse
int get daysSinceLastUse {
/// daysSinceLastUse by activity type
int daysSinceLastUseByType(ActivityTypeEnum a) {
final lastUsed = _lastUsedByActivityType(a);
if (lastUsed == null) return 1000;
return DateTime.now().difference(lastUsed!).inDays;
return DateTime.now().difference(lastUsed).inDays;
}

List<ConstructIdentifier> get _constructIDs {
Expand Down
25 changes: 17 additions & 8 deletions lib/pangea/widgets/chat/message_selection_overlay.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,11 @@ class MessageSelectionOverlay extends StatefulWidget {
required this.chatController,
required Event event,
required PangeaMessageEvent pangeaMessageEvent,
required PangeaToken? selectedTokenOnInitialization,
required PangeaToken? initialSelectedToken,
required Event? nextEvent,
required Event? prevEvent,
super.key,
}) : _initialSelectedToken = selectedTokenOnInitialization,
}) : _initialSelectedToken = initialSelectedToken,
_pangeaMessageEvent = pangeaMessageEvent,
_nextEvent = nextEvent,
_prevEvent = prevEvent,
Expand Down Expand Up @@ -78,19 +78,25 @@ class MessageOverlayController extends State<MessageSelectionOverlay>

bool get showToolbarButtons => !widget._pangeaMessageEvent.isAudioMessage;

/// Decides whether an _initialSelectedToken should be used
/// for a first practice activity on the word meaning
PangeaToken? get selectedTargetTokenForWordMeaning {
// if there is no initial selected token, then we don't need to do anything
if (widget._initialSelectedToken == null || messageAnalyticsEntry == null) {
return null;
}

final isInActivity = messageAnalyticsEntry!.isTokenInHiddenWordActivity(
// 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 isInActivity && shouldDoActivity
return !isInHiddenWordActivity && shouldDoActivity
? widget._initialSelectedToken
: null;
}
Expand All @@ -106,6 +112,13 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
const Duration(milliseconds: AppConfig.overlayAnimationDuration),
);

debugPrint(
"selected token: ${widget._initialSelectedToken?.toJson()} 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(", ")}",
);

_getTokens();

activitiesLeftToComplete = activitiesLeftToComplete -
Expand Down Expand Up @@ -216,10 +229,6 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
}

Future<void> _setInitialToolbarModeAndSelectedSpan() async {
debugPrint(
"setting initial toolbar mode and selected span with tokens $tokens",
);

if (widget._pangeaMessageEvent.isAudioMessage) {
toolbarMode = MessageMode.speechToText;
return setState(() => toolbarMode = MessageMode.practiceActivity);
Expand Down
2 changes: 0 additions & 2 deletions lib/pangea/widgets/chat/message_toolbar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,6 @@ class MessageToolbar extends StatelessWidget {
);
}
return PracticeActivityCard(
selectedTargetTokenForWordMeaning:
overLayController.selectedTargetTokenForWordMeaning,
pangeaMessageEvent: pangeaMessageEvent,
overlayController: overLayController,
ttsController: ttsController,
Expand Down
39 changes: 23 additions & 16 deletions lib/pangea/widgets/practice_activity/practice_activity_card.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,12 @@ class PracticeActivityCard extends StatefulWidget {
final PangeaMessageEvent pangeaMessageEvent;
final MessageOverlayController overlayController;
final TtsController ttsController;
final PangeaToken? selectedTargetTokenForWordMeaning;

const PracticeActivityCard({
super.key,
required this.pangeaMessageEvent,
required this.overlayController,
required this.ttsController,
required this.selectedTargetTokenForWordMeaning,
});

@override
Expand All @@ -57,6 +55,9 @@ class PracticeActivityCardState extends State<PracticeActivityCard> {
List<PracticeActivityEvent> get practiceActivities =>
widget.pangeaMessageEvent.practiceActivities;

// if the user has selected a token, we're going to give them an activity on that token first
late PangeaToken? startingToken;

// Used to show an animation when the user completes an activity
// while simultaneously fetching a new activity and not showing the loading spinner
// until the appropriate time has passed to 'savor the joy'
Expand Down Expand Up @@ -96,17 +97,14 @@ class PracticeActivityCardState extends State<PracticeActivityCard> {
/// Get an existing activity if there is one.
/// If not, get a new activity from the server.
Future<void> initialize() async {
startingToken = widget.overlayController.selectedTargetTokenForWordMeaning;
_setPracticeActivity(
await _fetchActivity(
selectedTargetTokenForWordMeaning:
widget.selectedTargetTokenForWordMeaning,
),
await _fetchActivity(),
);
}

Future<PracticeActivityModel?> _fetchActivity({
ActivityQualityFeedback? activityFeedback,
PangeaToken? selectedTargetTokenForWordMeaning,
}) async {
// try {
debugPrint('Fetching activity');
Expand All @@ -125,13 +123,22 @@ class PracticeActivityCardState extends State<PracticeActivityCard> {
// if the user selected a token which is not already in a hidden word activity,
// we're going to give them an activity on that token first
// otherwise, we're going to give them an activity on the next token in the queue
final TargetTokensAndActivityType? nextActivitySpecs =
selectedTargetTokenForWordMeaning != null
? TargetTokensAndActivityType(
tokens: [selectedTargetTokenForWordMeaning],
activityType: ActivityTypeEnum.wordMeaning,
)
: widget.overlayController.messageAnalyticsEntry?.nextActivity;
TargetTokensAndActivityType? nextActivitySpecs;
if (startingToken != null) {
// if the user selected a token, we're going to give them an activity on that token first
nextActivitySpecs = TargetTokensAndActivityType(
tokens: [startingToken!],
activityType: ActivityTypeEnum.wordMeaning,
);
// clear the starting token so that the next activity is not based on it
startingToken = null;
// we want to go down to 2 activities + the activity with the startingToken
// so we remove the last activity from the queue if there's more than 2
widget.overlayController.messageAnalyticsEntry?.goDownTo2Activities();
} else {
nextActivitySpecs =
widget.overlayController.messageAnalyticsEntry?.nextActivity;
}

// the client is going to be choosing the next activity now
// if nothing is set then it must be done with practice
Expand All @@ -141,11 +148,11 @@ class PracticeActivityCardState extends State<PracticeActivityCard> {
return null;
}

// check if we already have an activity matching the specs
final existingActivity = practiceActivities.firstWhereOrNull(
(activity) =>
nextActivitySpecs.matchesActivity(activity.practiceActivity),
nextActivitySpecs!.matchesActivity(activity.practiceActivity),
);

if (existingActivity != null) {
debugPrint('found existing activity');
_updateFetchingActivity(false);
Expand Down

0 comments on commit 1a4dc0b

Please sign in to comment.