feat: change UI of level up slightly and add animated bar before details summary

WIP: add a level up bar animation that plays before details screen when level up banner is clicked on. Also changed banner icon and background color
This commit is contained in:
avashilling 2025-06-04 16:15:13 -04:00
parent 01d797e53f
commit c13a316a31

View file

@ -1,19 +1,19 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:audioplayers/audioplayers.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/pangea/analytics_misc/analytics_constants.dart';
import 'package:fluffychat/pangea/analytics_misc/learning_skills_enum.dart';
import 'package:fluffychat/pangea/analytics_summary/progress_bar/progress_bar.dart';
import 'package:fluffychat/pangea/analytics_summary/progress_bar/progress_bar_details.dart';
import 'package:fluffychat/pangea/common/config/environment.dart';
import 'package:fluffychat/pangea/common/utils/overlay.dart';
import 'package:fluffychat/pangea/constructs/construct_repo.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
class LevelUpConstants {
static const String starFileName = "star.png";
@ -88,6 +88,7 @@ class LevelUpBannerState extends State<LevelUpBanner>
bool _showDetails = false;
bool _showedDetails = false;
bool _showingLevelingAnimation = false;
ConstructSummary? _constructSummary;
String? _error;
@ -177,9 +178,15 @@ class LevelUpBannerState extends State<LevelUpBanner>
if (!Environment.isStagingEnvironment) return;
if (mounted) {
if (!_showedDetails) {
setState(() {
_showingLevelingAnimation = true;
});
}
setState(() {
_showDetails = !_showDetails;
if (_showDetails && !_showedDetails) {
if (_showDetails && _showedDetails) {
_showedDetails = true;
}
});
@ -188,6 +195,14 @@ class LevelUpBannerState extends State<LevelUpBanner>
? _sizeController.forward()
: _sizeController.reverse());
if (_showDetails && _showingLevelingAnimation) {
await Future.delayed(const Duration(seconds: 2));
if (!mounted) return;
setState(() {
_showingLevelingAnimation = false;
});
}
if (!_showDetails) {
await Future.delayed(
const Duration(milliseconds: 300),
@ -240,9 +255,10 @@ class LevelUpBannerState extends State<LevelUpBanner>
top: 16,
),
decoration: BoxDecoration(
color: Theme.of(context)
.colorScheme
.secondaryContainer,
color:
Theme.of(context).brightness == Brightness.dark
? Colors.black
: Colors.white,
borderRadius: BorderRadius.circular(8),
),
padding: const EdgeInsets.symmetric(
@ -258,10 +274,8 @@ class LevelUpBannerState extends State<LevelUpBanner>
text: TextSpan(
children: [
TextSpan(
text: L10n.of(context)
.congratulationsOnReaching(
widget.level,
),
//Hardcoded for now, put in translations later
text: "Level up",
style: style,
),
TextSpan(
@ -287,7 +301,7 @@ class LevelUpBannerState extends State<LevelUpBanner>
duration: FluffyThemes.animationDuration,
child: _error == null
? FluffyThemes.isColumnMode(context)
? ElevatedButton(
? IconButton(
style: IconButton.styleFrom(
padding: const EdgeInsets
.symmetric(
@ -296,8 +310,11 @@ class LevelUpBannerState extends State<LevelUpBanner>
),
),
onPressed: _toggleDetails,
child: Text(
L10n.of(context).details,
icon: Icon(
Icons.arrow_drop_down,
color: Theme.of(context)
.colorScheme
.onSurface,
),
)
: SizedBox(
@ -337,10 +354,6 @@ class LevelUpBannerState extends State<LevelUpBanner>
],
),
),
IconButton(
icon: const Icon(Icons.close),
onPressed: _close,
),
],
),
],
@ -350,156 +363,168 @@ class LevelUpBannerState extends State<LevelUpBanner>
SizeTransition(
sizeFactor: _sizeAnimation,
child: Container(
constraints: BoxConstraints(
maxHeight:
MediaQuery.of(context).size.height * 0.75,
),
height: MediaQuery.of(context).size.height * 0.75,
width: MediaQuery.of(context).size.width * .5,
margin: const EdgeInsets.only(
top: 4.0,
),
decoration: BoxDecoration(
color: Colors.black,
color:
Theme.of(context).brightness == Brightness.dark
? Colors.black
: Colors.white,
borderRadius: BorderRadius.circular(8),
),
padding: const EdgeInsets.all(16),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
spacing: 24.0,
children: [
Table(
columnWidths: const {
0: IntrinsicColumnWidth(),
1: FlexColumnWidth(),
2: IntrinsicColumnWidth(),
},
defaultVerticalAlignment:
TableCellVerticalAlignment.middle,
children: [
...LearningSkillsEnum.values
.where(
(v) =>
v.isVisible && _skillsPoints(v) > -1,
)
.map((skill) {
return TableRow(
child: _showingLevelingAnimation
? const Expanded(
child: LevelUpBarAnimation(),
)
: SingleChildScrollView(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.center,
spacing: 24.0,
children: [
Table(
columnWidths: const {
0: IntrinsicColumnWidth(),
1: FlexColumnWidth(),
2: IntrinsicColumnWidth(),
},
defaultVerticalAlignment:
TableCellVerticalAlignment.middle,
children: [
Padding(
padding: const EdgeInsets.symmetric(
vertical: 9.0,
horizontal: 18.0,
),
child: Icon(
skill.icon,
size: 25,
color: Colors.white,
),
),
Padding(
padding: const EdgeInsets.symmetric(
vertical: 9.0,
horizontal: 18.0,
),
child: Text(
skill.tooltip(context),
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Colors.white,
),
textAlign: TextAlign.center,
),
),
Padding(
padding: const EdgeInsets.symmetric(
vertical: 9.0,
horizontal: 18.0,
),
child: Text(
"+ ${_skillsPoints(skill)} XP",
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Colors.white,
),
textAlign: TextAlign.center,
),
),
...LearningSkillsEnum.values
.where(
(v) =>
v.isVisible &&
_skillsPoints(v) > -1,
)
.map((skill) {
return TableRow(
children: [
Padding(
padding: const EdgeInsets
.symmetric(
vertical: 9.0,
horizontal: 18.0,
),
child: Icon(
skill.icon,
size: 25,
color: Theme.of(context)
.colorScheme
.onSurface,
),
),
Padding(
padding: const EdgeInsets
.symmetric(
vertical: 9.0,
horizontal: 18.0,
),
child: Text(
skill.tooltip(context),
style: const TextStyle(
fontSize: 16,
fontWeight:
FontWeight.w600,
),
textAlign: TextAlign.center,
),
),
Padding(
padding: const EdgeInsets
.symmetric(
vertical: 9.0,
horizontal: 18.0,
),
child: Text(
"+ ${_skillsPoints(skill)} XP",
style: const TextStyle(
fontSize: 16,
fontWeight:
FontWeight.w600,
),
textAlign: TextAlign.center,
),
),
],
);
}),
],
);
}),
],
),
CachedNetworkImage(
imageUrl:
"${AppConfig.assetsBaseURL}/${LevelUpConstants.dinoLevelUPFileName}",
width: 400,
fit: BoxFit.cover,
),
if (_constructSummary?.textSummary != null)
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Theme.of(context)
.colorScheme
.secondaryContainer,
borderRadius: BorderRadius.circular(8),
),
child: Text(
_constructSummary!.textSummary,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Theme.of(context)
.colorScheme
.onSecondaryContainer,
),
textAlign: TextAlign.center,
),
CachedNetworkImage(
imageUrl:
"${AppConfig.assetsBaseURL}/${LevelUpConstants.dinoLevelUPFileName}",
width: 400,
fit: BoxFit.cover,
),
if (_constructSummary?.textSummary !=
null)
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.transparent,
borderRadius:
BorderRadius.circular(8),
),
child: Text(
_constructSummary!.textSummary,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w400,
color: Theme.of(context)
.colorScheme
.onSecondaryContainer,
),
textAlign: TextAlign.center,
),
),
const SizedBox(
height: 24,
),
// Share button, currently no functionality
// ElevatedButton(
// onPressed: () {
// // Add share functionality
// },
// style: ElevatedButton.styleFrom(
// backgroundColor: Colors.white,
// foregroundColor: Colors.black,
// padding: const EdgeInsets.symmetric(
// vertical: 12,
// horizontal: 24,
// ),
// shape: RoundedRectangleBorder(
// borderRadius: BorderRadius.circular(8),
// ),
// ),
// child: const Row(
// mainAxisSize: MainAxisSize
// .min,
// children: [
// Text(
// "Share with Friends",
// style: TextStyle(
// fontSize: 16,
// fontWeight: FontWeight.bold,
// ),
// ),
// SizedBox(
// width: 8,
// ),
// Icon(
// Icons.ios_share,
// size: 20,
// ),
// ),
// ),
// ),
],
),
const SizedBox(
height: 24,
),
// Share button, currently no functionality
// ElevatedButton(
// onPressed: () {
// // Add share functionality
// },
// style: ElevatedButton.styleFrom(
// backgroundColor: Colors.white,
// foregroundColor: Colors.black,
// padding: const EdgeInsets.symmetric(
// vertical: 12,
// horizontal: 24,
// ),
// shape: RoundedRectangleBorder(
// borderRadius: BorderRadius.circular(8),
// ),
// ),
// child: const Row(
// mainAxisSize: MainAxisSize
// .min,
// children: [
// Text(
// "Share with Friends",
// style: TextStyle(
// fontSize: 16,
// fontWeight: FontWeight.bold,
// ),
// ),
// SizedBox(
// width: 8,
// ),
// Icon(
// Icons.ios_share,
// size: 20,
// ),
// ),
// ),
// ),
],
),
),
),
),
],
@ -513,3 +538,61 @@ class LevelUpBannerState extends State<LevelUpBanner>
);
}
}
//animated progress bar -- move to own file later
class LevelUpBarAnimation extends StatefulWidget {
const LevelUpBarAnimation({super.key});
@override
State<LevelUpBarAnimation> createState() => _LevelUpBarAnimationState();
}
class _LevelUpBarAnimationState extends State<LevelUpBarAnimation>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 1500),
vsync: this,
);
_animation = Tween<double>(begin: 0, end: 1).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
);
_controller.forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animation,
builder: (context, _) {
return Padding(
padding: const EdgeInsets.all(24),
child: ProgressBar(
levelBars: [
LevelBarDetails(
widthMultiplier: _animation.value,
currentPoints: 0,
fillColor: AppConfig.goldLight,
),
],
height: 20,
),
);
},
);
}
}