added level bar popup with XP, level, and recent construct use info (#1217)
This commit is contained in:
parent
c845c971d6
commit
dee409e41d
9 changed files with 286 additions and 18 deletions
|
|
@ -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": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -19,8 +19,12 @@ class ConstructListModel {
|
|||
level = 0;
|
||||
vocabLemmas = 0;
|
||||
grammarLemmas = 0;
|
||||
_uses.clear();
|
||||
}
|
||||
|
||||
final List<OneConstructUse> _uses = [];
|
||||
List<OneConstructUse> get uses => _uses;
|
||||
|
||||
/// A map of lemmas to ConstructUses, each of which contains a lemma
|
||||
/// key = lemmma + constructType.string, value = ConstructUses
|
||||
Map<String, ConstructUses> _constructMap = {};
|
||||
|
|
@ -49,6 +53,7 @@ class ConstructListModel {
|
|||
/// IDs to ConstructUses and re-sort the list of ConstructUses
|
||||
void updateConstructs(List<OneConstructUse> newUses) {
|
||||
try {
|
||||
_updateUsesList(newUses);
|
||||
_updateConstructMap(newUses);
|
||||
_updateConstructList();
|
||||
_updateCategoriesToUses();
|
||||
|
|
@ -64,6 +69,11 @@ class ConstructListModel {
|
|||
return a.lemma.compareTo(b.lemma);
|
||||
}
|
||||
|
||||
void _updateUsesList(List<OneConstructUse> 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<OneConstructUse> newUses) {
|
||||
|
|
|
|||
|
|
@ -101,9 +101,10 @@ class AnimatedLevelBarState extends State<AnimatedLevelBar>
|
|||
),
|
||||
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(
|
||||
|
|
|
|||
|
|
@ -8,10 +8,12 @@ import 'package:flutter/material.dart';
|
|||
|
||||
class ProgressBar extends StatefulWidget {
|
||||
final List<LevelBarDetails> levelBars;
|
||||
final double? height;
|
||||
|
||||
const ProgressBar({
|
||||
super.key,
|
||||
required this.levelBars,
|
||||
this.height,
|
||||
});
|
||||
|
||||
@override
|
||||
|
|
@ -29,6 +31,7 @@ class ProgressBarState extends State<ProgressBar> {
|
|||
get progressBarDetails => ProgressBarDetails(
|
||||
totalWidth: width,
|
||||
borderColor: Theme.of(context).colorScheme.primary.withOpacity(0.5),
|
||||
height: widget.height ?? 14,
|
||||
);
|
||||
|
||||
@override
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
showDialog<LevelBarPopup>(
|
||||
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),
|
||||
),
|
||||
],
|
||||
),
|
||||
Positioned(
|
||||
left: 0,
|
||||
child: LevelBadge(level: _constructsModel.level),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -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<OneConstructUse> 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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue