Merge pull request #5371 from pangeachat/5262-add-seedsproutflower-icons-to-message-xp-burst
5262 add seedsproutflower icons to message xp burst
This commit is contained in:
commit
27df30e801
6 changed files with 198 additions and 0 deletions
|
|
@ -19,6 +19,7 @@ import 'package:fluffychat/pangea/analytics_misc/constructs_event.dart';
|
|||
import 'package:fluffychat/pangea/analytics_misc/constructs_model.dart';
|
||||
import 'package:fluffychat/pangea/analytics_settings/analytics_settings_extension.dart';
|
||||
import 'package:fluffychat/pangea/constructs/construct_identifier.dart';
|
||||
import 'package:fluffychat/pangea/constructs/construct_level_enum.dart';
|
||||
import 'package:fluffychat/pangea/languages/language_model.dart';
|
||||
import 'package:fluffychat/pangea/user/analytics_profile_model.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
|
@ -442,6 +443,23 @@ class AnalyticsDataService {
|
|||
events.add(MorphUnlockedEvent(newUnlockedMorphs));
|
||||
}
|
||||
|
||||
for (final entry in newConstructs.entries) {
|
||||
final prevConstruct = prevConstructs[entry.key];
|
||||
if (prevConstruct == null) continue;
|
||||
|
||||
final prevLevel = prevConstruct.lemmaCategory;
|
||||
final newLevel = entry.value.lemmaCategory;
|
||||
if (newLevel.xpNeeded > prevLevel.xpNeeded) {
|
||||
events.add(
|
||||
ConstructLevelUpEvent(
|
||||
entry.key,
|
||||
newLevel,
|
||||
update.targetID,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (update.blockedConstruct != null) {
|
||||
events.add(ConstructBlockedEvent(update.blockedConstruct!));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import 'package:fluffychat/pangea/analytics_data/analytics_data_service.dart';
|
|||
import 'package:fluffychat/pangea/analytics_data/analytics_update_events.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/constructs_model.dart';
|
||||
import 'package:fluffychat/pangea/constructs/construct_identifier.dart';
|
||||
import 'package:fluffychat/pangea/constructs/construct_level_enum.dart';
|
||||
import 'package:fluffychat/pangea/lemmas/user_set_lemma_info.dart';
|
||||
|
||||
class LevelUpdate {
|
||||
|
|
@ -28,6 +29,18 @@ class AnalyticsUpdate {
|
|||
});
|
||||
}
|
||||
|
||||
class ConstructLevelUpdate {
|
||||
final ConstructIdentifier constructId;
|
||||
final ConstructLevelEnum level;
|
||||
final String? targetID;
|
||||
|
||||
ConstructLevelUpdate({
|
||||
required this.constructId,
|
||||
required this.level,
|
||||
this.targetID,
|
||||
});
|
||||
}
|
||||
|
||||
class AnalyticsUpdateDispatcher {
|
||||
final AnalyticsDataService dataService;
|
||||
|
||||
|
|
@ -46,6 +59,9 @@ class AnalyticsUpdateDispatcher {
|
|||
final StreamController<Set<ConstructIdentifier>> newConstructsStream =
|
||||
StreamController<Set<ConstructIdentifier>>.broadcast();
|
||||
|
||||
final StreamController<ConstructLevelUpdate> constructLevelUpdateStream =
|
||||
StreamController<ConstructLevelUpdate>.broadcast();
|
||||
|
||||
final StreamController<MapEntry<ConstructIdentifier, UserSetLemmaInfo>>
|
||||
_lemmaInfoUpdateStream = StreamController<
|
||||
MapEntry<ConstructIdentifier, UserSetLemmaInfo>>.broadcast();
|
||||
|
|
@ -57,6 +73,7 @@ class AnalyticsUpdateDispatcher {
|
|||
activityAnalyticsStream.close();
|
||||
unlockedConstructsStream.close();
|
||||
levelUpdateStream.close();
|
||||
constructLevelUpdateStream.close();
|
||||
_lemmaInfoUpdateStream.close();
|
||||
}
|
||||
|
||||
|
|
@ -101,6 +118,9 @@ class AnalyticsUpdateDispatcher {
|
|||
case final ConstructBlockedEvent e:
|
||||
_onBlockedConstruct(e.blockedConstruct);
|
||||
break;
|
||||
case final ConstructLevelUpEvent e:
|
||||
_onConstructLevelUp(e.constructId, e.level, e.targetID);
|
||||
break;
|
||||
case final NewConstructsEvent e:
|
||||
_onNewConstruct(e.newConstructs);
|
||||
break;
|
||||
|
|
@ -137,6 +157,20 @@ class AnalyticsUpdateDispatcher {
|
|||
constructUpdateStream.add(update);
|
||||
}
|
||||
|
||||
void _onConstructLevelUp(
|
||||
ConstructIdentifier constructId,
|
||||
ConstructLevelEnum level,
|
||||
String? targetID,
|
||||
) {
|
||||
constructLevelUpdateStream.add(
|
||||
ConstructLevelUpdate(
|
||||
constructId: constructId,
|
||||
level: level,
|
||||
targetID: targetID,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onBlockedConstruct(ConstructIdentifier constructId) {
|
||||
final update = AnalyticsStreamUpdate(
|
||||
blockedConstruct: constructId,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:fluffychat/pangea/constructs/construct_identifier.dart';
|
||||
import 'package:fluffychat/pangea/constructs/construct_level_enum.dart';
|
||||
|
||||
sealed class AnalyticsUpdateEvent {}
|
||||
|
||||
|
|
@ -13,6 +14,17 @@ class MorphUnlockedEvent extends AnalyticsUpdateEvent {
|
|||
MorphUnlockedEvent(this.unlocked);
|
||||
}
|
||||
|
||||
class ConstructLevelUpEvent extends AnalyticsUpdateEvent {
|
||||
final ConstructIdentifier constructId;
|
||||
final ConstructLevelEnum level;
|
||||
final String? targetID;
|
||||
ConstructLevelUpEvent(
|
||||
this.constructId,
|
||||
this.level,
|
||||
this.targetID,
|
||||
);
|
||||
}
|
||||
|
||||
class XPGainedEvent extends AnalyticsUpdateEvent {
|
||||
final int points;
|
||||
final String? targetID;
|
||||
|
|
|
|||
|
|
@ -3,12 +3,14 @@ import 'dart:async';
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/analytics_data/analytics_data_service.dart';
|
||||
import 'package:fluffychat/pangea/analytics_data/analytics_update_dispatcher.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/constructs_model.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/overlay.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
mixin AnalyticsUpdater<T extends StatefulWidget> on State<T> {
|
||||
StreamSubscription? _analyticsSubscription;
|
||||
StreamSubscription? _constructLevelSubscription;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
|
@ -16,11 +18,14 @@ mixin AnalyticsUpdater<T extends StatefulWidget> on State<T> {
|
|||
final updater = Matrix.of(context).analyticsDataService.updateDispatcher;
|
||||
_analyticsSubscription =
|
||||
updater.constructUpdateStream.stream.listen(_onAnalyticsUpdate);
|
||||
_constructLevelSubscription =
|
||||
updater.constructLevelUpdateStream.stream.listen(_onConstructLevelUp);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_analyticsSubscription?.cancel();
|
||||
_constructLevelSubscription?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
|
@ -38,4 +43,10 @@ mixin AnalyticsUpdater<T extends StatefulWidget> on State<T> {
|
|||
OverlayUtil.showPointsGained(update.targetID!, update.points, context);
|
||||
}
|
||||
}
|
||||
|
||||
void _onConstructLevelUp(ConstructLevelUpdate update) {
|
||||
if (update.targetID != null) {
|
||||
OverlayUtil.showGrowthAnimation(context, update.targetID!, update.level);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
97
lib/pangea/analytics_misc/growth_animation.dart
Normal file
97
lib/pangea/analytics_misc/growth_animation.dart
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/constructs/construct_level_enum.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
/// Tracks active growth animations for offset calculation
|
||||
class GrowthAnimationTracker {
|
||||
static int _activeCount = 0;
|
||||
|
||||
static int get activeCount => _activeCount;
|
||||
|
||||
static double? startAnimation() {
|
||||
if (_activeCount >= 5) return null;
|
||||
final index = _activeCount;
|
||||
_activeCount++;
|
||||
if (index == 0) return 0;
|
||||
final side = index.isOdd ? 1 : -1;
|
||||
return side * ((index + 1) ~/ 2) * 20.0;
|
||||
}
|
||||
|
||||
static void endAnimation() {
|
||||
_activeCount = (_activeCount - 1).clamp(0, 999);
|
||||
}
|
||||
}
|
||||
|
||||
class GrowthAnimation extends StatefulWidget {
|
||||
final String targetID;
|
||||
final ConstructLevelEnum level;
|
||||
|
||||
const GrowthAnimation({
|
||||
super.key,
|
||||
required this.targetID,
|
||||
required this.level,
|
||||
});
|
||||
|
||||
@override
|
||||
State<GrowthAnimation> createState() => _GrowthAnimationState();
|
||||
}
|
||||
|
||||
class _GrowthAnimationState extends State<GrowthAnimation>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late final AnimationController _controller;
|
||||
late final double? _horizontalOffset;
|
||||
late final double _wiggleAmplitude;
|
||||
late final double _wiggleFrequency;
|
||||
final Random _random = Random();
|
||||
|
||||
static const _durationMs = 1600;
|
||||
static const _riseDistance = 72.0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_horizontalOffset = GrowthAnimationTracker.startAnimation();
|
||||
_wiggleAmplitude = 4.0 + _random.nextDouble() * 4.0;
|
||||
_wiggleFrequency = 1.5 + _random.nextDouble() * 1.0;
|
||||
|
||||
_controller = AnimationController(
|
||||
duration: const Duration(milliseconds: _durationMs),
|
||||
vsync: this,
|
||||
)..forward().then((_) {
|
||||
if (mounted) {
|
||||
MatrixState.pAnyState.closeOverlay(widget.targetID);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
GrowthAnimationTracker.endAnimation();
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_horizontalOffset == null) return const SizedBox.shrink();
|
||||
return AnimatedBuilder(
|
||||
animation: _controller,
|
||||
builder: (context, child) {
|
||||
final t = _controller.value;
|
||||
final dy = -_riseDistance * Curves.easeOut.transform(t);
|
||||
final opacity = t < 0.5 ? t * 2 : (1.0 - t) * 2;
|
||||
final wiggle = sin(t * pi * _wiggleFrequency) * _wiggleAmplitude;
|
||||
return Transform.translate(
|
||||
offset: Offset(_horizontalOffset! + wiggle, dy),
|
||||
child: Opacity(
|
||||
opacity: opacity.clamp(0.0, 1.0),
|
||||
child: widget.level.icon(24),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import 'package:flutter/foundation.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/analytics_misc/gain_points_animation.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/growth_animation.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/level_up/star_rain_widget.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/choreo_constants.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/choreographer.dart';
|
||||
|
|
@ -13,6 +14,7 @@ import 'package:fluffychat/pangea/common/utils/any_state_holder.dart';
|
|||
import 'package:fluffychat/pangea/common/widgets/anchored_overlay_widget.dart';
|
||||
import 'package:fluffychat/pangea/common/widgets/overlay_container.dart';
|
||||
import 'package:fluffychat/pangea/common/widgets/transparent_backdrop.dart';
|
||||
import 'package:fluffychat/pangea/constructs/construct_level_enum.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/language_mismatch_popup.dart';
|
||||
import '../../../config/themes.dart';
|
||||
import '../../../widgets/matrix.dart';
|
||||
|
|
@ -312,6 +314,30 @@ class OverlayUtil {
|
|||
);
|
||||
}
|
||||
|
||||
static int _growthAnimationCounter = 0;
|
||||
|
||||
static void showGrowthAnimation(
|
||||
BuildContext context,
|
||||
String targetId,
|
||||
ConstructLevelEnum level,
|
||||
) {
|
||||
final overlayKey = "${targetId}_growth_${_growthAnimationCounter++}";
|
||||
showOverlay(
|
||||
overlayKey: overlayKey,
|
||||
followerAnchor: Alignment.topCenter,
|
||||
targetAnchor: Alignment.topCenter,
|
||||
context: context,
|
||||
child: GrowthAnimation(
|
||||
targetID: overlayKey,
|
||||
level: level,
|
||||
),
|
||||
transformTargetId: targetId,
|
||||
closePrevOverlay: false,
|
||||
backDropToDismiss: false,
|
||||
ignorePointer: true,
|
||||
);
|
||||
}
|
||||
|
||||
static void showLanguageMismatchPopup({
|
||||
required BuildContext context,
|
||||
required String targetId,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue