Not functional, committing for code review

This commit is contained in:
avashilling 2025-06-12 12:37:39 -04:00
parent eaf13337a5
commit d49d08f67b
5 changed files with 382 additions and 319 deletions

View file

@ -4,22 +4,9 @@ import 'dart:async';
import 'dart:developer';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:collection/collection.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:emoji_picker_flutter/emoji_picker_flutter.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:go_router/go_router.dart';
import 'package:image_picker/image_picker.dart';
import 'package:matrix/matrix.dart';
import 'package:scroll_to_index/scroll_to_index.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:universal_html/html.dart' as html;
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/config/setting_keys.dart';
import 'package:fluffychat/config/themes.dart';
@ -30,7 +17,7 @@ import 'package:fluffychat/pages/chat_details/chat_details.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
import 'package:fluffychat/pangea/analytics_misc/constructs_model.dart';
import 'package:fluffychat/pangea/analytics_misc/gain_points_animation.dart';
import 'package:fluffychat/pangea/analytics_misc/level_up.dart';
import 'package:fluffychat/pangea/analytics_misc/level_up/level_up_banner.dart';
import 'package:fluffychat/pangea/analytics_misc/put_analytics_controller.dart';
import 'package:fluffychat/pangea/chat/utils/unlocked_morphs_snackbar.dart';
import 'package:fluffychat/pangea/chat/widgets/event_too_large_dialog.dart';
@ -66,6 +53,18 @@ import 'package:fluffychat/widgets/adaptive_dialogs/show_text_input_dialog.dart'
import 'package:fluffychat/widgets/future_loading_dialog.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:fluffychat/widgets/share_scaffold_dialog.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:go_router/go_router.dart';
import 'package:image_picker/image_picker.dart';
import 'package:matrix/matrix.dart';
import 'package:scroll_to_index/scroll_to_index.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:universal_html/html.dart' as html;
import '../../utils/account_bundles.dart';
import '../../utils/localized_exception_extension.dart';
import 'send_file_dialog.dart';

View file

@ -8,7 +8,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
//import for testing level up
import '../../pangea/analytics_misc/level_up.dart';
import '../../pangea/analytics_misc/level_up/level_up_banner.dart';
import 'settings_chat.dart';
class SettingsChatView extends StatelessWidget {
@ -34,8 +34,8 @@ class SettingsChatView extends StatelessWidget {
// Test button for leveling up
onPressed: () {
LevelUpUtil.showLevelUpDialog(
5,
4,
3,
context,
);
},

View file

@ -0,0 +1,267 @@
import 'dart:async';
import 'package:audioplayers/audioplayers.dart';
import 'package:cached_network_image/cached_network_image.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/level_up/level_up_manager.dart';
import 'package:fluffychat/pangea/analytics_misc/level_up/level_up_popup.dart';
import 'package:fluffychat/pangea/common/config/environment.dart';
import 'package:fluffychat/pangea/common/utils/overlay.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/material.dart';
class LevelUpConstants {
static const String starFileName = "star.png";
static const String dinoLevelUPFileName = "DinoBot-Congratulate.png";
}
class LevelUpUtil {
static Future<void> showLevelUpDialog(
int level,
int prevLevel,
BuildContext context,
) async {
final player = AudioPlayer();
final snackbarRegex = RegExp(r'_snackbar$');
while (MatrixState.pAnyState.activeOverlays
.any((overlayId) => snackbarRegex.hasMatch(overlayId))) {
await Future.delayed(const Duration(milliseconds: 100));
}
player
.play(
UrlSource(
"${AppConfig.assetsBaseURL}/${AnalyticsConstants.levelUpAudioFileName}",
),
)
.then(
(_) => Future.delayed(
const Duration(seconds: 2),
() => player.dispose(),
),
);
OverlayUtil.showOverlay(
overlayKey: "level_up_notification",
context: context,
child: LevelUpBanner(
level: level,
prevLevel: prevLevel,
backButtonOverride: IconButton(
icon: const Icon(Icons.close),
onPressed: () {
Navigator.of(context).pop();
},
),
),
transformTargetId: '',
position: OverlayPositionEnum.top,
backDropToDismiss: false,
closePrevOverlay: false,
canPop: false,
);
}
}
class LevelUpBanner extends StatefulWidget {
final int level;
final int prevLevel;
final Widget? backButtonOverride;
const LevelUpBanner({
required this.level,
required this.prevLevel,
required this.backButtonOverride,
super.key,
});
@override
LevelUpBannerState createState() => LevelUpBannerState();
}
class LevelUpBannerState extends State<LevelUpBanner>
with TickerProviderStateMixin {
late AnimationController _slideController;
late Animation<Offset> _slideAnimation;
bool _showedDetails = false;
@override
void initState() {
super.initState();
LevelUpManager().preloadAnalytics(
context,
widget.level,
widget.prevLevel,
);
LevelUpManager().shouldAutoPopup = true;
LevelUpManager().printAnalytics();
_slideController = AnimationController(
vsync: this,
duration: FluffyThemes.animationDuration,
);
_slideAnimation = Tween<Offset>(
begin: const Offset(0, -1),
end: Offset.zero,
).animate(
CurvedAnimation(
parent: _slideController,
curve: Curves.easeOut,
),
);
_slideController.forward();
Future.delayed(const Duration(seconds: 10), () async {
if (mounted && !_showedDetails) {}
_close();
});
}
Future<void> _close() async {
await _slideController.reverse();
MatrixState.pAnyState.closeOverlay("level_up_notification");
}
@override
void dispose() {
_slideController.dispose();
super.dispose();
}
Future<void> _toggleDetails() async {
await _close();
LevelUpManager().markPopupSeen();
_showedDetails = true;
await showDialog(
context: context,
builder: (context) => LevelUpPopup(widget: widget),
);
}
@override
Widget build(BuildContext context) {
final isColumnMode = FluffyThemes.isColumnMode(context);
final style = isColumnMode
? Theme.of(context).textTheme.titleLarge?.copyWith(
color: AppConfig.gold,
fontWeight: FontWeight.bold,
letterSpacing: 0.5,
)
: Theme.of(context).textTheme.bodyLarge?.copyWith(
color: AppConfig.gold,
fontWeight: FontWeight.bold,
letterSpacing: 0.5,
);
return SafeArea(
child: Material(
type: MaterialType.transparency,
child: SlideTransition(
position: _slideAnimation,
child: LayoutBuilder(
builder: (context, constraints) {
return GestureDetector(
onPanUpdate: (details) {
if (details.delta.dy < -10) _close();
},
onTap: _toggleDetails,
child: Container(
padding: const EdgeInsets.symmetric(
vertical: 16.0,
horizontal: 4.0,
),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
border: Border(
bottom: BorderSide(
color: AppConfig.gold.withAlpha(200),
width: 2.0,
),
),
borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(AppConfig.borderRadius),
bottomRight: Radius.circular(AppConfig.borderRadius),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// Spacer for symmetry
SizedBox(
width: constraints.maxWidth >= 600 ? 120.0 : 65.0,
),
// Centered content
Expanded(
child: Padding(
padding: EdgeInsets.symmetric(
horizontal: isColumnMode ? 16.0 : 8.0,
),
child: Wrap(
spacing: 16.0,
alignment: WrapAlignment.center,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
Text(
"Level up",
style: style,
overflow: TextOverflow.ellipsis,
),
CachedNetworkImage(
imageUrl:
"${AppConfig.assetsBaseURL}/${LevelUpConstants.starFileName}",
height: 24,
width: 24,
),
],
),
),
),
// Optional staging-only dropdown icon
SizedBox(
width: constraints.maxWidth >= 600 ? 120.0 : 65.0,
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
if (Environment.isStagingEnvironment)
SizedBox(
width: 32.0,
height: 32.0,
child: Center(
child: IconButton(
icon: const Icon(Icons.arrow_drop_down),
style: IconButton.styleFrom(
padding: const EdgeInsets.all(4.0),
),
onPressed: _toggleDetails,
constraints: const BoxConstraints(),
color:
Theme.of(context).colorScheme.onSurface,
),
),
),
],
),
),
],
),
),
);
},
),
),
),
);
}
}

View file

@ -0,0 +1,95 @@
import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
import 'package:fluffychat/pangea/constructs/construct_repo.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/material.dart';
class LevelUpManager {
LevelUpManager._internal();
factory LevelUpManager() {
return _instance;
}
static final LevelUpManager _instance = LevelUpManager._internal();
int? prevLevel;
int? level;
int? prevGrammar;
int? nextGrammar;
int? prevVocab;
int? nextVocab;
ConstructSummary? constructSummary;
bool hasSeenPopup = false;
bool shouldAutoPopup = false;
String? error;
Future<void> preloadAnalytics(
BuildContext context,
int level,
int prevLevel,
) async {
this.level = level;
this.prevLevel = prevLevel;
//grammar and vocab
nextGrammar = MatrixState.pangeaController.getAnalytics.constructListModel
.unlockedLemmas(
ConstructTypeEnum.morph,
)
.length;
nextVocab = MatrixState.pangeaController.getAnalytics.constructListModel
.unlockedLemmas(
ConstructTypeEnum.vocab,
)
.length;
//for now idk how to get these
prevGrammar = 0;
prevVocab = 0;
//fetch construct summary
try {
constructSummary = await MatrixState.pangeaController.getAnalytics
.generateLevelUpAnalytics(
level,
prevLevel,
);
} catch (e) {
error = e.toString();
}
}
void markPopupSeen() {
hasSeenPopup = true;
shouldAutoPopup = false;
}
void printAnalytics() {
print('Level Up Analytics:');
print('Current Level: $level');
print('Previous Level: $prevLevel');
print('Next Grammar: $nextGrammar');
print('Next Vocab: $nextVocab');
if (constructSummary != null) {
print('Construct Summary: ${constructSummary!.toJson()}');
} else {
print('Construct Summary: Not available');
}
}
void reset() {
hasSeenPopup = false;
shouldAutoPopup = false;
prevLevel = null;
level = null;
prevGrammar = null;
nextGrammar = null;
prevVocab = null;
nextVocab = null;
constructSummary = null;
error = null;
// Reset any other state if necessary
}
}

View file

@ -1,18 +1,14 @@
import 'dart:async';
import 'dart:math';
import 'package:audioplayers/audioplayers.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:confetti/confetti.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/construct_type_enum.dart';
import 'package:fluffychat/pangea/analytics_misc/learning_skills_enum.dart';
import 'package:fluffychat/pangea/analytics_misc/level_up/level_up_banner.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/common/widgets/full_width_dialog.dart';
import 'package:fluffychat/pangea/constructs/construct_repo.dart';
import 'package:fluffychat/widgets/matrix.dart';
@ -23,257 +19,6 @@ import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:matrix/matrix_api_lite/generated/model.dart';
class LevelUpConstants {
static const String starFileName = "star.png";
static const String dinoLevelUPFileName = "DinoBot-Congratulate.png";
}
class LevelUpUtil {
static Future<void> showLevelUpDialog(
int level,
int prevLevel,
BuildContext context,
) async {
final player = AudioPlayer();
final snackbarRegex = RegExp(r'_snackbar$');
while (MatrixState.pAnyState.activeOverlays
.any((overlayId) => snackbarRegex.hasMatch(overlayId))) {
await Future.delayed(const Duration(milliseconds: 100));
}
player
.play(
UrlSource(
"${AppConfig.assetsBaseURL}/${AnalyticsConstants.levelUpAudioFileName}",
),
)
.then(
(_) => Future.delayed(
const Duration(seconds: 2),
() => player.dispose(),
),
);
OverlayUtil.showOverlay(
overlayKey: "level_up_notification",
context: context,
child: LevelUpBanner(
level: level,
prevLevel: prevLevel,
backButtonOverride: IconButton(
icon: const Icon(Icons.close),
onPressed: () {
Navigator.of(context).pop();
},
),
),
transformTargetId: '',
position: OverlayPositionEnum.top,
backDropToDismiss: false,
closePrevOverlay: false,
canPop: false,
);
}
}
class LevelUpBanner extends StatefulWidget {
final int level;
final int prevLevel;
final Widget? backButtonOverride;
const LevelUpBanner({
required this.level,
required this.prevLevel,
required this.backButtonOverride,
super.key,
});
@override
LevelUpBannerState createState() => LevelUpBannerState();
}
class LevelUpBannerState extends State<LevelUpBanner>
with TickerProviderStateMixin {
late AnimationController _slideController;
late Animation<Offset> _slideAnimation;
late AnimationController _sizeController;
final bool _showedDetails = false;
@override
void initState() {
super.initState();
_slideController = AnimationController(
vsync: this,
duration: FluffyThemes.animationDuration,
);
_slideAnimation = Tween<Offset>(
begin: const Offset(0, -1),
end: Offset.zero,
).animate(
CurvedAnimation(
parent: _slideController,
curve: Curves.easeOut,
),
);
_sizeController = AnimationController(
vsync: this,
duration: FluffyThemes.animationDuration,
);
_slideController.forward();
Future.delayed(const Duration(seconds: 10), () async {
if (mounted && !_showedDetails) _close();
});
}
Future<void> _close() async {
await _slideController.reverse();
MatrixState.pAnyState.closeOverlay("level_up_notification");
}
@override
void dispose() {
_slideController.dispose();
_sizeController.dispose();
super.dispose();
}
Future<void> _toggleDetails() async {
await _close();
//if (!mounted) return;
await showDialog(
context: context,
builder: (context) => LevelUpPopup(widget: widget),
);
}
@override
Widget build(BuildContext context) {
final isColumnMode = FluffyThemes.isColumnMode(context);
final style = isColumnMode
? Theme.of(context).textTheme.titleLarge?.copyWith(
color: AppConfig.gold,
fontWeight: FontWeight.bold,
letterSpacing: 0.5,
)
: Theme.of(context).textTheme.bodyLarge?.copyWith(
color: AppConfig.gold,
fontWeight: FontWeight.bold,
letterSpacing: 0.5,
);
return SafeArea(
child: Material(
type: MaterialType.transparency,
child: SlideTransition(
position: _slideAnimation,
child: LayoutBuilder(
builder: (context, constraints) {
return GestureDetector(
onPanUpdate: (details) {
if (details.delta.dy < -10) _close();
},
onTap: _toggleDetails,
child: Container(
padding: const EdgeInsets.symmetric(
vertical: 16.0,
horizontal: 4.0,
),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
border: Border(
bottom: BorderSide(
color: AppConfig.gold.withAlpha(200),
width: 2.0,
),
),
borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(AppConfig.borderRadius),
bottomRight: Radius.circular(AppConfig.borderRadius),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// Spacer for symmetry
SizedBox(
width: constraints.maxWidth >= 600 ? 120.0 : 65.0,
),
// Centered content
Expanded(
child: Padding(
padding: EdgeInsets.symmetric(
horizontal: isColumnMode ? 16.0 : 8.0,
),
child: Wrap(
spacing: 16.0,
alignment: WrapAlignment.center,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
Text(
"Level up",
style: style,
overflow: TextOverflow.ellipsis,
),
CachedNetworkImage(
imageUrl:
"${AppConfig.assetsBaseURL}/${LevelUpConstants.starFileName}",
height: 24,
width: 24,
),
],
),
),
),
// Optional staging-only dropdown icon
SizedBox(
width: constraints.maxWidth >= 600 ? 120.0 : 65.0,
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
if (Environment.isStagingEnvironment)
SizedBox(
width: 32.0,
height: 32.0,
child: Center(
child: IconButton(
icon: const Icon(Icons.arrow_drop_down),
style: IconButton.styleFrom(
padding: const EdgeInsets.all(4.0),
),
onPressed: _toggleDetails,
constraints: const BoxConstraints(),
color:
Theme.of(context).colorScheme.onSurface,
),
),
),
],
),
),
],
),
),
);
},
),
),
),
);
}
}
class LevelUpPopup extends StatelessWidget {
const LevelUpPopup({
super.key,
@ -334,7 +79,7 @@ class _LevelUpBarAnimationState extends State<LevelUpBarAnimation>
late final Animation<int> _grammarAnimation;
late final Animation<double> _skillsOpacity;
late final Animation<double> _shrinkMultiplier;
late final Uri? avatarUrl;
Uri? avatarUrl;
late final Future<Profile> profile;
late final ConfettiController _confettiController;
@ -423,7 +168,9 @@ class _LevelUpBarAnimationState extends State<LevelUpBarAnimation>
),
);
_controller.forward();
WidgetsBinding.instance.addPostFrameCallback((_) {
_controller.forward(); // or start your animation
});
}
Future<void> _setConstructSummary() async {
@ -515,51 +262,6 @@ class _LevelUpBarAnimationState extends State<LevelUpBarAnimation>
],
),
),
// Avatar and language
/*AnimatedBuilder(
animation: _progressAnimation,
builder: (_, __) => Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
FutureBuilder<Profile>(
future: client.fetchOwnProfile(),
builder: (context, snapshot) {
if (snapshot.connectionState != ConnectionState.done) {
return const CircularProgressIndicator(); // Or a skeleton
}
if (snapshot.hasError) {
print("Profile fetch error: ${snapshot.error}");
return const Text("Error loading profile");
}
if (!snapshot.hasData || snapshot.data == null) {
print("No profile data!");
return const Text("No data");
}
return ListTile(
leading: Avatar(
mxContent: snapshot.data?.avatarUrl,
size: 20,
),
title: Text(profile?.displayName ?? client.userID!),
contentPadding: EdgeInsets.zero,
);
},
),
const SizedBox(width: 16),
Text(
language,
style: TextStyle(
fontSize: 24 * _skillsOpacity.value,
color: AppConfig.goldLight,
fontWeight: FontWeight.bold,
),
),
],
),
),*/
// Progress bar + Level
AnimatedBuilder(
animation: _progressAnimation,