diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 53f439316..930cd20f7 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -4017,7 +4017,7 @@ "conversationBotTextAdventureZone_title": "Text Adventure", "conversationBotTextAdventureZone_instructionLabel": "Game Master Instructions", "conversationBotTextAdventureZone_instructionPlaceholder": "Set game master instructions", - "conversationBotCustomZone_instructionSystemPromptEmptyError": "Missing game master instructions", + "conversationBotCustomZone_instructionSystemPromptEmptyError": "Missing game master instructions", "studentAnalyticsNotAvailable": "Student data not currently available", "roomDataMissing": "Some data may be missing from rooms in which you are not a member.", "updatePhoneOS": "You may need to update your device's OS version.", @@ -4125,5 +4125,6 @@ "wordsUsed": "Words Used", "errorTypes": "Error Types", "level": "Level", - "canceledSend": "Canceled send" + "canceledSend": "Canceled send", + "morphsUsed": "Morphs Used" } \ No newline at end of file diff --git a/lib/pages/chat/chat_view.dart b/lib/pages/chat/chat_view.dart index 4a7d288d8..ea48c202d 100644 --- a/lib/pages/chat/chat_view.dart +++ b/lib/pages/chat/chat_view.dart @@ -470,7 +470,7 @@ class ChatView extends StatelessWidget { Row( children: [ const PointsGainedAnimation( - color: Colors.blue, + gainColor: Colors.blue, ), ChatFloatingActionButton( controller: controller, diff --git a/lib/pangea/choreographer/controllers/choreographer.dart b/lib/pangea/choreographer/controllers/choreographer.dart index bafd1bbef..1a11de0e3 100644 --- a/lib/pangea/choreographer/controllers/choreographer.dart +++ b/lib/pangea/choreographer/controllers/choreographer.dart @@ -411,7 +411,6 @@ class Choreographer { choreoRecord = ChoreoRecord.newRecord; itController.clear(); igc.clear(); - pangeaController.myAnalytics.clearDraftConstructUses(roomId); // errorService.clear(); _resetDebounceTimer(); } diff --git a/lib/pangea/choreographer/controllers/it_controller.dart b/lib/pangea/choreographer/controllers/it_controller.dart index 3187d4a6a..0067eee86 100644 --- a/lib/pangea/choreographer/controllers/it_controller.dart +++ b/lib/pangea/choreographer/controllers/it_controller.dart @@ -3,6 +3,8 @@ import 'dart:developer'; import 'package:fluffychat/pangea/choreographer/controllers/error_service.dart'; import 'package:fluffychat/pangea/constants/choreo_constants.dart'; +import 'package:fluffychat/pangea/enum/construct_use_type_enum.dart'; +import 'package:fluffychat/pangea/models/pangea_token_model.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -285,6 +287,20 @@ class ITController { showChoiceFeedback = true; + // Get a list of the choices that the user did not click + final List? ignoredTokens = currentITStep?.continuances + .where((e) => !e.wasClicked) + .map((e) => e.tokens) + .expand((e) => e) + .toList(); + + // Save those choices' tokens to local construct analytics as ignored tokens + choreographer.pangeaController.myAnalytics.addDraftUses( + ignoredTokens ?? [], + choreographer.roomId, + ConstructUseTypeEnum.ignIt, + ); + Future.delayed( const Duration( milliseconds: ChoreoConstants.millisecondsToDisplayFeedback, diff --git a/lib/pangea/choreographer/widgets/it_bar.dart b/lib/pangea/choreographer/widgets/it_bar.dart index 536fd4610..5d47cce7e 100644 --- a/lib/pangea/choreographer/widgets/it_bar.dart +++ b/lib/pangea/choreographer/widgets/it_bar.dart @@ -7,7 +7,9 @@ import 'package:fluffychat/pangea/choreographer/widgets/it_bar_buttons.dart'; import 'package:fluffychat/pangea/choreographer/widgets/it_feedback_card.dart'; import 'package:fluffychat/pangea/choreographer/widgets/translation_finished_flow.dart'; import 'package:fluffychat/pangea/constants/choreo_constants.dart'; +import 'package:fluffychat/pangea/enum/construct_use_type_enum.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; +import 'package:fluffychat/pangea/widgets/animations/gain_points.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -76,7 +78,12 @@ class ITBarState extends State { width: double.infinity, padding: const EdgeInsets.fromLTRB(0, 3, 3, 3), child: Stack( + alignment: Alignment.topCenter, children: [ + const Positioned( + top: 60, + child: PointsGainedAnimation(), + ), SingleChildScrollView( child: Column( children: [ @@ -334,6 +341,15 @@ class ITChoices extends StatelessWidget { continuance.feedbackText(context), ); } + if (!continuance.wasClicked) { + controller.choreographer.pangeaController.myAnalytics.addDraftUses( + continuance.tokens, + controller.choreographer.roomId, + continuance.level > 1 + ? ConstructUseTypeEnum.incIt + : ConstructUseTypeEnum.corIt, + ); + } controller.currentITStep!.continuances[index].wasClicked = true; controller.choreographer.setState(); } diff --git a/lib/pangea/controllers/my_analytics_controller.dart b/lib/pangea/controllers/my_analytics_controller.dart index daefe1cd9..c24f3391b 100644 --- a/lib/pangea/controllers/my_analytics_controller.dart +++ b/lib/pangea/controllers/my_analytics_controller.dart @@ -112,7 +112,6 @@ class MyAnalyticsController extends BaseController { void onMessageSent(Map data) { // cancel the last timer that was set on message event and // reset it to fire after _minutesBeforeUpdate minutes - debugPrint("ONE MESSAGE SENT"); _updateTimer?.cancel(); _updateTimer = Timer(Duration(minutes: _minutesBeforeUpdate), () { debugPrint("timer fired, updating analytics"); @@ -138,12 +137,11 @@ class MyAnalyticsController extends BaseController { timeStamp: DateTime.now(), ); - final List constructs = []; + final List constructs = getDraftUses(roomID); if (eventType == EventTypes.Message) { final grammarConstructs = choreo?.grammarConstructUses(metadata: metadata); - final itConstructs = choreo?.itStepsToConstructUses(metadata: metadata); final vocabUses = tokensSent != null ? originalSent?.vocabUses( choreo: choreo, @@ -153,7 +151,6 @@ class MyAnalyticsController extends BaseController { : null; constructs.addAll([ ...(grammarConstructs ?? []), - ...(itConstructs ?? []), ...(vocabUses ?? []), ]); } @@ -171,35 +168,18 @@ class MyAnalyticsController extends BaseController { .filterConstructs(unfilteredConstructs: constructs) .then((filtered) { if (filtered.isEmpty) return; + filtered.addAll(getDraftUses(roomID)); final level = _pangeaController.analytics.level; addLocalMessage(eventID, filtered).then( - (_) => afterAddLocalMessages(level), + (_) { + clearDraftUses(roomID); + afterAddLocalMessages(level); + }, ); }); } - /// Called when the user selects a replacement during IGC - void onReplacementSelected( - List tokens, - String roomID, - bool isBestCorrection, - ) { - final useType = isBestCorrection - ? ConstructUseTypeEnum.corIGC - : ConstructUseTypeEnum.incIGC; - setDraftConstructUses(tokens, roomID, useType); - } - - /// Called when the user ignores a match during IGC - void onIgnoreMatch( - List tokens, - String roomID, - ) { - const useType = ConstructUseTypeEnum.ignIGC; - setDraftConstructUses(tokens, roomID, useType); - } - - void setDraftConstructUses( + void addDraftUses( List tokens, String roomID, ConstructUseTypeEnum useType, @@ -220,19 +200,41 @@ class MyAnalyticsController extends BaseController { ), ) .toList(); - addLocalMessage('draft$roomID', uses); + + final List morphs = tokens + .map((t) => t.morph.values) + .expand((m) => m) + .cast() + .toList(); + + uses.addAll( + morphs.map( + (morph) => OneConstructUse( + useType: useType, + lemma: morph, + constructType: ConstructTypeEnum.morph, + metadata: metadata, + ), + ), + ); + + final level = _pangeaController.analytics.level; + addLocalMessage('draft$roomID', uses).then( + (_) => afterAddLocalMessages(level), + ); } - void clearDraftConstructUses(String roomID) { + List getDraftUses(String roomID) { + final currentCache = _pangeaController.analytics.messagesSinceUpdate; + return currentCache['draft$roomID'] ?? []; + } + + void clearDraftUses(String roomID) { final currentCache = _pangeaController.analytics.messagesSinceUpdate; currentCache.remove('draft$roomID'); setMessagesSinceUpdate(currentCache); } - /// Called when the user selects a continuance during IT - /// TODO implement - void onSelectContinuance() {} - /// Add a list of construct uses for a new message to the local /// cache of recently sent messages Future addLocalMessage( diff --git a/lib/pangea/enum/construct_type_enum.dart b/lib/pangea/enum/construct_type_enum.dart index 7db7f9cd5..86c715fb6 100644 --- a/lib/pangea/enum/construct_type_enum.dart +++ b/lib/pangea/enum/construct_type_enum.dart @@ -1,6 +1,7 @@ enum ConstructTypeEnum { grammar, vocab, + morph, } extension ConstructExtension on ConstructTypeEnum { @@ -10,6 +11,8 @@ extension ConstructExtension on ConstructTypeEnum { return 'grammar'; case ConstructTypeEnum.vocab: return 'vocab'; + case ConstructTypeEnum.morph: + return 'morph'; } } } @@ -23,6 +26,9 @@ class ConstructTypeUtil { case 'v': case 'vocab': return ConstructTypeEnum.vocab; + case 'm': + case 'morph': + return ConstructTypeEnum.morph; default: return ConstructTypeEnum.vocab; } diff --git a/lib/pangea/enum/progress_indicators_enum.dart b/lib/pangea/enum/progress_indicators_enum.dart index 39032433e..aa38230e2 100644 --- a/lib/pangea/enum/progress_indicators_enum.dart +++ b/lib/pangea/enum/progress_indicators_enum.dart @@ -1,10 +1,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:material_symbols_icons/symbols.dart'; enum ProgressIndicatorEnum { level, wordsUsed, errorTypes, + morphsUsed, } extension ProgressIndicatorsExtension on ProgressIndicatorEnum { @@ -14,6 +16,8 @@ extension ProgressIndicatorsExtension on ProgressIndicatorEnum { return Icons.text_fields_outlined; case ProgressIndicatorEnum.errorTypes: return Icons.error_outline; + case ProgressIndicatorEnum.morphsUsed: + return Symbols.toys_and_games; case ProgressIndicatorEnum.level: return Icons.star; } @@ -36,8 +40,10 @@ extension ProgressIndicatorsExtension on ProgressIndicatorEnum { return Theme.of(context).brightness == Brightness.dark ? const Color.fromARGB(255, 250, 220, 129) : const Color.fromARGB(255, 255, 208, 67); - default: - return Theme.of(context).textTheme.bodyLarge!.color ?? Colors.blueGrey; + case ProgressIndicatorEnum.morphsUsed: + return Theme.of(context).brightness == Brightness.dark + ? const Color.fromARGB(255, 169, 183, 237) + : const Color.fromARGB(255, 38, 59, 141); } } @@ -49,6 +55,8 @@ extension ProgressIndicatorsExtension on ProgressIndicatorEnum { return L10n.of(context)!.errorTypes; case ProgressIndicatorEnum.level: return L10n.of(context)!.level; + case ProgressIndicatorEnum.morphsUsed: + return L10n.of(context)!.morphsUsed; } } } diff --git a/lib/pangea/matrix_event_wrappers/pangea_message_event.dart b/lib/pangea/matrix_event_wrappers/pangea_message_event.dart index 06359a24b..bfa7564cf 100644 --- a/lib/pangea/matrix_event_wrappers/pangea_message_event.dart +++ b/lib/pangea/matrix_event_wrappers/pangea_message_event.dart @@ -665,12 +665,10 @@ class PangeaMessageEvent { l2Code == null ? [] : practiceActivitiesByLangCode(l2Code!); /// all construct uses for the message, including vocab and grammar - List get allConstructUses => - [..._grammarConstructUses, ..._vocabUses, ..._itStepsToConstructUses]; - - /// Returns a list of [OneConstructUse] from itSteps - List get _itStepsToConstructUses => - originalSent?.choreo?.itStepsToConstructUses(event: event) ?? []; + List get allConstructUses => [ + ..._grammarConstructUses, + ..._vocabUses, + ]; /// get construct uses of type vocab for the message List get _vocabUses { diff --git a/lib/pangea/models/analytics/constructs_model.dart b/lib/pangea/models/analytics/constructs_model.dart index 0e62419f0..e618c4307 100644 --- a/lib/pangea/models/analytics/constructs_model.dart +++ b/lib/pangea/models/analytics/constructs_model.dart @@ -82,9 +82,9 @@ class OneConstructUse { OneConstructUse({ required this.useType, required this.lemma, - required this.form, required this.constructType, required this.metadata, + this.form, this.id, }); diff --git a/lib/pangea/models/choreo_record.dart b/lib/pangea/models/choreo_record.dart index 6fdde333a..fe95dfc09 100644 --- a/lib/pangea/models/choreo_record.dart +++ b/lib/pangea/models/choreo_record.dart @@ -1,11 +1,9 @@ import 'dart:convert'; -import 'package:fluffychat/pangea/constants/choreo_constants.dart'; import 'package:fluffychat/pangea/enum/construct_type_enum.dart'; import 'package:fluffychat/pangea/enum/construct_use_type_enum.dart'; import 'package:fluffychat/pangea/models/analytics/constructs_model.dart'; import 'package:fluffychat/pangea/models/pangea_match_model.dart'; -import 'package:fluffychat/pangea/models/pangea_token_model.dart'; import 'package:matrix/matrix.dart'; import 'it_step.dart'; @@ -155,65 +153,6 @@ class ChoreoRecord { } return uses; } - - /// Returns a list of [OneConstructUse] from itSteps for which the continuance - /// was selected or ignored. Correct selections are considered in the tokens - /// flow. Once all continuances have lemmas, we can do both correct and incorrect - /// in this flow. It actually doesn't do anything at all right now, because the - /// choregrapher is not returning lemmas for continuances. This is a TODO. - /// So currently only the lemmas can be gotten from the tokens for choices that - /// are actually in the final message. - List itStepsToConstructUses({ - Event? event, - ConstructUseMetaData? metadata, - }) { - final List uses = []; - if (event == null && metadata == null) { - return uses; - } - - metadata ??= ConstructUseMetaData( - roomId: event!.roomId!, - eventId: event.eventId, - timeStamp: event.originServerTs, - ); - - for (final itStep in itSteps) { - for (final continuance in itStep.continuances) { - final List tokensToSave = - continuance.tokens.where((t) => t.lemma.saveVocab).toList(); - - if (finalMessage.contains(continuance.text)) { - continue; - } - if (continuance.wasClicked) { - //PTODO - account for end of flow score - if (continuance.level != ChoreoConstants.levelThresholdForGreen) { - for (final token in tokensToSave) { - uses.add( - token.lemma.toVocabUse( - ConstructUseTypeEnum.incIt, - metadata, - ), - ); - } - } - } else { - if (continuance.level != ChoreoConstants.levelThresholdForGreen) { - for (final token in tokensToSave) { - uses.add( - token.lemma.toVocabUse( - ConstructUseTypeEnum.ignIt, - metadata, - ), - ); - } - } - } - } - } - return uses; - } } /// A new ChoreoRecordStep is saved in the following cases: diff --git a/lib/pangea/models/representation_content_model.dart b/lib/pangea/models/representation_content_model.dart index 3600267f3..be68c1195 100644 --- a/lib/pangea/models/representation_content_model.dart +++ b/lib/pangea/models/representation_content_model.dart @@ -1,3 +1,4 @@ +import 'package:fluffychat/pangea/enum/construct_type_enum.dart'; import 'package:fluffychat/pangea/enum/construct_use_type_enum.dart'; import 'package:fluffychat/pangea/models/analytics/constructs_model.dart'; import 'package:fluffychat/pangea/models/choreo_record.dart'; @@ -118,8 +119,8 @@ class PangeaRepresentation { final tokensToSave = tokens.where((token) => token.lemma.saveVocab).toList(); for (final token in tokensToSave) { - uses.add( - getVocabUseForToken( + uses.addAll( + getUsesForToken( token, metadata, choreo: choreo, @@ -137,21 +138,38 @@ class PangeaRepresentation { /// If the [token] is in the [choreo.acceptedOrIgnoredMatch], it is considered to be a [ConstructUseTypeEnum.ga]. /// If the [token] is in the [choreo.acceptedOrIgnoredMatch.choices], it is considered to be a [ConstructUseTypeEnum.corIt]. /// If the [token] is not included in any choreoStep, it is considered to be a [ConstructUseTypeEnum.wa]. - OneConstructUse getVocabUseForToken( + List getUsesForToken( PangeaToken token, ConstructUseMetaData metadata, { ChoreoRecord? choreo, }) { + final List uses = []; final lemma = token.lemma; final content = token.text.content; + final morphs = token.morph.values.toList(); if (choreo == null) { final bool inUserL2 = langCode == MatrixState.pangeaController.languageController.activeL2Code(); - return lemma.toVocabUse( - inUserL2 ? ConstructUseTypeEnum.wa : ConstructUseTypeEnum.unk, - metadata, + final useType = + inUserL2 ? ConstructUseTypeEnum.wa : ConstructUseTypeEnum.unk; + uses.addAll( + morphs.map( + (morph) => OneConstructUse( + useType: useType, + lemma: morph, + constructType: ConstructTypeEnum.morph, + metadata: metadata, + ), + ), ); + uses.add( + lemma.toVocabUse( + inUserL2 ? ConstructUseTypeEnum.wa : ConstructUseTypeEnum.unk, + metadata, + ), + ); + return uses; } for (final step in choreo.choreoSteps) { @@ -174,10 +192,7 @@ class PangeaRepresentation { step.text.contains(choice.value), ); if (stepContainedToken) { - return lemma.toVocabUse( - ConstructUseTypeEnum.ga, - metadata, - ); + return []; } } @@ -185,16 +200,27 @@ class PangeaRepresentation { final bool pickedThroughIT = step.itStep!.chosenContinuance!.text.contains(content); if (pickedThroughIT) { - return lemma.toVocabUse( - ConstructUseTypeEnum.corIt, - metadata, - ); + return []; } } } - return lemma.toVocabUse( - ConstructUseTypeEnum.wa, - metadata, + + uses.addAll( + morphs.map( + (morph) => OneConstructUse( + useType: ConstructUseTypeEnum.wa, + lemma: morph, + constructType: ConstructTypeEnum.morph, + metadata: metadata, + ), + ), ); + uses.add( + lemma.toVocabUse( + ConstructUseTypeEnum.wa, + metadata, + ), + ); + return uses; } } diff --git a/lib/pangea/widgets/animations/gain_points.dart b/lib/pangea/widgets/animations/gain_points.dart index dfbee2931..13ae324e7 100644 --- a/lib/pangea/widgets/animations/gain_points.dart +++ b/lib/pangea/widgets/animations/gain_points.dart @@ -6,8 +6,13 @@ import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; class PointsGainedAnimation extends StatefulWidget { - final Color? color; - const PointsGainedAnimation({super.key, this.color}); + final Color? gainColor; + final Color? loseColor; + const PointsGainedAnimation({ + super.key, + this.gainColor, + this.loseColor = Colors.red, + }); @override PointsGainedAnimationState createState() => PointsGainedAnimationState(); @@ -66,7 +71,7 @@ class PointsGainedAnimationState extends State void _showPointsGained(List constructs) { setState(() => _addedPoints = (_currentXP ?? 0) - (_prevXP ?? 0)); - if (_prevXP != _currentXP && !_controller.isAnimating) { + if (_prevXP != _currentXP) { _controller.reset(); _controller.forward(); } @@ -82,18 +87,20 @@ class PointsGainedAnimationState extends State Widget build(BuildContext context) { if (!animate) return const SizedBox(); + final textColor = _addedPoints! > 0 ? widget.gainColor : widget.loseColor; + return SlideTransition( position: _offsetAnimation, child: FadeTransition( opacity: _fadeAnimation, child: Text( - '+$_addedPoints', + '${_addedPoints! > 0 ? '+' : ''}$_addedPoints', style: BotStyle.text( context, big: true, - setColor: widget.color == null, + setColor: textColor == null, existingStyle: TextStyle( - color: widget.color, + color: textColor, ), ), ), diff --git a/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart b/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart index bf8bf2f96..f74217eb9 100644 --- a/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart +++ b/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart @@ -45,6 +45,9 @@ class LearningProgressIndicatorsState /// Grammar constructs model ConstructListModel? errors; + /// Morph constructs model + ConstructListModel? morphs; + bool loading = true; int get serverXP => _pangeaController.analytics.serverXP; @@ -78,6 +81,10 @@ class LearningProgressIndicatorsState type: ConstructTypeEnum.grammar, uses: constructs, ); + morphs = ConstructListModel( + type: ConstructTypeEnum.morph, + uses: constructs, + ); if (loading) loading = false; if (mounted) setState(() {}); @@ -90,6 +97,8 @@ class LearningProgressIndicatorsState return words?.lemmas.length; case ProgressIndicatorEnum.errorTypes: return errors?.lemmas.length; + case ProgressIndicatorEnum.morphsUsed: + return morphs?.lemmas.length; case ProgressIndicatorEnum.level: return level; } diff --git a/lib/pangea/widgets/igc/span_card.dart b/lib/pangea/widgets/igc/span_card.dart index a431f5666..5ddf1b0c5 100644 --- a/lib/pangea/widgets/igc/span_card.dart +++ b/lib/pangea/widgets/igc/span_card.dart @@ -1,11 +1,14 @@ import 'dart:developer'; import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/pangea/enum/construct_use_type_enum.dart'; import 'package:fluffychat/pangea/enum/span_data_type.dart'; +import 'package:fluffychat/pangea/models/pangea_token_model.dart'; import 'package:fluffychat/pangea/models/span_data.dart'; import 'package:fluffychat/pangea/utils/bot_style.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:fluffychat/pangea/utils/match_copy.dart'; +import 'package:fluffychat/pangea/widgets/animations/gain_points.dart'; import 'package:fluffychat/pangea/widgets/igc/card_error_widget.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -120,6 +123,16 @@ class SpanCardState extends State { Future onChoiceSelect(int index) async { selectedChoiceIndex = index; if (selectedChoice != null) { + if (!selectedChoice!.selected) { + MatrixState.pangeaController.myAnalytics.addDraftUses( + selectedChoice!.tokens, + widget.roomId, + selectedChoice!.isBestCorrection + ? ConstructUseTypeEnum.corIGC + : ConstructUseTypeEnum.incIGC, + ); + } + selectedChoice!.timestamp = DateTime.now(); selectedChoice!.selected = true; setState( @@ -130,41 +143,44 @@ class SpanCardState extends State { } } - void onReplaceSelected() { - if (selectedChoice != null) { - final tokens = widget.scm.choreographer.igc.igcTextData - ?.matchTokens(widget.scm.matchIndex) ?? - []; - MatrixState.pangeaController.myAnalytics.onReplacementSelected( - tokens, - widget.roomId, - selectedChoice!.isBestCorrection, - ); - } + /// Returns the list of choices that are not selected + List? get ignoredMatches => widget.scm.pangeaMatch?.match.choices + ?.where((choice) => !choice.selected) + .toList(); + /// Returns the list of tokens from choices that are not selected + List? get ignoredTokens => ignoredMatches + ?.expand((choice) => choice.tokens) + .toList() + .cast(); + + /// Adds the ignored tokens to locally cached analytics + void addIgnoredTokenUses() { + MatrixState.pangeaController.myAnalytics.addDraftUses( + ignoredTokens ?? [], + widget.roomId, + ConstructUseTypeEnum.ignIGC, + ); + } + + void onReplaceSelected() { + addIgnoredTokenUses(); widget.scm .onReplacementSelect( - matchIndex: widget.scm.matchIndex, - choiceIndex: selectedChoiceIndex!, - ) - .then((value) { - setState(() {}); - }); + matchIndex: widget.scm.matchIndex, + choiceIndex: selectedChoiceIndex!, + ) + .then((value) => setState(() {})); } void onIgnoreMatch() { MatrixState.pAnyState.closeOverlay(); + addIgnoredTokenUses(); + Future.delayed( Duration.zero, () { widget.scm.onIgnore(); - final tokens = widget.scm.choreographer.igc.igcTextData - ?.matchTokens(widget.scm.matchIndex) ?? - []; - MatrixState.pangeaController.myAnalytics.onIgnoreMatch( - tokens, - widget.roomId, - ); }, ); } @@ -205,142 +221,153 @@ class WordMatchContent extends StatelessWidget { final ScrollController scrollController = ScrollController(); try { - return Column( + return Stack( + alignment: Alignment.topCenter, children: [ - // if (!controller.widget.scm.pangeaMatch!.isITStart) - CardHeader( - text: controller.error?.toString() ?? matchCopy.title, - botExpression: controller.error == null - ? controller.currentExpression - : BotExpression.addled, + const Positioned( + top: 40, + child: PointsGainedAnimation(), ), - Expanded( - child: Scrollbar( - controller: scrollController, - thumbVisibility: true, - child: SingleChildScrollView( - controller: scrollController, - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - // const SizedBox(height: 10.0), - // if (matchCopy.description != null) - // Padding( - // padding: const EdgeInsets.only(), - // child: Text( - // matchCopy.description!, - // style: BotStyle.text(context), - // ), - // ), - const SizedBox(height: 8), - if (!controller.widget.scm.pangeaMatch!.isITStart) - ChoicesArray( - originalSpan: - controller.widget.scm.pangeaMatch!.matchContent, - isLoading: controller.fetchingData, - choices: - controller.widget.scm.pangeaMatch!.match.choices - ?.map( - (e) => Choice( - text: e.value, - color: e.selected ? e.type.color : null, - isGold: e.type.name == 'bestCorrection', - ), - ) - .toList(), - onPressed: controller.onChoiceSelect, - uniqueKeyForLayerLink: (int index) => "wordMatch$index", - selectedChoiceIndex: controller.selectedChoiceIndex, - ), - const SizedBox(height: 12), - PromptAndFeedback(controller: controller), - ], - ), - ), - ), - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + Column( children: [ - const SizedBox(width: 10), + // if (!controller.widget.scm.pangeaMatch!.isITStart) + CardHeader( + text: controller.error?.toString() ?? matchCopy.title, + botExpression: controller.error == null + ? controller.currentExpression + : BotExpression.addled, + ), Expanded( - child: Opacity( - opacity: 0.8, - child: TextButton( - style: ButtonStyle( - backgroundColor: WidgetStateProperty.all( - AppConfig.primaryColor.withOpacity(0.1), - ), - ), - onPressed: controller.onIgnoreMatch, - child: Center( - child: Text(L10n.of(context)!.ignoreInThisText), + child: Scrollbar( + controller: scrollController, + thumbVisibility: true, + child: SingleChildScrollView( + controller: scrollController, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + // const SizedBox(height: 10.0), + // if (matchCopy.description != null) + // Padding( + // padding: const EdgeInsets.only(), + // child: Text( + // matchCopy.description!, + // style: BotStyle.text(context), + // ), + // ), + const SizedBox(height: 8), + if (!controller.widget.scm.pangeaMatch!.isITStart) + ChoicesArray( + originalSpan: + controller.widget.scm.pangeaMatch!.matchContent, + isLoading: controller.fetchingData, + choices: + controller.widget.scm.pangeaMatch!.match.choices + ?.map( + (e) => Choice( + text: e.value, + color: e.selected ? e.type.color : null, + isGold: e.type.name == 'bestCorrection', + ), + ) + .toList(), + onPressed: controller.onChoiceSelect, + uniqueKeyForLayerLink: (int index) => + "wordMatch$index", + selectedChoiceIndex: controller.selectedChoiceIndex, + ), + const SizedBox(height: 12), + PromptAndFeedback(controller: controller), + ], ), ), ), ), - const SizedBox(width: 10), - if (!controller.widget.scm.pangeaMatch!.isITStart) - Expanded( - child: Opacity( - opacity: controller.selectedChoiceIndex != null ? 1.0 : 0.5, - child: TextButton( - onPressed: controller.selectedChoiceIndex != null - ? controller.onReplaceSelected - : null, - style: ButtonStyle( - backgroundColor: WidgetStateProperty.all( - (controller.selectedChoice != null - ? controller.selectedChoice!.color - : AppConfig.primaryColor) - .withOpacity(0.2), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const SizedBox(width: 10), + Expanded( + child: Opacity( + opacity: 0.8, + child: TextButton( + style: ButtonStyle( + backgroundColor: WidgetStateProperty.all( + AppConfig.primaryColor.withOpacity(0.1), + ), + ), + onPressed: controller.onIgnoreMatch, + child: Center( + child: Text(L10n.of(context)!.ignoreInThisText), ), - // Outline if Replace button enabled - side: controller.selectedChoice != null - ? WidgetStateProperty.all( - BorderSide( - color: controller.selectedChoice!.color, - style: BorderStyle.solid, - width: 2.0, - ), - ) - : null, ), - child: Text(L10n.of(context)!.replace), ), ), - ), - const SizedBox(width: 10), + const SizedBox(width: 10), + if (!controller.widget.scm.pangeaMatch!.isITStart) + Expanded( + child: Opacity( + opacity: + controller.selectedChoiceIndex != null ? 1.0 : 0.5, + child: TextButton( + onPressed: controller.selectedChoiceIndex != null + ? controller.onReplaceSelected + : null, + style: ButtonStyle( + backgroundColor: WidgetStateProperty.all( + (controller.selectedChoice != null + ? controller.selectedChoice!.color + : AppConfig.primaryColor) + .withOpacity(0.2), + ), + // Outline if Replace button enabled + side: controller.selectedChoice != null + ? WidgetStateProperty.all( + BorderSide( + color: controller.selectedChoice!.color, + style: BorderStyle.solid, + width: 2.0, + ), + ) + : null, + ), + child: Text(L10n.of(context)!.replace), + ), + ), + ), + const SizedBox(width: 10), + if (controller.widget.scm.pangeaMatch!.isITStart) + Expanded( + child: TextButton( + onPressed: () { + MatrixState.pAnyState.closeOverlay(); + Future.delayed( + Duration.zero, + () => controller.widget.scm.onITStart(), + ); + }, + style: ButtonStyle( + backgroundColor: WidgetStateProperty.all( + (AppConfig.primaryColor).withOpacity(0.1), + ), + ), + child: Text(L10n.of(context)!.helpMeTranslate), + ), + ), + ], + ), if (controller.widget.scm.pangeaMatch!.isITStart) - Expanded( - child: TextButton( - onPressed: () { - MatrixState.pAnyState.closeOverlay(); - Future.delayed( - Duration.zero, - () => controller.widget.scm.onITStart(), - ); - }, - style: ButtonStyle( - backgroundColor: WidgetStateProperty.all( - (AppConfig.primaryColor).withOpacity(0.1), - ), - ), - child: Text(L10n.of(context)!.helpMeTranslate), - ), + DontShowSwitchListTile( + controller: pangeaController, + onSwitch: (bool value) { + pangeaController.userController.updateProfile((profile) { + profile.userSettings.itAutoPlay = value; + return profile; + }); + }, ), ], ), - if (controller.widget.scm.pangeaMatch!.isITStart) - DontShowSwitchListTile( - controller: pangeaController, - onSwitch: (bool value) { - pangeaController.userController.updateProfile((profile) { - profile.userSettings.itAutoPlay = value; - return profile; - }); - }, - ), ], ); } on Exception catch (e) {