From 6f71dd4e95d793587dec1736a06e391887a5008c Mon Sep 17 00:00:00 2001 From: ggurdin <46800240+ggurdin@users.noreply.github.com> Date: Mon, 12 May 2025 09:23:03 -0400 Subject: [PATCH] feat: personal analytics downloads (#2759) * feat: personal analytics downloads * chore: download all analytics into one spreadsheet --- assets/l10n/intl_en.arb | 9 +- .../analytics_details_popup.dart | 3 + .../vocab_analytics_list_view.dart | 2 +- .../analytics_dowload_dialog.dart | 386 ++++++++++++++++++ .../analytics_download_button.dart | 24 ++ .../analytics_summary_enum.dart | 141 +++---- .../analytics_summary_model.dart | 57 +++ .../space_analytics_summary_enum.dart | 100 +++++ .../space_analytics_summary_model.dart} | 75 ++-- .../analytics_misc/construct_list_model.dart | 46 +-- .../analytics_misc/construct_type_enum.dart | 12 + .../construct_use_type_enum.dart | 10 +- .../pages/pangea_chat_details.dart | 4 +- ...t => download_space_analytics_button.dart} | 6 +- ...t => download_space_analytics_dialog.dart} | 44 +- 15 files changed, 723 insertions(+), 196 deletions(-) create mode 100644 lib/pangea/analytics_downloads/analytics_dowload_dialog.dart create mode 100644 lib/pangea/analytics_downloads/analytics_download_button.dart create mode 100644 lib/pangea/analytics_downloads/analytics_summary_model.dart create mode 100644 lib/pangea/analytics_downloads/space_analytics_summary_enum.dart rename lib/pangea/{analytics_misc/analytics_summary_model.dart => analytics_downloads/space_analytics_summary_model.dart} (78%) rename lib/pangea/chat_settings/widgets/{download_analytics_button.dart => download_space_analytics_button.dart} (84%) rename lib/pangea/spaces/widgets/{download_analytics_dialog.dart => download_space_analytics_dialog.dart} (91%) diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 6cb335831..e39ea8e0c 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -4905,5 +4905,12 @@ "kick": "Kick", "approve": "Approve", "youHaveKnocked": "You have knocked", - "pleaseWaitUntilInvited": "Please wait now, until someone from the room invites you." + "pleaseWaitUntilInvited": "Please wait now, until someone from the room invites you.", + "lemma": "Lemma", + "grammarFeature": "Grammar feature", + "grammarTag": "Grammar tag", + "forms": "Forms", + "exampleMessages": "Example messages", + "timesUsedIndependently": "Times used independently", + "timesUsedWithAssistance": "Times used with assistance" } diff --git a/lib/pangea/analytics_details_popup/analytics_details_popup.dart b/lib/pangea/analytics_details_popup/analytics_details_popup.dart index 7a5a6af0b..512ffc4bc 100644 --- a/lib/pangea/analytics_details_popup/analytics_details_popup.dart +++ b/lib/pangea/analytics_details_popup/analytics_details_popup.dart @@ -8,6 +8,7 @@ import 'package:fluffychat/pangea/analytics_details_popup/morph_analytics_list_v import 'package:fluffychat/pangea/analytics_details_popup/morph_details_view.dart'; import 'package:fluffychat/pangea/analytics_details_popup/vocab_analytics_details_view.dart'; import 'package:fluffychat/pangea/analytics_details_popup/vocab_analytics_list_view.dart'; +import 'package:fluffychat/pangea/analytics_downloads/analytics_download_button.dart'; import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart'; import 'package:fluffychat/pangea/analytics_summary/progress_indicators_enum.dart'; import 'package:fluffychat/pangea/common/utils/error_handler.dart'; @@ -162,6 +163,8 @@ class AnalyticsPopupWrapperState extends State { }), ), const SizedBox(width: 4.0), + if (kIsWeb) const DownloadAnalyticsButton(), + if (kIsWeb) const SizedBox(width: 4.0), ], ), body: localView == ConstructTypeEnum.morph diff --git a/lib/pangea/analytics_details_popup/vocab_analytics_list_view.dart b/lib/pangea/analytics_details_popup/vocab_analytics_list_view.dart index 7f3db077d..c1e00254a 100644 --- a/lib/pangea/analytics_details_popup/vocab_analytics_list_view.dart +++ b/lib/pangea/analytics_details_popup/vocab_analytics_list_view.dart @@ -98,7 +98,7 @@ class VocabAnalyticsListView extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 225.0), + constraints: const BoxConstraints(maxWidth: 250.0), child: AnimatedSwitcher( duration: const Duration(milliseconds: 300), transitionBuilder: (child, animation) => FadeTransition( diff --git a/lib/pangea/analytics_downloads/analytics_dowload_dialog.dart b/lib/pangea/analytics_downloads/analytics_dowload_dialog.dart new file mode 100644 index 000000000..4cfa8a320 --- /dev/null +++ b/lib/pangea/analytics_downloads/analytics_dowload_dialog.dart @@ -0,0 +1,386 @@ +import 'package:flutter/material.dart'; + +import 'package:excel/excel.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:matrix/matrix.dart'; + +import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/config/themes.dart'; +import 'package:fluffychat/pangea/analytics_downloads/analytics_summary_enum.dart'; +import 'package:fluffychat/pangea/analytics_downloads/analytics_summary_model.dart'; +import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart'; +import 'package:fluffychat/pangea/analytics_misc/construct_use_model.dart'; +import 'package:fluffychat/pangea/analytics_misc/construct_use_type_enum.dart'; +import 'package:fluffychat/pangea/analytics_misc/constructs_model.dart'; +import 'package:fluffychat/pangea/analytics_misc/learning_skills_enum.dart'; +import 'package:fluffychat/pangea/chat_settings/utils/download_file.dart'; +import 'package:fluffychat/pangea/common/utils/error_handler.dart'; +import 'package:fluffychat/pangea/constructs/construct_identifier.dart'; +import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart'; +import 'package:fluffychat/pangea/morphs/get_grammar_copy.dart'; +import 'package:fluffychat/pangea/morphs/morph_features_enum.dart'; +import 'package:fluffychat/pangea/morphs/morph_repo.dart'; +import 'package:fluffychat/widgets/matrix.dart'; + +class AnalyticsDownloadDialog extends StatefulWidget { + const AnalyticsDownloadDialog({ + super.key, + }); + + @override + AnalyticsDownloadDialogState createState() => AnalyticsDownloadDialogState(); +} + +class AnalyticsDownloadDialogState extends State { + DownloadType _downloadType = DownloadType.csv; + + bool _downloading = false; + bool _downloaded = false; + String? _error; + + String? get _statusText { + if (_downloading) return L10n.of(context).downloading; + if (_downloaded) return L10n.of(context).downloadComplete; + return null; + } + + void _setDownloadType(DownloadType type) { + if (mounted) setState(() => _downloadType = type); + } + + Future _downloadAnalytics() async { + try { + setState(() { + _downloading = true; + _downloaded = false; + _error = null; + }); + + final vocabSummary = await _getVocabAnalytics(); + final morphSummary = await _getMorphAnalytics(); + + final content = _getExcelFileContent({ + ConstructTypeEnum.vocab: vocabSummary, + ConstructTypeEnum.morph: morphSummary, + }); + + final fileName = + "analytics_${MatrixState.pangeaController.matrixState.client.userID?.localpart}_${DateTime.now().toIso8601String()}.${_downloadType == DownloadType.xlsx ? 'xlsx' : 'csv'}"; + + await downloadFile( + content, + fileName, + _downloadType, + ); + } catch (e) { + ErrorHandler.logError( + e: e, + data: { + "downloadType": _downloadType, + }, + ); + _error = e.toString(); + } finally { + setState(() { + _downloading = false; + _downloaded = true; + }); + } + } + + Future> _getVocabAnalytics() async { + final uses = MatrixState.pangeaController.getAnalytics.constructListModel + .constructList(type: ConstructTypeEnum.vocab); + final Map> lemmasToUses = {}; + for (final use in uses) { + lemmasToUses[use.lemma] ??= []; + lemmasToUses[use.lemma]!.add(use); + } + + final List summaries = []; + for (final entry in lemmasToUses.entries) { + final lemma = entry.key; + final uses = entry.value; + + final xp = uses.map((e) => e.points).reduce((a, total) => a + total); + final exampleMessages = await _getExampleMessages(uses); + final allUses = uses.map((u) => u.uses).expand((element) => element); + + int independantUseOccurrences = 0; + int assistedUseOccurrences = 0; + for (final use in allUses) { + use.useType == ConstructUseTypeEnum.wa + ? independantUseOccurrences++ + : assistedUseOccurrences++; + } + + final forms = allUses + .map((e) => e.form?.toLowerCase()) + .toSet() + .whereType() + .toList(); + + final summary = AnalyticsSummaryModel( + lemma: lemma, + xp: xp, + forms: forms, + exampleMessages: exampleMessages, + independantUseOccurrences: independantUseOccurrences, + assistedUseOccurrences: assistedUseOccurrences, + ); + + summaries.add(summary); + } + + return summaries; + } + + Future> _getMorphAnalytics() async { + final constructListModel = + MatrixState.pangeaController.getAnalytics.constructListModel; + + final morphs = await MorphsRepo.get(); + final List summaries = []; + for (final feature in morphs.displayFeatures) { + final allTags = morphs + .getDisplayTags(feature.feature) + .map((tag) => tag.toLowerCase()) + .toSet(); + + for (final morphTag in allTags) { + final id = ConstructIdentifier( + lemma: morphTag, + type: ConstructTypeEnum.morph, + category: feature.feature, + ); + + final uses = constructListModel.getConstructUses(id); + if (uses == null) continue; + + final xp = uses.points; + final exampleMessages = await _getExampleMessages([uses]); + final allUses = uses.uses; + + int independantUseOccurrences = 0; + int assistedUseOccurrences = 0; + for (final use in allUses) { + use.useType == ConstructUseTypeEnum.wa + ? independantUseOccurrences++ + : assistedUseOccurrences++; + } + + final forms = allUses + .map((e) => e.form?.toLowerCase()) + .toSet() + .whereType() + .toList(); + + final tagCopy = getGrammarCopy( + category: feature.feature, + lemma: morphTag, + context: context, + ); + + final summary = AnalyticsSummaryModel( + morphFeature: MorphFeaturesEnumExtension.fromString(feature.feature) + .getDisplayCopy(context), + morphTag: tagCopy, + xp: xp, + forms: forms, + exampleMessages: exampleMessages, + independantUseOccurrences: independantUseOccurrences, + assistedUseOccurrences: assistedUseOccurrences, + ); + + summaries.add(summary); + } + } + + return summaries; + } + + Future> _getExampleMessages( + List constructUses, + ) async { + final allUses = constructUses.map((e) => e.uses).expand((e) => e).toList(); + final List examples = []; + for (final OneConstructUse use in allUses) { + final Room? room = MatrixState.pangeaController.matrixState.client + .getRoomById(use.metadata.roomId!); + if (room == null) continue; + + if (use.useType.skillsEnumType != LearningSkillsEnum.writing || + use.metadata.eventId == null || + use.form == null || + use.xp <= 0) { + continue; + } + + final exampleIndex = examples.indexWhere( + (example) => example.eventId == use.metadata.eventId!, + ); + if (exampleIndex != -1) continue; + if (use.metadata.roomId == null) continue; + + Timeline? timeline = room.timeline; + if (room.timeline == null) { + timeline = await room.getTimeline(); + } + + final Event? event = await room.getEventById(use.metadata.eventId!); + + if (event == null || event.senderId != room.client.userID) continue; + final PangeaMessageEvent pangeaMessageEvent = PangeaMessageEvent( + event: event, + timeline: timeline!, + ownMessage: event.senderId == + MatrixState.pangeaController.matrixState.client.userID, + ); + examples.add(pangeaMessageEvent); + if (examples.length >= 5) break; + } + + return examples.map((m) => m.messageDisplayText).toSet().toList(); + } + + List _getExcelFileContent( + Map> summaries, + ) { + final excel = Excel.createExcel(); + + for (final entry in summaries.entries) { + final sheet = excel[entry.key.sheetname(context)]; + final values = entry.key == ConstructTypeEnum.vocab + ? AnalyticsSummaryEnum.vocabValues + : AnalyticsSummaryEnum.morphValues; + + for (final key in values) { + sheet + .cell( + CellIndex.indexByColumnRow( + rowIndex: 0, + columnIndex: values.indexOf(key), + ), + ) + .value = TextCellValue(key.header(context)); + } + + final rows = entry.value + .map( + (summary) => _formatExcelRow( + summary, + entry.key, + ), + ) + .toList(); + + for (int i = 0; i < rows.length; i++) { + final row = rows[i]; + for (int j = 0; j < row.length; j++) { + final cell = row[j]; + sheet + .cell(CellIndex.indexByColumnRow(rowIndex: i + 2, columnIndex: j)) + .value = cell; + } + } + } + + excel.setDefaultSheet(ConstructTypeEnum.vocab.sheetname(context)); + excel.delete('Sheet1'); + return excel.encode() ?? []; + } + + List _formatExcelRow( + AnalyticsSummaryModel summary, + ConstructTypeEnum type, + ) { + final List row = []; + final values = type == ConstructTypeEnum.vocab + ? AnalyticsSummaryEnum.vocabValues + : AnalyticsSummaryEnum.morphValues; + + for (int i = 0; i < values.length; i++) { + final key = values[i]; + final value = summary.getValue(key); + if (value is int) { + row.add(IntCellValue(value)); + } else if (value is String) { + row.add(TextCellValue(value)); + } else if (value is List) { + row.add(TextCellValue(value.map((v) => "\"$v\"").join(", "))); + } + } + return row; + } + + @override + Widget build(BuildContext context) { + return Dialog( + child: Container( + constraints: const BoxConstraints( + maxWidth: 400, + ), + padding: const EdgeInsets.symmetric(vertical: 20), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + L10n.of(context).fileType, + style: TextStyle( + fontSize: AppConfig.fontSizeFactor * AppConfig.messageFontSize, + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: SegmentedButton( + selected: {_downloadType}, + onSelectionChanged: (c) => _setDownloadType(c.first), + segments: [ + ButtonSegment( + value: DownloadType.csv, + label: Text(L10n.of(context).commaSeparatedFile), + ), + ButtonSegment( + value: DownloadType.xlsx, + label: Text(L10n.of(context).excelFile), + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.fromLTRB(8.0, 16.0, 8.0, 8.0), + child: OutlinedButton( + onPressed: _downloading ? null : _downloadAnalytics, + child: _downloading + ? const SizedBox( + height: 10, + width: 100, + child: LinearProgressIndicator(), + ) + : Text(L10n.of(context).download), + ), + ), + AnimatedSize( + duration: FluffyThemes.animationDuration, + child: _statusText != null + ? Padding( + padding: const EdgeInsets.all(8.0), + child: Text(_statusText!), + ) + : const SizedBox(), + ), + AnimatedSize( + duration: FluffyThemes.animationDuration, + child: _error != null + ? Padding( + padding: const EdgeInsets.all(8.0), + child: Text(L10n.of(context).oopsSomethingWentWrong), + ) + : const SizedBox(), + ), + ], + ), + ), + ); + } +} diff --git a/lib/pangea/analytics_downloads/analytics_download_button.dart b/lib/pangea/analytics_downloads/analytics_download_button.dart new file mode 100644 index 000000000..b18ff4ff1 --- /dev/null +++ b/lib/pangea/analytics_downloads/analytics_download_button.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; + +import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:material_symbols_icons/symbols.dart'; + +import 'package:fluffychat/pangea/analytics_downloads/analytics_dowload_dialog.dart'; + +class DownloadAnalyticsButton extends StatelessWidget { + const DownloadAnalyticsButton({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return IconButton( + tooltip: L10n.of(context).download, + icon: const Icon(Symbols.download), + onPressed: () => showDialog( + context: context, + builder: (context) => const AnalyticsDownloadDialog(), + ), + ); + } +} diff --git a/lib/pangea/analytics_downloads/analytics_summary_enum.dart b/lib/pangea/analytics_downloads/analytics_summary_enum.dart index 4e32161c5..8bf70a564 100644 --- a/lib/pangea/analytics_downloads/analytics_summary_enum.dart +++ b/lib/pangea/analytics_downloads/analytics_summary_enum.dart @@ -1,100 +1,57 @@ +import 'package:flutter/material.dart'; + import 'package:flutter_gen/gen_l10n/l10n.dart'; enum AnalyticsSummaryEnum { - username, - dataAvailable, - level, - totalXP, - - numMessagesSent, - numWordsTyped, - numChoicesCorrect, - numChoicesIncorrect, - - numLemmas, - numLemmasUsedCorrectly, - numLemmasUsedIncorrectly, - - /// 0 - 30 XP - numLemmasSmallXP, - - /// 31 - 200 XP - numLemmasMediumXP, - - /// > 200 XP - numLemmasLargeXP, - - numMorphConstructs, - listMorphConstructs, - listMorphConstructsUsedCorrectlyOriginal, - listMorphConstructsUsedIncorrectlyOriginal, - listMorphConstructsUsedCorrectlySystem, - listMorphConstructsUsedIncorrectlySystem, - - // list morph 0 - 30 XP - listMorphSmallXP, - - // list morph 31 - 200 XP - listMorphMediumXP, - - // list morph 200 - 500 XP - listMorphLargeXP, - - // list morph > 500 XP - listMorphHugeXP, -} - -extension AnalyticsSummaryEnumExtension on AnalyticsSummaryEnum { - String header(L10n l10n) { + lemma, + morphFeature, + morphTag, + xp, + forms, + exampleMessages, + independentUseOccurrences, + assistedUseOccurrences; + + String header(BuildContext context) { + final l10n = L10n.of(context); switch (this) { - case AnalyticsSummaryEnum.username: - return l10n.username; - case AnalyticsSummaryEnum.dataAvailable: - return l10n.dataAvailable; - case AnalyticsSummaryEnum.level: - return l10n.level; - case AnalyticsSummaryEnum.totalXP: + case lemma: + return l10n.lemma; + case morphFeature: + return l10n.grammarFeature; + case morphTag: + return l10n.grammarTag; + case xp: return l10n.totalXP; - case AnalyticsSummaryEnum.numLemmas: - return l10n.numLemmas; - case AnalyticsSummaryEnum.numLemmasUsedCorrectly: - return l10n.numLemmasUsedCorrectly; - case AnalyticsSummaryEnum.numLemmasUsedIncorrectly: - return l10n.numLemmasUsedIncorrectly; - case AnalyticsSummaryEnum.numLemmasSmallXP: - return l10n.numLemmasSmallXP; - case AnalyticsSummaryEnum.numLemmasMediumXP: - return l10n.numLemmasMediumXP; - case AnalyticsSummaryEnum.numLemmasLargeXP: - return l10n.numLemmasLargeXP; - case AnalyticsSummaryEnum.numMorphConstructs: - return l10n.numGrammarConcepts; - case AnalyticsSummaryEnum.listMorphConstructs: - return l10n.listGrammarConcepts; - case AnalyticsSummaryEnum.listMorphConstructsUsedCorrectlyOriginal: - return l10n.listGrammarConceptsUsedCorrectly; - case AnalyticsSummaryEnum.listMorphConstructsUsedIncorrectlyOriginal: - return l10n.listGrammarConceptsUsedIncorrectly; - case AnalyticsSummaryEnum.listMorphConstructsUsedCorrectlySystem: - return l10n.listGrammarConceptsUseCorrectlySystemGenerated; - case AnalyticsSummaryEnum.listMorphConstructsUsedIncorrectlySystem: - return l10n.listGrammarConceptsUseIncorrectlySystemGenerated; - case AnalyticsSummaryEnum.listMorphSmallXP: - return l10n.listGrammarConceptsSmallXP; - case AnalyticsSummaryEnum.listMorphMediumXP: - return l10n.listGrammarConceptsMediumXP; - case AnalyticsSummaryEnum.listMorphLargeXP: - return l10n.listGrammarConceptsLargeXP; - case AnalyticsSummaryEnum.listMorphHugeXP: - return l10n.listGrammarConceptsHugeXP; - case AnalyticsSummaryEnum.numMessagesSent: - return l10n.numMessagesSent; - case AnalyticsSummaryEnum.numWordsTyped: - return l10n.numWordsTyped; - case AnalyticsSummaryEnum.numChoicesCorrect: - return l10n.numCorrectChoices; - case AnalyticsSummaryEnum.numChoicesIncorrect: - return l10n.numIncorrectChoices; + case forms: + return l10n.forms; + case exampleMessages: + return l10n.exampleMessages; + case independentUseOccurrences: + return l10n.timesUsedIndependently; + case assistedUseOccurrences: + return l10n.timesUsedWithAssistance; } } + + const AnalyticsSummaryEnum(); + + static List get vocabValues => [ + lemma, + xp, + forms, + exampleMessages, + independentUseOccurrences, + assistedUseOccurrences, + ]; + + static List get morphValues => [ + morphFeature, + morphTag, + xp, + forms, + exampleMessages, + independentUseOccurrences, + assistedUseOccurrences, + ]; } diff --git a/lib/pangea/analytics_downloads/analytics_summary_model.dart b/lib/pangea/analytics_downloads/analytics_summary_model.dart new file mode 100644 index 000000000..485ece53f --- /dev/null +++ b/lib/pangea/analytics_downloads/analytics_summary_model.dart @@ -0,0 +1,57 @@ +import 'package:fluffychat/pangea/analytics_downloads/analytics_summary_enum.dart'; + +class AnalyticsSummaryModel { + String? lemma; + String? morphFeature; + String? morphTag; + int xp; + List forms; + List exampleMessages; + int independantUseOccurrences; + int assistedUseOccurrences; + + AnalyticsSummaryModel({ + this.lemma, + this.morphFeature, + this.morphTag, + required this.xp, + required this.forms, + required this.exampleMessages, + required this.independantUseOccurrences, + required this.assistedUseOccurrences, + }); + + Map toJson() { + return { + 'lemma': lemma, + 'morphFeature': morphFeature, + 'morphTag': morphTag, + 'xp': xp, + 'forms': forms, + 'exampleMessages': exampleMessages, + 'totalOriginalUseOccurrences': independantUseOccurrences, + 'correctOriginalUseOccurrences': independantUseOccurrences, + }; + } + + dynamic getValue(AnalyticsSummaryEnum key) { + switch (key) { + case AnalyticsSummaryEnum.lemma: + return lemma; + case AnalyticsSummaryEnum.morphFeature: + return morphFeature; + case AnalyticsSummaryEnum.morphTag: + return morphTag; + case AnalyticsSummaryEnum.xp: + return xp; + case AnalyticsSummaryEnum.forms: + return forms; + case AnalyticsSummaryEnum.exampleMessages: + return exampleMessages; + case AnalyticsSummaryEnum.independentUseOccurrences: + return independantUseOccurrences; + case AnalyticsSummaryEnum.assistedUseOccurrences: + return assistedUseOccurrences; + } + } +} diff --git a/lib/pangea/analytics_downloads/space_analytics_summary_enum.dart b/lib/pangea/analytics_downloads/space_analytics_summary_enum.dart new file mode 100644 index 000000000..0f60c00b1 --- /dev/null +++ b/lib/pangea/analytics_downloads/space_analytics_summary_enum.dart @@ -0,0 +1,100 @@ +import 'package:flutter_gen/gen_l10n/l10n.dart'; + +enum SpaceAnalyticsSummaryEnum { + username, + dataAvailable, + level, + totalXP, + + numMessagesSent, + numWordsTyped, + numChoicesCorrect, + numChoicesIncorrect, + + numLemmas, + numLemmasUsedCorrectly, + numLemmasUsedIncorrectly, + + /// 0 - 30 XP + numLemmasSmallXP, + + /// 31 - 200 XP + numLemmasMediumXP, + + /// > 200 XP + numLemmasLargeXP, + + numMorphConstructs, + listMorphConstructs, + listMorphConstructsUsedCorrectlyOriginal, + listMorphConstructsUsedIncorrectlyOriginal, + listMorphConstructsUsedCorrectlySystem, + listMorphConstructsUsedIncorrectlySystem, + + // list morph 0 - 30 XP + listMorphSmallXP, + + // list morph 31 - 200 XP + listMorphMediumXP, + + // list morph 200 - 500 XP + listMorphLargeXP, + + // list morph > 500 XP + listMorphHugeXP, +} + +extension AnalyticsSummaryEnumExtension on SpaceAnalyticsSummaryEnum { + String header(L10n l10n) { + switch (this) { + case SpaceAnalyticsSummaryEnum.username: + return l10n.username; + case SpaceAnalyticsSummaryEnum.dataAvailable: + return l10n.dataAvailable; + case SpaceAnalyticsSummaryEnum.level: + return l10n.level; + case SpaceAnalyticsSummaryEnum.totalXP: + return l10n.totalXP; + case SpaceAnalyticsSummaryEnum.numLemmas: + return l10n.numLemmas; + case SpaceAnalyticsSummaryEnum.numLemmasUsedCorrectly: + return l10n.numLemmasUsedCorrectly; + case SpaceAnalyticsSummaryEnum.numLemmasUsedIncorrectly: + return l10n.numLemmasUsedIncorrectly; + case SpaceAnalyticsSummaryEnum.numLemmasSmallXP: + return l10n.numLemmasSmallXP; + case SpaceAnalyticsSummaryEnum.numLemmasMediumXP: + return l10n.numLemmasMediumXP; + case SpaceAnalyticsSummaryEnum.numLemmasLargeXP: + return l10n.numLemmasLargeXP; + case SpaceAnalyticsSummaryEnum.numMorphConstructs: + return l10n.numGrammarConcepts; + case SpaceAnalyticsSummaryEnum.listMorphConstructs: + return l10n.listGrammarConcepts; + case SpaceAnalyticsSummaryEnum.listMorphConstructsUsedCorrectlyOriginal: + return l10n.listGrammarConceptsUsedCorrectly; + case SpaceAnalyticsSummaryEnum.listMorphConstructsUsedIncorrectlyOriginal: + return l10n.listGrammarConceptsUsedIncorrectly; + case SpaceAnalyticsSummaryEnum.listMorphConstructsUsedCorrectlySystem: + return l10n.listGrammarConceptsUseCorrectlySystemGenerated; + case SpaceAnalyticsSummaryEnum.listMorphConstructsUsedIncorrectlySystem: + return l10n.listGrammarConceptsUseIncorrectlySystemGenerated; + case SpaceAnalyticsSummaryEnum.listMorphSmallXP: + return l10n.listGrammarConceptsSmallXP; + case SpaceAnalyticsSummaryEnum.listMorphMediumXP: + return l10n.listGrammarConceptsMediumXP; + case SpaceAnalyticsSummaryEnum.listMorphLargeXP: + return l10n.listGrammarConceptsLargeXP; + case SpaceAnalyticsSummaryEnum.listMorphHugeXP: + return l10n.listGrammarConceptsHugeXP; + case SpaceAnalyticsSummaryEnum.numMessagesSent: + return l10n.numMessagesSent; + case SpaceAnalyticsSummaryEnum.numWordsTyped: + return l10n.numWordsTyped; + case SpaceAnalyticsSummaryEnum.numChoicesCorrect: + return l10n.numCorrectChoices; + case SpaceAnalyticsSummaryEnum.numChoicesIncorrect: + return l10n.numIncorrectChoices; + } + } +} diff --git a/lib/pangea/analytics_misc/analytics_summary_model.dart b/lib/pangea/analytics_downloads/space_analytics_summary_model.dart similarity index 78% rename from lib/pangea/analytics_misc/analytics_summary_model.dart rename to lib/pangea/analytics_downloads/space_analytics_summary_model.dart index 8ebfdf46d..155021593 100644 --- a/lib/pangea/analytics_misc/analytics_summary_model.dart +++ b/lib/pangea/analytics_downloads/space_analytics_summary_model.dart @@ -2,13 +2,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:fluffychat/pangea/analytics_downloads/analytics_summary_enum.dart'; +import 'package:fluffychat/pangea/analytics_downloads/space_analytics_summary_enum.dart'; import 'package:fluffychat/pangea/analytics_misc/construct_list_model.dart'; import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart'; import 'package:fluffychat/pangea/analytics_misc/construct_use_model.dart'; import 'package:fluffychat/pangea/analytics_misc/construct_use_type_enum.dart'; -class AnalyticsSummaryModel { +class SpaceAnalyticsSummaryModel { String username; bool dataAvailable; int? level; @@ -51,7 +51,7 @@ class AnalyticsSummaryModel { int? numChoicesCorrect; int? numChoicesIncorrect; - AnalyticsSummaryModel({ + SpaceAnalyticsSummaryModel({ required this.username, required this.dataAvailable, this.level, @@ -78,14 +78,14 @@ class AnalyticsSummaryModel { this.numChoicesIncorrect, }); - static AnalyticsSummaryModel emptyModel(String userID) { - return AnalyticsSummaryModel( + static SpaceAnalyticsSummaryModel emptyModel(String userID) { + return SpaceAnalyticsSummaryModel( username: userID, dataAvailable: false, ); } - static AnalyticsSummaryModel fromConstructListModel( + static SpaceAnalyticsSummaryModel fromConstructListModel( String userID, ConstructListModel? model, String Function(ConstructUses) getCopy, @@ -111,7 +111,8 @@ class AnalyticsSummaryModel { final originalWrittenUses = morphLemmas.lemmasByPercent( filter: (use) => use.useType == ConstructUseTypeEnum.wa || - use.useType == ConstructUseTypeEnum.ga, + use.useType == ConstructUseTypeEnum.ga || + use.useType == ConstructUseTypeEnum.ta, percent: 0.8, context: context, ); @@ -123,6 +124,7 @@ class AnalyticsSummaryModel { filter: (use) => use.useType != ConstructUseTypeEnum.wa && use.useType != ConstructUseTypeEnum.ga && + use.useType != ConstructUseTypeEnum.ta && use.useType != ConstructUseTypeEnum.unk && use.xp != 0, percent: 0.8, @@ -143,13 +145,14 @@ class AnalyticsSummaryModel { numChoicesCorrect = 0; numChoicesIncorrect = 0; for (final use in model.uses) { - if (use.useType.summaryEnumType == AnalyticsSummaryEnum.numWordsTyped) { + if (use.useType.summaryEnumType == + SpaceAnalyticsSummaryEnum.numWordsTyped) { numWordsTyped = numWordsTyped! + 1; } else if (use.useType.summaryEnumType == - AnalyticsSummaryEnum.numChoicesCorrect) { + SpaceAnalyticsSummaryEnum.numChoicesCorrect) { numChoicesCorrect = numChoicesCorrect! + 1; } else if (use.useType.summaryEnumType == - AnalyticsSummaryEnum.numChoicesIncorrect) { + SpaceAnalyticsSummaryEnum.numChoicesIncorrect) { numChoicesIncorrect = numChoicesIncorrect! + 1; } } @@ -161,7 +164,7 @@ class AnalyticsSummaryModel { .toSet() .length; - return AnalyticsSummaryModel( + return SpaceAnalyticsSummaryModel( username: userID, dataAvailable: model != null, level: model?.level, @@ -208,57 +211,57 @@ class AnalyticsSummaryModel { ); } - dynamic getValue(AnalyticsSummaryEnum key, BuildContext context) { + dynamic getValue(SpaceAnalyticsSummaryEnum key, BuildContext context) { switch (key) { - case AnalyticsSummaryEnum.username: + case SpaceAnalyticsSummaryEnum.username: return username; - case AnalyticsSummaryEnum.dataAvailable: + case SpaceAnalyticsSummaryEnum.dataAvailable: return dataAvailable ? L10n.of(context).available : L10n.of(context).unavailable; - case AnalyticsSummaryEnum.level: + case SpaceAnalyticsSummaryEnum.level: return level; - case AnalyticsSummaryEnum.totalXP: + case SpaceAnalyticsSummaryEnum.totalXP: return totalXP; - case AnalyticsSummaryEnum.numLemmas: + case SpaceAnalyticsSummaryEnum.numLemmas: return numLemmas; - case AnalyticsSummaryEnum.numLemmasUsedCorrectly: + case SpaceAnalyticsSummaryEnum.numLemmasUsedCorrectly: return numLemmasUsedCorrectly; - case AnalyticsSummaryEnum.numLemmasUsedIncorrectly: + case SpaceAnalyticsSummaryEnum.numLemmasUsedIncorrectly: return numLemmasUsedIncorrectly; - case AnalyticsSummaryEnum.numLemmasSmallXP: + case SpaceAnalyticsSummaryEnum.numLemmasSmallXP: return numLemmasSmallXP; - case AnalyticsSummaryEnum.numLemmasMediumXP: + case SpaceAnalyticsSummaryEnum.numLemmasMediumXP: return numLemmasMediumXP; - case AnalyticsSummaryEnum.numLemmasLargeXP: + case SpaceAnalyticsSummaryEnum.numLemmasLargeXP: return numLemmasLargeXP; - case AnalyticsSummaryEnum.numMorphConstructs: + case SpaceAnalyticsSummaryEnum.numMorphConstructs: return numMorphConstructs; - case AnalyticsSummaryEnum.listMorphConstructs: + case SpaceAnalyticsSummaryEnum.listMorphConstructs: return listMorphConstructs; - case AnalyticsSummaryEnum.listMorphConstructsUsedCorrectlyOriginal: + case SpaceAnalyticsSummaryEnum.listMorphConstructsUsedCorrectlyOriginal: return listMorphConstructsUsedCorrectlyOriginal; - case AnalyticsSummaryEnum.listMorphConstructsUsedIncorrectlyOriginal: + case SpaceAnalyticsSummaryEnum.listMorphConstructsUsedIncorrectlyOriginal: return listMorphConstructsUsedIncorrectlyOriginal; - case AnalyticsSummaryEnum.listMorphConstructsUsedCorrectlySystem: + case SpaceAnalyticsSummaryEnum.listMorphConstructsUsedCorrectlySystem: return listMorphConstructsUsedCorrectlySystem; - case AnalyticsSummaryEnum.listMorphConstructsUsedIncorrectlySystem: + case SpaceAnalyticsSummaryEnum.listMorphConstructsUsedIncorrectlySystem: return listMorphConstructsUsedIncorrectlySystem; - case AnalyticsSummaryEnum.listMorphSmallXP: + case SpaceAnalyticsSummaryEnum.listMorphSmallXP: return listMorphSmallXP; - case AnalyticsSummaryEnum.listMorphMediumXP: + case SpaceAnalyticsSummaryEnum.listMorphMediumXP: return listMorphMediumXP; - case AnalyticsSummaryEnum.listMorphLargeXP: + case SpaceAnalyticsSummaryEnum.listMorphLargeXP: return listMorphLargeXP; - case AnalyticsSummaryEnum.listMorphHugeXP: + case SpaceAnalyticsSummaryEnum.listMorphHugeXP: return listMorphHugeXP; - case AnalyticsSummaryEnum.numMessagesSent: + case SpaceAnalyticsSummaryEnum.numMessagesSent: return numMessagesSent; - case AnalyticsSummaryEnum.numWordsTyped: + case SpaceAnalyticsSummaryEnum.numWordsTyped: return numWordsTyped; - case AnalyticsSummaryEnum.numChoicesCorrect: + case SpaceAnalyticsSummaryEnum.numChoicesCorrect: return numChoicesCorrect; - case AnalyticsSummaryEnum.numChoicesIncorrect: + case SpaceAnalyticsSummaryEnum.numChoicesIncorrect: return numChoicesIncorrect; } } diff --git a/lib/pangea/analytics_misc/construct_list_model.dart b/lib/pangea/analytics_misc/construct_list_model.dart index 1eb845154..86a54e963 100644 --- a/lib/pangea/analytics_misc/construct_list_model.dart +++ b/lib/pangea/analytics_misc/construct_list_model.dart @@ -19,7 +19,7 @@ class ConstructListModel { List get uses => _uses; List get truncatedUses => _uses.take(100).toList(); - /// A map of lemmas to ConstructUses, each of which contains a lemma + /// A map of ConstructIdentifiers to ConstructUses, each of which contains a lemma /// key = lemma + constructType.string, value = ConstructUses final Map _constructMap = {}; @@ -27,14 +27,11 @@ class ConstructListModel { /// be accessed. It contains the same information as _constructMap, but sorted. List _constructList = []; - /// A map of categories to lists of ConstructUses - Map> _categoriesToUses = {}; - /// A list of unique vocab lemmas - List vocabLemmasList = []; + List _vocabLemmasList = []; /// A list of unique grammar lemmas - List grammarLemmasList = []; + List _grammarLemmasList = []; /// [D] is the "compression factor". It determines how quickly /// or slowly the level grows relative to XP @@ -47,7 +44,7 @@ class ConstructListModel { final constructs = constructList(type: type); final List unlocked = []; final constructsList = - type == ConstructTypeEnum.vocab ? vocabLemmasList : grammarLemmasList; + type == ConstructTypeEnum.vocab ? _vocabLemmasList : _grammarLemmasList; for (final lemma in constructsList) { final matches = constructs.where((m) => m.lemma == lemma); @@ -74,10 +71,10 @@ class ConstructListModel { updateConstructs(uses, offset); } - int get totalLemmas => vocabLemmasList.length + grammarLemmasList.length; - int get vocabLemmas => vocabLemmasList.length; - int get grammarLemmas => grammarLemmasList.length; - List get lemmasList => vocabLemmasList + grammarLemmasList; + int get totalLemmas => _vocabLemmasList.length + _grammarLemmasList.length; + int get vocabLemmas => _vocabLemmasList.length; + int get grammarLemmas => _grammarLemmasList.length; + List get lemmasList => _vocabLemmasList + _grammarLemmasList; /// Given a list of new construct uses, update the map of construct /// IDs to ConstructUses and re-sort the list of ConstructUses @@ -86,7 +83,6 @@ class ConstructListModel { _updateUsesList(newUses); _updateConstructMap(newUses); _updateConstructList(); - _updateCategoriesToUses(); _updateMetrics(offset); } catch (err, s) { ErrorHandler.logError( @@ -157,22 +153,13 @@ class ConstructListModel { _constructList.sort(_sortConstructs); } - void _updateCategoriesToUses() { - _categoriesToUses = {}; - for (final ConstructUses use in constructList()) { - final category = use.category; - _categoriesToUses.putIfAbsent(category, () => []); - _categoriesToUses[category]!.add(use); - } - } - void _updateMetrics(int offset) { - vocabLemmasList = constructList(type: ConstructTypeEnum.vocab) + _vocabLemmasList = constructList(type: ConstructTypeEnum.vocab) .map((e) => e.lemma) .toSet() .toList(); - grammarLemmasList = constructList(type: ConstructTypeEnum.morph) + _grammarLemmasList = constructList(type: ConstructTypeEnum.morph) .map((e) => e.lemma) .toSet() .toList(); @@ -262,19 +249,6 @@ class ConstructListModel { ) .toList(); - Map> categoriesToUses({ConstructTypeEnum? type}) { - if (type == null) return _categoriesToUses; - final entries = _categoriesToUses.entries.toList(); - return Map.fromEntries( - entries.map((entry) { - return MapEntry( - entry.key, - entry.value.where((use) => use.constructType == type).toList(), - ); - }).where((entry) => entry.value.isNotEmpty), - ); - } - // uses where points < AnalyticConstants.xpForGreens List get seeds => _constructList .where( diff --git a/lib/pangea/analytics_misc/construct_type_enum.dart b/lib/pangea/analytics_misc/construct_type_enum.dart index d94ad25c0..38b0e0eab 100644 --- a/lib/pangea/analytics_misc/construct_type_enum.dart +++ b/lib/pangea/analytics_misc/construct_type_enum.dart @@ -3,6 +3,8 @@ import 'dart:developer'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; + import 'package:fluffychat/pangea/analytics_misc/analytics_constants.dart'; import 'package:fluffychat/pangea/analytics_summary/progress_indicators_enum.dart'; import 'package:fluffychat/pangea/morphs/morph_features_enum.dart'; @@ -26,6 +28,16 @@ extension ConstructExtension on ConstructTypeEnum { } } + String sheetname(BuildContext context) { + final l10n = L10n.of(context); + switch (this) { + case ConstructTypeEnum.vocab: + return l10n.vocab; + case ConstructTypeEnum.morph: + return l10n.grammar; + } + } + int get maxXPPerLemma { switch (this) { case ConstructTypeEnum.vocab: diff --git a/lib/pangea/analytics_misc/construct_use_type_enum.dart b/lib/pangea/analytics_misc/construct_use_type_enum.dart index d0c2eec3b..1099e3cdb 100644 --- a/lib/pangea/analytics_misc/construct_use_type_enum.dart +++ b/lib/pangea/analytics_misc/construct_use_type_enum.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:fluffychat/pangea/analytics_downloads/analytics_summary_enum.dart'; +import 'package:fluffychat/pangea/analytics_downloads/space_analytics_summary_enum.dart'; import 'package:fluffychat/pangea/analytics_misc/learning_skills_enum.dart'; import 'package:fluffychat/pangea/practice_activities/activity_type_enum.dart'; @@ -327,14 +327,14 @@ extension ConstructUseTypeExtension on ConstructUseTypeEnum { } } - AnalyticsSummaryEnum? get summaryEnumType { + SpaceAnalyticsSummaryEnum? get summaryEnumType { switch (this) { case ConstructUseTypeEnum.wa: case ConstructUseTypeEnum.ga: case ConstructUseTypeEnum.ta: case ConstructUseTypeEnum.unk: case ConstructUseTypeEnum.pvm: - return AnalyticsSummaryEnum.numWordsTyped; + return SpaceAnalyticsSummaryEnum.numWordsTyped; case ConstructUseTypeEnum.corIt: case ConstructUseTypeEnum.corPA: @@ -345,7 +345,7 @@ extension ConstructUseTypeExtension on ConstructUseTypeEnum { case ConstructUseTypeEnum.corM: case ConstructUseTypeEnum.em: case ConstructUseTypeEnum.corMM: - return AnalyticsSummaryEnum.numChoicesCorrect; + return SpaceAnalyticsSummaryEnum.numChoicesCorrect; case ConstructUseTypeEnum.incIt: case ConstructUseTypeEnum.incIGC: @@ -355,7 +355,7 @@ extension ConstructUseTypeExtension on ConstructUseTypeEnum { case ConstructUseTypeEnum.incL: case ConstructUseTypeEnum.incM: case ConstructUseTypeEnum.incMM: - return AnalyticsSummaryEnum.numChoicesIncorrect; + return SpaceAnalyticsSummaryEnum.numChoicesIncorrect; case ConstructUseTypeEnum.ignIt: case ConstructUseTypeEnum.ignPA: diff --git a/lib/pangea/chat_settings/pages/pangea_chat_details.dart b/lib/pangea/chat_settings/pages/pangea_chat_details.dart index 42a48efc2..49e22be3f 100644 --- a/lib/pangea/chat_settings/pages/pangea_chat_details.dart +++ b/lib/pangea/chat_settings/pages/pangea_chat_details.dart @@ -14,7 +14,7 @@ import 'package:fluffychat/pangea/chat_settings/utils/download_file.dart'; import 'package:fluffychat/pangea/chat_settings/widgets/class_details_toggle_add_students_tile.dart'; import 'package:fluffychat/pangea/chat_settings/widgets/class_name_header.dart'; import 'package:fluffychat/pangea/chat_settings/widgets/conversation_bot/conversation_bot_settings.dart'; -import 'package:fluffychat/pangea/chat_settings/widgets/download_analytics_button.dart'; +import 'package:fluffychat/pangea/chat_settings/widgets/download_space_analytics_button.dart'; import 'package:fluffychat/pangea/chat_settings/widgets/room_capacity_button.dart'; import 'package:fluffychat/pangea/chat_settings/widgets/visibility_toggle.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart'; @@ -348,7 +348,7 @@ class PangeaChatDetailsView extends StatelessWidget { controller: controller, ), if (room.isSpace && room.isRoomAdmin && kIsWeb) - DownloadAnalyticsButton(space: room), + DownloadSpaceAnalyticsButton(space: room), Divider(color: theme.dividerColor, height: 1), if (room.isRoomAdmin && !room.isSpace) ListTile( diff --git a/lib/pangea/chat_settings/widgets/download_analytics_button.dart b/lib/pangea/chat_settings/widgets/download_space_analytics_button.dart similarity index 84% rename from lib/pangea/chat_settings/widgets/download_analytics_button.dart rename to lib/pangea/chat_settings/widgets/download_space_analytics_button.dart index fb4d5863c..ac11a5cd2 100644 --- a/lib/pangea/chat_settings/widgets/download_analytics_button.dart +++ b/lib/pangea/chat_settings/widgets/download_space_analytics_button.dart @@ -3,12 +3,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:matrix/matrix.dart'; -import 'package:fluffychat/pangea/spaces/widgets/download_analytics_dialog.dart'; +import 'package:fluffychat/pangea/spaces/widgets/download_space_analytics_dialog.dart'; -class DownloadAnalyticsButton extends StatelessWidget { +class DownloadSpaceAnalyticsButton extends StatelessWidget { final Room space; - const DownloadAnalyticsButton({ + const DownloadSpaceAnalyticsButton({ super.key, required this.space, }); diff --git a/lib/pangea/spaces/widgets/download_analytics_dialog.dart b/lib/pangea/spaces/widgets/download_space_analytics_dialog.dart similarity index 91% rename from lib/pangea/spaces/widgets/download_analytics_dialog.dart rename to lib/pangea/spaces/widgets/download_space_analytics_dialog.dart index 81a798340..a7a14bb59 100644 --- a/lib/pangea/spaces/widgets/download_analytics_dialog.dart +++ b/lib/pangea/spaces/widgets/download_space_analytics_dialog.dart @@ -7,8 +7,8 @@ import 'package:matrix/matrix.dart'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/themes.dart'; -import 'package:fluffychat/pangea/analytics_downloads/analytics_summary_enum.dart'; -import 'package:fluffychat/pangea/analytics_misc/analytics_summary_model.dart'; +import 'package:fluffychat/pangea/analytics_downloads/space_analytics_summary_enum.dart'; +import 'package:fluffychat/pangea/analytics_downloads/space_analytics_summary_model.dart'; import 'package:fluffychat/pangea/analytics_misc/construct_list_model.dart'; import 'package:fluffychat/pangea/analytics_misc/construct_use_model.dart'; import 'package:fluffychat/pangea/analytics_misc/constructs_model.dart'; @@ -120,20 +120,22 @@ class DownloadAnalyticsDialogState extends State { _downloading = true; }); - final List summaries = []; + final List summaries = []; await for (final batch in widget.space.getNextAnalyticsRoomBatch(userL2!)) { if (batch.isEmpty) continue; - final List batchSummaries = await Future.wait( + final List batchSummaries = + await Future.wait( batch.map((r) => _getAnalyticsModel(r)), ); - summaries.addAll(batchSummaries.whereType()); + summaries + .addAll(batchSummaries.whereType()); } for (final userID in _downloadStatuses.keys) { if (_downloadStatuses[userID] == 0) { _downloadStatuses[userID] = -1; - summaries.add(AnalyticsSummaryModel.emptyModel(userID)); + summaries.add(SpaceAnalyticsSummaryModel.emptyModel(userID)); } } @@ -161,7 +163,7 @@ class DownloadAnalyticsDialogState extends State { } Future _downloadSpaceAnalytics( - List summaries, + List summaries, ) async { final content = _downloadType == DownloadType.xlsx ? _getExcelFileContent(summaries) @@ -177,11 +179,13 @@ class DownloadAnalyticsDialogState extends State { ); } - Future _getAnalyticsModel(Room analyticsRoom) async { + Future _getAnalyticsModel( + Room analyticsRoom, + ) async { final String? userID = analyticsRoom.creatorId; if (userID == null) return null; - AnalyticsSummaryModel? summary; + SpaceAnalyticsSummaryModel? summary; try { _downloadStatuses[userID] = 1; if (mounted) setState(() {}); @@ -192,7 +196,7 @@ class DownloadAnalyticsDialogState extends State { if (constructEvents == null) { if (mounted) setState(() => _downloadStatuses[userID] = -1); - return AnalyticsSummaryModel.emptyModel(userID); + return SpaceAnalyticsSummaryModel.emptyModel(userID); } final List uses = []; @@ -201,7 +205,7 @@ class DownloadAnalyticsDialogState extends State { } final constructs = ConstructListModel(uses: uses); - summary = AnalyticsSummaryModel.fromConstructListModel( + summary = SpaceAnalyticsSummaryModel.fromConstructListModel( userID, constructs, getCopy, @@ -238,11 +242,11 @@ class DownloadAnalyticsDialogState extends State { } List _formatExcelRow( - AnalyticsSummaryModel summary, + SpaceAnalyticsSummaryModel summary, ) { final List row = []; - for (int i = 0; i < AnalyticsSummaryEnum.values.length; i++) { - final key = AnalyticsSummaryEnum.values[i]; + for (int i = 0; i < SpaceAnalyticsSummaryEnum.values.length; i++) { + final key = SpaceAnalyticsSummaryEnum.values[i]; final value = summary.getValue(key, context); if (value is int) { row.add(IntCellValue(value)); @@ -256,12 +260,12 @@ class DownloadAnalyticsDialogState extends State { } List _getExcelFileContent( - List summaries, + List summaries, ) { final excel = Excel.createExcel(); final sheet = excel['Sheet1']; - for (final key in AnalyticsSummaryEnum.values) { + for (final key in SpaceAnalyticsSummaryEnum.values) { sheet .cell( CellIndex.indexByColumnRow( @@ -287,19 +291,19 @@ class DownloadAnalyticsDialogState extends State { } String _getCSVFileContent( - List summaries, + List summaries, ) { final List> rows = []; final headerRow = []; - for (final key in AnalyticsSummaryEnum.values) { + for (final key in SpaceAnalyticsSummaryEnum.values) { headerRow.add(key.header(L10n.of(context))); } rows.add(headerRow); for (final summary in summaries) { final row = []; - for (int i = 0; i < AnalyticsSummaryEnum.values.length; i++) { - final key = AnalyticsSummaryEnum.values[i]; + for (int i = 0; i < SpaceAnalyticsSummaryEnum.values.length; i++) { + final key = SpaceAnalyticsSummaryEnum.values[i]; final value = summary.getValue(key, context); if (value == null) continue; value is List ? row.add(value.join(", ")) : row.add(value);