diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index e5f694652..c1310a016 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -4595,5 +4595,32 @@ "mandatoryUpdateRequiredDesc": "A new version of the app is required to continue. Please update now to proceed.", "updateAvailableDesc": "A new version of the app is available. Update now for the latest features and improvements!", "updateNow": "Update Now", - "updateLater": "Later" + "updateLater": "Later", + "constructUseWaDesc": "Used without help", + "constructUseGaDesc": "Grammar mistake", + "constructUseUnkDesc": "Unknown", + "constructUseCorITDesc": "Correct in translation", + "constructUseIgnITDesc": "Ignored in translation", + "constructUseIncITDesc": "Incorrect in translation", + "constructUseIgnIGCDesc": "Ignored in grammar correction", + "constructUseCorIGCDesc": "Correct in grammar correction", + "constructUseIncIGCDesc": "Incorrect in grammar correction", + "constructUseCorPADesc": "Correct in word meaning activity", + "constructUseIgnPADesc": "Ignored in word meaning activity", + "constructUseIncPADesc": "Incorrect in word meaning activity", + "constructUseCorWLDesc": "Correct in word listening activity", + "constructUseIncWLDesc": "Incorrect in word listening activity", + "constructUseIngWLDesc": "Ignored in word listening activity", + "constructUseCorHWLDesc": "Correct in hidden word activity", + "constructUseIncHWLDesc": "Incorrect in hidden word activity", + "constructUseIgnHWLDesc": "Ignored in hidden word activity", + "constructUseNanDesc": "Not applicable", + "xpIntoLevel": "{currentXP} / {maxXP} XP", + "@xpIntoLevel": { + "type": "text", + "placeholders": { + "currentXP": {}, + "maxXP": {} + } + } } diff --git a/lib/pangea/controllers/get_analytics_controller.dart b/lib/pangea/controllers/get_analytics_controller.dart index 2c55b203b..2372f96bf 100644 --- a/lib/pangea/controllers/get_analytics_controller.dart +++ b/lib/pangea/controllers/get_analytics_controller.dart @@ -50,6 +50,8 @@ class GetAnalyticsController { return _calculateMinXpForLevel(constructListModel.level + 1); } + int get minXPForNextLevel => _minXPForNextLevel; + /// Calculates the minimum XP required for a specific level. int _calculateMinXpForLevel(int level) { if (level == 1) return 0; // Ensure level 1 starts at 0 XP diff --git a/lib/pangea/enum/construct_use_type_enum.dart b/lib/pangea/enum/construct_use_type_enum.dart index ee3871f92..9f45e63bd 100644 --- a/lib/pangea/enum/construct_use_type_enum.dart +++ b/lib/pangea/enum/construct_use_type_enum.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; enum ConstructUseTypeEnum { /// produced in chat by user, igc was run, and we've judged it to be a correct use @@ -64,6 +65,49 @@ enum ConstructUseTypeEnum { extension ConstructUseTypeExtension on ConstructUseTypeEnum { String get string => toString().split('.').last; + String description(BuildContext context) { + switch (this) { + case ConstructUseTypeEnum.wa: + return L10n.of(context).constructUseWaDesc; + case ConstructUseTypeEnum.ga: + return L10n.of(context).constructUseGaDesc; + case ConstructUseTypeEnum.unk: + return L10n.of(context).constructUseUnkDesc; + case ConstructUseTypeEnum.corIt: + return L10n.of(context).constructUseCorITDesc; + case ConstructUseTypeEnum.ignIt: + return L10n.of(context).constructUseIgnITDesc; + case ConstructUseTypeEnum.incIt: + return L10n.of(context).constructUseIncITDesc; + case ConstructUseTypeEnum.ignIGC: + return L10n.of(context).constructUseIgnIGCDesc; + case ConstructUseTypeEnum.corIGC: + return L10n.of(context).constructUseCorIGCDesc; + case ConstructUseTypeEnum.incIGC: + return L10n.of(context).constructUseIncIGCDesc; + case ConstructUseTypeEnum.corPA: + return L10n.of(context).constructUseCorPADesc; + case ConstructUseTypeEnum.ignPA: + return L10n.of(context).constructUseIgnPADesc; + case ConstructUseTypeEnum.incPA: + return L10n.of(context).constructUseIncPADesc; + case ConstructUseTypeEnum.corWL: + return L10n.of(context).constructUseCorWLDesc; + case ConstructUseTypeEnum.incWL: + return L10n.of(context).constructUseIncWLDesc; + case ConstructUseTypeEnum.ignWL: + return L10n.of(context).constructUseIngWLDesc; + case ConstructUseTypeEnum.corHWL: + return L10n.of(context).constructUseCorHWLDesc; + case ConstructUseTypeEnum.incHWL: + return L10n.of(context).constructUseIncHWLDesc; + case ConstructUseTypeEnum.ignHWL: + return L10n.of(context).constructUseIgnHWLDesc; + case ConstructUseTypeEnum.nan: + return L10n.of(context).constructUseNanDesc; + } + } + IconData get icon { switch (this) { case ConstructUseTypeEnum.wa: diff --git a/lib/pangea/models/analytics/construct_list_model.dart b/lib/pangea/models/analytics/construct_list_model.dart index 20356acb9..184d0de1e 100644 --- a/lib/pangea/models/analytics/construct_list_model.dart +++ b/lib/pangea/models/analytics/construct_list_model.dart @@ -19,8 +19,12 @@ class ConstructListModel { level = 0; vocabLemmas = 0; grammarLemmas = 0; + _uses.clear(); } + final List _uses = []; + List get uses => _uses; + /// A map of lemmas to ConstructUses, each of which contains a lemma /// key = lemmma + constructType.string, value = ConstructUses Map _constructMap = {}; @@ -49,6 +53,7 @@ class ConstructListModel { /// IDs to ConstructUses and re-sort the list of ConstructUses void updateConstructs(List newUses) { try { + _updateUsesList(newUses); _updateConstructMap(newUses); _updateConstructList(); _updateCategoriesToUses(); @@ -64,6 +69,11 @@ class ConstructListModel { return a.lemma.compareTo(b.lemma); } + void _updateUsesList(List newUses) { + newUses.sort((a, b) => a.timeStamp.compareTo(b.timeStamp)); + _uses.addAll(newUses); + } + /// A map of lemmas to ConstructUses, each of which contains a lemma /// key = lemmma + constructType.string, value = ConstructUses void _updateConstructMap(final List newUses) { diff --git a/lib/pangea/widgets/animations/progress_bar/animated_level_dart.dart b/lib/pangea/widgets/animations/progress_bar/animated_level_dart.dart index f7d7fed4e..ab2da5927 100644 --- a/lib/pangea/widgets/animations/progress_bar/animated_level_dart.dart +++ b/lib/pangea/widgets/animations/progress_bar/animated_level_dart.dart @@ -101,9 +101,10 @@ class AnimatedLevelBarState extends State ), Positioned( top: 2, + left: 8, child: Container( height: 6, - width: _animation.value >= 8 ? _animation.value - 8 : 0, + width: _animation.value >= 16 ? _animation.value - 16 : 0, decoration: BoxDecoration( color: widget.highlightColor, borderRadius: const BorderRadius.all( diff --git a/lib/pangea/widgets/animations/progress_bar/progress_bar.dart b/lib/pangea/widgets/animations/progress_bar/progress_bar.dart index a8383bae5..ed517ca20 100644 --- a/lib/pangea/widgets/animations/progress_bar/progress_bar.dart +++ b/lib/pangea/widgets/animations/progress_bar/progress_bar.dart @@ -8,10 +8,12 @@ import 'package:flutter/material.dart'; class ProgressBar extends StatefulWidget { final List levelBars; + final double? height; const ProgressBar({ super.key, required this.levelBars, + this.height, }); @override @@ -29,6 +31,7 @@ class ProgressBarState extends State { get progressBarDetails => ProgressBarDetails( totalWidth: width, borderColor: Theme.of(context).colorScheme.primary.withOpacity(0.5), + height: widget.height ?? 14, ); @override diff --git a/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_bar.dart b/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_bar.dart index 108195833..f3a9e7fcc 100644 --- a/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_bar.dart +++ b/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_bar.dart @@ -4,15 +4,21 @@ import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; class LearningProgressBar extends StatelessWidget { + final int level; final int totalXP; + final double? height; + const LearningProgressBar({ + required this.level, required this.totalXP, + this.height, super.key, }); @override Widget build(BuildContext context) { return ProgressBar( + height: height, levelBars: [ LevelBarDetails( fillColor: Theme.of(context).colorScheme.primary, 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 bbf1081f3..37321701c 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 @@ -9,6 +9,7 @@ import 'package:fluffychat/pangea/widgets/chat_list/analytics_summary/analytics_ import 'package:fluffychat/pangea/widgets/chat_list/analytics_summary/learning_progress_bar.dart'; import 'package:fluffychat/pangea/widgets/chat_list/analytics_summary/learning_settings_button.dart'; import 'package:fluffychat/pangea/widgets/chat_list/analytics_summary/level_badge.dart'; +import 'package:fluffychat/pangea/widgets/chat_list/analytics_summary/level_bar_popup.dart'; import 'package:fluffychat/pangea/widgets/chat_list/analytics_summary/progress_indicator.dart'; import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; @@ -131,23 +132,35 @@ class LearningProgressIndicatorsState ], ), const SizedBox(height: 6), - SizedBox( - height: 26, - child: Stack( - alignment: Alignment.center, - children: [ - Positioned( - left: 16, - right: 0, - child: LearningProgressBar( - totalXP: _constructsModel.totalXP, - ), - ), - Positioned( - left: 0, - child: LevelBadge(level: _constructsModel.level), + MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: () { + showDialog( + context: context, + builder: (c) => const LevelBarPopup(), + ); + }, + child: SizedBox( + height: 26, + child: Stack( + alignment: Alignment.center, + children: [ + Positioned( + left: 16, + right: 0, + child: LearningProgressBar( + level: _constructsModel.level, + totalXP: _constructsModel.totalXP, + ), + ), + Positioned( + left: 0, + child: LevelBadge(level: _constructsModel.level), + ), + ], ), - ], + ), ), ), ], diff --git a/lib/pangea/widgets/chat_list/analytics_summary/level_bar_popup.dart b/lib/pangea/widgets/chat_list/analytics_summary/level_bar_popup.dart new file mode 100644 index 000000000..af812777f --- /dev/null +++ b/lib/pangea/widgets/chat_list/analytics_summary/level_bar_popup.dart @@ -0,0 +1,162 @@ +import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/pangea/controllers/get_analytics_controller.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/utils/grammar/get_grammar_copy.dart'; +import 'package:fluffychat/pangea/widgets/chat_list/analytics_summary/learning_progress_bar.dart'; +import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; + +class LevelBarPopup extends StatelessWidget { + const LevelBarPopup({ + super.key, + }); + + GetAnalyticsController get getAnalyticsController => + MatrixState.pangeaController.getAnalytics; + int get level => getAnalyticsController.constructListModel.level; + int get totalXP => getAnalyticsController.constructListModel.totalXP; + int get maxLevelXP => getAnalyticsController.minXPForNextLevel; + List get uses => + getAnalyticsController.constructListModel.uses; + + @override + Widget build(BuildContext context) { + return Dialog( + child: ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 400, + maxHeight: 600, + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(20.0), + child: Scaffold( + appBar: AppBar( + titleSpacing: 0, + automaticallyImplyLeading: false, + title: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + const CircleAvatar( + radius: 20, + backgroundColor: AppConfig.gold, + child: Icon( + size: 30, + Icons.star, + color: Colors.white, + ), + ), + const SizedBox(width: 10), + Text( + L10n.of(context).levelShort(level), + style: const TextStyle( + fontSize: 24, + fontWeight: FontWeight.w900, + color: AppConfig.gold, + ), + ), + ], + ), + Opacity( + opacity: 0.25, + child: Text( + L10n.of(context).levelShort(level + 1), + style: const TextStyle( + fontSize: 24, + fontWeight: FontWeight.w900, + ), + ), + ), + ], + ), + ), + ), + body: Column( + children: [ + Padding( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LearningProgressBar( + height: 24, + level: level, + totalXP: totalXP, + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 6, + ), + child: Text( + L10n.of(context).xpIntoLevel(totalXP, maxLevelXP), + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.w900, + color: AppConfig.gold, + ), + ), + ), + const Divider(), + ], + ), + ), + Expanded( + child: ListView.builder( + itemCount: uses.length, + itemBuilder: (context, index) { + final use = uses[(uses.length - 1) - index]; + String lemmaCopy = use.lemma; + if (use.constructType == ConstructTypeEnum.morph) { + lemmaCopy = getGrammarCopy( + category: use.category, + lemma: use.lemma, + context: context, + ) ?? + use.lemma; + } + return ListTile( + leading: Icon(use.useType.icon), + title: Text( + "\"$lemmaCopy\" - ${use.useType.description(context)}", + style: const TextStyle(fontSize: 14), + ), + trailing: Container( + alignment: Alignment.centerRight, + width: 60, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "${use.pointValue > 0 ? '+' : ''}${use.pointValue}", + ), + const SizedBox(width: 5), + const CircleAvatar( + radius: 10, + child: Icon( + size: 12, + Icons.star, + color: Colors.white, + ), + ), + ], + ), + ), + ); + }, + ), + ), + ], + ), + ), + ), + ), + ); + } +}