From 617030cd78d51909efcb24f7ef93add2e6d16748 Mon Sep 17 00:00:00 2001 From: avashilling <165050625+avashilling@users.noreply.github.com> Date: Mon, 15 Sep 2025 10:57:17 -0400 Subject: [PATCH] 3873 add superlatives for most vocab grammar and xp (#3977) * add grammar and vocab superlatives - adds superlatives to the summary cards for each user - WIP, doesn't include XP superlative yet and it continuously reinitializes state which reloads the superlatives * get analytic superlatives from saved state event Revert activity user summaries widget to stateless, get info from saved state event instead of awaiting it, and fix some spacing issues. * add xp superlative - simplify 3 loop logic into one - change from constant updates to only generate superlatives on analytics save * sort imports * put analytics loading inside buttonControlledCarouselView * delete commented out code * return superlative map instead of setting value --- .../activity_room_extension.dart | 1 - .../activity_user_summaries_widget.dart | 100 +++++++++++++----- .../activity_summary_analytics_model.dart | 54 +++++++++- 3 files changed, 127 insertions(+), 28 deletions(-) diff --git a/lib/pangea/activity_sessions/activity_room_extension.dart b/lib/pangea/activity_sessions/activity_room_extension.dart index b5ab6aff3..0a356bc1b 100644 --- a/lib/pangea/activity_sessions/activity_room_extension.dart +++ b/lib/pangea/activity_sessions/activity_room_extension.dart @@ -127,7 +127,6 @@ extension ActivityRoomExtension on Room { if (activitySummary?.summary != null) { return; } - await setActivitySummary( ActivitySummaryModel( requestedAt: DateTime.now(), diff --git a/lib/pangea/activity_sessions/activity_user_summaries_widget.dart b/lib/pangea/activity_sessions/activity_user_summaries_widget.dart index 162e6f6fc..f371b58f1 100644 --- a/lib/pangea/activity_sessions/activity_user_summaries_widget.dart +++ b/lib/pangea/activity_sessions/activity_user_summaries_widget.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:collection/collection.dart'; +import 'package:material_symbols_icons/symbols.dart'; import 'package:matrix/matrix.dart'; import 'package:fluffychat/config/app_config.dart'; @@ -87,10 +88,10 @@ class ButtonControlledCarouselView extends StatelessWidget { @override Widget build(BuildContext context) { final room = controller.room; - + final superlatives = + room.activitySummary?.analytics?.generateSuperlatives(); final availableRoles = room.activityPlan!.roles; final assignedRoles = room.assignedRoles ?? {}; - final userSummaries = summary.participants .where( (p) => assignedRoles.values.any( @@ -160,21 +161,17 @@ class ButtonControlledCarouselView extends StatelessWidget { ), ), ), - Row( - spacing: 14.0, - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.all(4.0), - child: Row( - spacing: 4.0, - mainAxisSize: MainAxisSize.min, + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Wrap( + alignment: WrapAlignment.center, + spacing: 12, + runSpacing: 8, + //crossAxisAlignment: CrossAxisAlignment.center, children: [ - const Icon( - Icons.school, - size: 12.0, - color: AppConfig.yellowDark, - ), Text( p.cefrLevel, style: const TextStyle( @@ -182,18 +179,42 @@ class ButtonControlledCarouselView extends StatelessWidget { fontSize: 12.0, ), ), + //const SizedBox(width: 8), + if (superlatives != null && + (superlatives['vocab']!.contains( + p.participantId, + ))) ...[ + const SuperlativeTile( + icon: Symbols.dictionary, + ), + ], + if (superlatives != null && + (superlatives['grammar']!.contains( + p.participantId, + ))) ...[ + const SuperlativeTile( + icon: Symbols.toys_and_games, + ), + ], + if (superlatives != null && + (superlatives['xp']!.contains( + p.participantId, + ))) ...[ + const SuperlativeTile( + icon: Icons.star, + ), + ], + if (p.superlatives.isNotEmpty) ...[ + //const SizedBox(width: 8), + Text( + p.superlatives.first, + style: const TextStyle(fontSize: 12.0), + ), + ], ], ), - ), - if (p.superlatives.isNotEmpty) - Padding( - padding: const EdgeInsets.all(4.0), - child: Text( - p.superlatives.first, - style: const TextStyle(fontSize: 12.0), - ), - ), - ], + ], + ), ), ], ), @@ -229,3 +250,30 @@ class ButtonControlledCarouselView extends StatelessWidget { ); } } + +class SuperlativeTile extends StatelessWidget { + final IconData icon; + + const SuperlativeTile({ + super.key, + required this.icon, + }); + + @override + Widget build(BuildContext context) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(icon, size: 14, color: AppConfig.gold), + const SizedBox(width: 2), + const Text( + "1st", + style: TextStyle( + color: AppConfig.gold, + fontSize: 12.0, + ), + ), + ], + ); + } +} diff --git a/lib/pangea/activity_summary/activity_summary_analytics_model.dart b/lib/pangea/activity_summary/activity_summary_analytics_model.dart index c189e72e7..43e62f12a 100644 --- a/lib/pangea/activity_summary/activity_summary_analytics_model.dart +++ b/lib/pangea/activity_summary/activity_summary_analytics_model.dart @@ -55,9 +55,61 @@ class ActivitySummaryAnalyticsModel { } } + Map generateSuperlatives() { + final Map> superlatives = { + 'vocab': [], + 'grammar': [], + 'xp': [], + }; + // Find all user IDs + final userIds = constructs.keys.toList(); + if (userIds.isEmpty) { + return superlatives; + } + int maxVocab = 0; + int maxGrammar = 0; + int maxXp = 0; + final Map allVocabs = {}; + final Map allGrammars = {}; + final Map allXPs = {}; + + for (final userId in userIds) { + //vocab + final vocabCount = + uniqueConstructCountForUser(userId, ConstructTypeEnum.vocab); + allVocabs[userId] = vocabCount; + if (vocabCount > maxVocab) maxVocab = vocabCount; + + //grammar + final grammarCount = + uniqueConstructCountForUser(userId, ConstructTypeEnum.morph); + allGrammars[userId] = grammarCount; + if (grammarCount > maxGrammar) maxGrammar = grammarCount; + + //XP + final xpCount = totalXPForUser(userId); + allXPs[userId] = xpCount; + if (xpCount > maxXp) maxXp = xpCount; + } + superlatives['vocab'] = allVocabs.entries + .where((e) => e.value == maxVocab && maxVocab > 0) + .map((e) => e.key) + .toList(); + + superlatives['grammar'] = allGrammars.entries + .where((e) => e.value == maxGrammar && maxGrammar > 0) + .map((e) => e.key) + .toList(); + + superlatives['xp'] = allXPs.entries + .where((e) => e.value == maxXp && maxXp > 0) + .map((e) => e.key) + .toList(); + return superlatives; + } + factory ActivitySummaryAnalyticsModel.fromJson(Map json) { final model = ActivitySummaryAnalyticsModel(); - for (final userEntry in json.entries) { final userId = userEntry.key; final constructList = userEntry.value as List;