From 879a52c81f25e791ba62b7b7528c1f6dc8a71068 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Mon, 7 Oct 2024 14:48:14 -0400 Subject: [PATCH] responded to comment from Will in client code, added fix for mini analytics view level display --- .../controllers/get_analytics_controller.dart | 27 ++++++++++++-- .../controllers/my_analytics_controller.dart | 29 ++++++++------- .../models/analytics/constructs_model.dart | 4 +++ lib/pangea/models/choreo_record.dart | 1 - .../animations/progress_bar/level_bar.dart | 11 ++---- .../progress_bar/progress_bar_details.dart | 2 ++ .../chat/message_selection_overlay.dart | 1 - .../widgets/chat/message_toolbar_buttons.dart | 36 ++++++------------- .../learning_progress_indicators.dart | 28 +++++++++------ lib/pangea/widgets/igc/span_card.dart | 6 ++-- 10 files changed, 80 insertions(+), 65 deletions(-) diff --git a/lib/pangea/controllers/get_analytics_controller.dart b/lib/pangea/controllers/get_analytics_controller.dart index 9a6a934a1..03cb2d60b 100644 --- a/lib/pangea/controllers/get_analytics_controller.dart +++ b/lib/pangea/controllers/get_analytics_controller.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:math'; import 'package:fluffychat/pangea/constants/class_default_values.dart'; import 'package:fluffychat/pangea/constants/local.key.dart'; @@ -42,8 +43,30 @@ class GetAnalyticsController { /// Get the current level based on the number of xp points /// The formula is calculated from XP and modeled on RPG games - // int get level => 1 + sqrt((1 + 8 * currentXP / 100) / 2).floor(); - int get level => currentXP ~/ 10; + int get level => 1 + sqrt((1 + 8 * currentXP / 100) / 2).floor(); + + // the minimum XP required for a given level + double get minXPForLevel { + return 12.5 * (2 * pow(level - 1, 2) - 1); + } + + // the minimum XP required for the next level + double get minXPForNextLevel { + return 12.5 * (2 * pow(level, 2) - 1); + } + + // the progress within the current level as a percentage (0.0 to 1.0) + double get levelProgress { + final progress = + (currentXP - minXPForLevel) / (minXPForNextLevel - minXPForLevel); + return progress >= 0 ? progress : 0; + } + + double get serverLevelProgress { + final progress = + (serverXP - minXPForLevel) / (minXPForNextLevel - minXPForLevel); + return progress >= 0 ? progress : 0; + } void initialize() { _analyticsUpdateSubscription ??= _pangeaController diff --git a/lib/pangea/controllers/my_analytics_controller.dart b/lib/pangea/controllers/my_analytics_controller.dart index f8fae3457..77e6caf27 100644 --- a/lib/pangea/controllers/my_analytics_controller.dart +++ b/lib/pangea/controllers/my_analytics_controller.dart @@ -42,7 +42,7 @@ class MyAnalyticsController extends BaseController { final int _maxMessagesCached = 10; /// the number of minutes before an automatic update is triggered - final int _minutesBeforeUpdate = 2; + final int _minutesBeforeUpdate = 5; /// the time since the last update that will trigger an automatic update final Duration _timeSinceUpdate = const Duration(days: 1); @@ -120,9 +120,6 @@ class MyAnalyticsController extends BaseController { } if (filtered.isEmpty) return; - // @ggurdin - are we sure this isn't happening twice? it's also above - filtered.addAll(_getDraftUses(data.roomId)); - final level = _pangeaController.analytics.level; _addLocalMessage(eventID, filtered).then( @@ -179,8 +176,6 @@ class MyAnalyticsController extends BaseController { } } - // @ggurdin - if the point of draft uses is that we don't want to send them twice, - // then, if this is triggered here, couldn't that make a problem? final level = _pangeaController.analytics.level; _addLocalMessage('draft$roomID', uses).then( (_) => _decideWhetherToUpdateAnalyticsRoom(level), @@ -201,21 +196,20 @@ class MyAnalyticsController extends BaseController { /// Add a list of construct uses for a new message to the local /// cache of recently sent messages Future _addLocalMessage( - String eventID, - // @ggurdin - why is this an eventID and not a roomID? + String cacheKey, List constructs, ) async { try { final currentCache = _pangeaController.analytics.messagesSinceUpdate; - constructs.addAll(currentCache[eventID] ?? []); - currentCache[eventID] = constructs; + constructs.addAll(currentCache[cacheKey] ?? []); + currentCache[cacheKey] = constructs; await _setMessagesSinceUpdate(currentCache); } catch (e, s) { ErrorHandler.logError( e: PangeaWarningError("Failed to add message since update: $e"), s: s, - m: 'Failed to add message since update for eventId: $eventID', + m: 'Failed to add message since update for eventId: $cacheKey', ); } } @@ -248,7 +242,18 @@ class MyAnalyticsController extends BaseController { /// Clears the local cache of recently sent constructs. Called before updating analytics void clearMessagesSinceUpdate() { - _pangeaController.pStoreService.delete(PLocalKey.messagesSinceUpdate); + final localCache = _pangeaController.analytics.messagesSinceUpdate; + final draftKeys = localCache.keys.where((key) => key.startsWith('draft')); + if (draftKeys.isEmpty) { + _pangeaController.pStoreService.delete(PLocalKey.messagesSinceUpdate); + return; + } + + final Map> newCache = {}; + for (final key in draftKeys) { + newCache[key] = localCache[key]!; + } + _setMessagesSinceUpdate(newCache); } /// Save the local cache of recently sent constructs to the local storage diff --git a/lib/pangea/models/analytics/constructs_model.dart b/lib/pangea/models/analytics/constructs_model.dart index a1495a276..10a47516a 100644 --- a/lib/pangea/models/analytics/constructs_model.dart +++ b/lib/pangea/models/analytics/constructs_model.dart @@ -78,6 +78,10 @@ class OneConstructUse { List categories; ConstructTypeEnum constructType; ConstructUseTypeEnum useType; + + /// Used to unqiuely identify the construct use. Useful in the case + /// that a users makes the same type of mistake multiple times in a + /// message, and those uses need to be disinguished. String? id; ConstructUseMetaData metadata; diff --git a/lib/pangea/models/choreo_record.dart b/lib/pangea/models/choreo_record.dart index ace5a738e..fe95dfc09 100644 --- a/lib/pangea/models/choreo_record.dart +++ b/lib/pangea/models/choreo_record.dart @@ -145,7 +145,6 @@ class ChoreoRecord { lemma: name, form: name, constructType: ConstructTypeEnum.grammar, - // @ggurdin what is this used for? id: "${metadata.eventId}_${step.acceptedOrIgnoredMatch!.match.offset}_${step.acceptedOrIgnoredMatch!.match.length}", metadata: metadata, ), diff --git a/lib/pangea/widgets/animations/progress_bar/level_bar.dart b/lib/pangea/widgets/animations/progress_bar/level_bar.dart index fb57a3bd5..fb8461f43 100644 --- a/lib/pangea/widgets/animations/progress_bar/level_bar.dart +++ b/lib/pangea/widgets/animations/progress_bar/level_bar.dart @@ -1,5 +1,4 @@ import 'package:fluffychat/config/app_config.dart'; -import 'package:fluffychat/pangea/constants/analytics_constants.dart'; import 'package:fluffychat/pangea/widgets/animations/progress_bar/animated_level_dart.dart'; import 'package:fluffychat/pangea/widgets/animations/progress_bar/progress_bar_details.dart'; import 'package:flutter/material.dart'; @@ -21,17 +20,11 @@ class LevelBar extends StatefulWidget { class LevelBarState extends State { double prevWidth = 0; - double get width { - const perLevel = AnalyticsConstants.xpPerLevel; - final percent = (widget.details.currentPoints % perLevel) / perLevel; - return widget.progressBarDetails.totalWidth * percent; - } - @override void didUpdateWidget(covariant LevelBar oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.details.currentPoints != widget.details.currentPoints) { - setState(() => prevWidth = width); + setState(() => prevWidth = widget.details.width); } } @@ -40,7 +33,7 @@ class LevelBarState extends State { return AnimatedLevelBar( height: widget.progressBarDetails.height, beginWidth: prevWidth, - endWidth: width, + endWidth: widget.details.width, decoration: BoxDecoration( borderRadius: const BorderRadius.all( Radius.circular(AppConfig.borderRadius), diff --git a/lib/pangea/widgets/animations/progress_bar/progress_bar_details.dart b/lib/pangea/widgets/animations/progress_bar/progress_bar_details.dart index debe93816..9ff4df142 100644 --- a/lib/pangea/widgets/animations/progress_bar/progress_bar_details.dart +++ b/lib/pangea/widgets/animations/progress_bar/progress_bar_details.dart @@ -3,10 +3,12 @@ import 'dart:ui'; class LevelBarDetails { final Color fillColor; final int currentPoints; + final double width; const LevelBarDetails({ required this.fillColor, required this.currentPoints, + required this.width, }); } diff --git a/lib/pangea/widgets/chat/message_selection_overlay.dart b/lib/pangea/widgets/chat/message_selection_overlay.dart index 5c41a3c33..01c44955f 100644 --- a/lib/pangea/widgets/chat/message_selection_overlay.dart +++ b/lib/pangea/widgets/chat/message_selection_overlay.dart @@ -348,7 +348,6 @@ class MessageOverlayController extends State nextEvent: widget._nextEvent, previousEvent: widget._prevEvent, ), - // TODO for @ggurdin - move reactions and toolbar here // MessageReactions(widget._event, widget.chatController.timeline!), // const SizedBox(height: 6), // MessagePadding( diff --git a/lib/pangea/widgets/chat/message_toolbar_buttons.dart b/lib/pangea/widgets/chat/message_toolbar_buttons.dart index 564538a75..c0d021caf 100644 --- a/lib/pangea/widgets/chat/message_toolbar_buttons.dart +++ b/lib/pangea/widgets/chat/message_toolbar_buttons.dart @@ -8,7 +8,7 @@ import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dar import 'package:fluffychat/pangea/widgets/chat/message_selection_overlay.dart'; import 'package:flutter/material.dart'; -class ToolbarButtons extends StatefulWidget { +class ToolbarButtons extends StatelessWidget { final MessageOverlayController overlayController; final double width; @@ -18,13 +18,8 @@ class ToolbarButtons extends StatefulWidget { super.key, }); - @override - ToolbarButtonsState createState() => ToolbarButtonsState(); -} - -class ToolbarButtonsState extends State { PangeaMessageEvent get pangeaMessageEvent => - widget.overlayController.pangeaMessageEvent; + overlayController.pangeaMessageEvent; List get modes => MessageMode.values .where((mode) => mode.isValidMode(pangeaMessageEvent.event)) @@ -32,31 +27,23 @@ class ToolbarButtonsState extends State { static const double iconWidth = 36.0; - MessageOverlayController get overlayController => widget.overlayController; - - // @ggurdin - maybe this can be stateless now? - @override - void initState() { - super.initState(); - } - @override Widget build(BuildContext context) { - final double barWidth = widget.width - iconWidth; + final double barWidth = width - iconWidth; - if (widget.overlayController.pangeaMessageEvent.isAudioMessage) { + if (overlayController.pangeaMessageEvent.isAudioMessage) { return const SizedBox(); } return SizedBox( - width: widget.width, + width: width, child: Stack( alignment: Alignment.center, children: [ Stack( children: [ Container( - width: widget.width, + width: width, height: 12, decoration: BoxDecoration( color: MessageModeExtension.barAndLockedButtonColor(context), @@ -87,18 +74,18 @@ class ToolbarButtonsState extends State { child: IconButton( iconSize: 20, icon: Icon(mode.icon), - color: mode == widget.overlayController.toolbarMode + color: mode == overlayController.toolbarMode ? Colors.white : null, - isSelected: mode == widget.overlayController.toolbarMode, + isSelected: mode == overlayController.toolbarMode, style: ButtonStyle( backgroundColor: WidgetStateProperty.all( mode.iconButtonColor( context, index, - widget.overlayController.toolbarMode, + overlayController.toolbarMode, pangeaMessageEvent.numberOfActivitiesCompleted, - widget.overlayController.isPracticeComplete, + overlayController.isPracticeComplete, ), ), ), @@ -107,8 +94,7 @@ class ToolbarButtonsState extends State { pangeaMessageEvent.numberOfActivitiesCompleted, overlayController.isPracticeComplete, ) - ? () => - widget.overlayController.updateToolbarMode(mode) + ? () => overlayController.updateToolbarMode(mode) : null, ), ), 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 5c1c3337f..6695d2673 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 @@ -1,7 +1,6 @@ import 'dart:async'; import 'package:fluffychat/config/themes.dart'; -import 'package:fluffychat/pangea/constants/analytics_constants.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/enum/construct_type_enum.dart'; import 'package:fluffychat/pangea/enum/progress_indicators_enum.dart'; @@ -60,7 +59,7 @@ class LearningProgressIndicatorsState _pangeaController.analytics.locallyCachedConstructs, ); int get serverXP => currentXP - localXP; - int get level => currentXP ~/ AnalyticsConstants.xpPerLevel; + int get level => _pangeaController.analytics.level; @override void initState() { @@ -147,10 +146,13 @@ class LearningProgressIndicatorsState ? const Color.fromARGB(255, 0, 190, 83) : Theme.of(context).colorScheme.primary, currentPoints: currentXP, + width: levelBarWidth * _pangeaController.analytics.levelProgress, ), LevelBarDetails( fillColor: Theme.of(context).colorScheme.primary, currentPoints: serverXP, + width: + levelBarWidth * _pangeaController.analytics.serverLevelProgress, ), ], progressBarDetails: ProgressBarDetails( @@ -242,15 +244,19 @@ class LearningProgressIndicatorsState ], ), ), - Container( - height: 36, - padding: const EdgeInsets.symmetric(horizontal: 32), - child: Stack( - alignment: Alignment.center, - children: [ - Positioned(left: 16, right: 0, child: progressBar), - Positioned(left: 0, child: levelBadge), - ], + Center( + child: SizedBox( + height: 36, + child: SizedBox( + width: levelBarWidth + 16, + child: Stack( + alignment: Alignment.center, + children: [ + Positioned(left: 16, right: 0, child: progressBar), + Positioned(left: 0, child: levelBadge), + ], + ), + ), ), ), const SizedBox(height: 16), diff --git a/lib/pangea/widgets/igc/span_card.dart b/lib/pangea/widgets/igc/span_card.dart index 757f8f0ea..816e8ed15 100644 --- a/lib/pangea/widgets/igc/span_card.dart +++ b/lib/pangea/widgets/igc/span_card.dart @@ -143,11 +143,9 @@ class SpanCardState extends State { } } - /// @ggurdin - this seems like it would be including the correct answer as well - /// we only want to give this kind of points for ignored distractors - /// Returns the list of choices that are not selected + /// Returns the list of distractor choices that are not selected List? get ignoredMatches => widget.scm.pangeaMatch?.match.choices - ?.where((choice) => !choice.selected) + ?.where((choice) => choice.isDistractor && !choice.selected) .toList(); /// Returns the list of tokens from choices that are not selected