Merge pull request #714 from pangeachat/analytics-notes
responded to comment from Will in client code, added fix for mini ana…
This commit is contained in:
commit
74fceeb82c
10 changed files with 80 additions and 65 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ class MyAnalyticsController extends BaseController<AnalyticsStream> {
|
|||
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<AnalyticsStream> {
|
|||
}
|
||||
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<AnalyticsStream> {
|
|||
}
|
||||
}
|
||||
|
||||
// @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<AnalyticsStream> {
|
|||
/// Add a list of construct uses for a new message to the local
|
||||
/// cache of recently sent messages
|
||||
Future<void> _addLocalMessage(
|
||||
String eventID,
|
||||
// @ggurdin - why is this an eventID and not a roomID?
|
||||
String cacheKey,
|
||||
List<OneConstructUse> 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<AnalyticsStream> {
|
|||
|
||||
/// 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<String, List<OneConstructUse>> newCache = {};
|
||||
for (final key in draftKeys) {
|
||||
newCache[key] = localCache[key]!;
|
||||
}
|
||||
_setMessagesSinceUpdate(newCache);
|
||||
}
|
||||
|
||||
/// Save the local cache of recently sent constructs to the local storage
|
||||
|
|
|
|||
|
|
@ -78,6 +78,10 @@ class OneConstructUse {
|
|||
List<String> 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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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<LevelBar> {
|
||||
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<LevelBar> {
|
|||
return AnimatedLevelBar(
|
||||
height: widget.progressBarDetails.height,
|
||||
beginWidth: prevWidth,
|
||||
endWidth: width,
|
||||
endWidth: widget.details.width,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(AppConfig.borderRadius),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -348,7 +348,6 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
|
|||
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(
|
||||
|
|
|
|||
|
|
@ -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<ToolbarButtons> {
|
||||
PangeaMessageEvent get pangeaMessageEvent =>
|
||||
widget.overlayController.pangeaMessageEvent;
|
||||
overlayController.pangeaMessageEvent;
|
||||
|
||||
List<MessageMode> get modes => MessageMode.values
|
||||
.where((mode) => mode.isValidMode(pangeaMessageEvent.event))
|
||||
|
|
@ -32,31 +27,23 @@ class ToolbarButtonsState extends State<ToolbarButtons> {
|
|||
|
||||
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<ToolbarButtons> {
|
|||
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<ToolbarButtons> {
|
|||
pangeaMessageEvent.numberOfActivitiesCompleted,
|
||||
overlayController.isPracticeComplete,
|
||||
)
|
||||
? () =>
|
||||
widget.overlayController.updateToolbarMode(mode)
|
||||
? () => overlayController.updateToolbarMode(mode)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -143,11 +143,9 @@ class SpanCardState extends State<SpanCard> {
|
|||
}
|
||||
}
|
||||
|
||||
/// @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<SpanChoice>? 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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue