Skip to content

feat: new centralized chat banner controller#5919

Merged
ggurdin merged 2 commits intomainfrom
5911-if-grammar-improvement-popup-stack-is-more-than-2-3-ignore-others
Mar 6, 2026
Merged

feat: new centralized chat banner controller#5919
ggurdin merged 2 commits intomainfrom
5911-if-grammar-improvement-popup-stack-is-more-than-2-3-ignore-others

Conversation

@ggurdin
Copy link
Collaborator

@ggurdin ggurdin commented Mar 5, 2026

Thank you so much for your contribution to FluffyChat ❤️❤️❤️

Pull Request has been tested on:

  • Android
  • iOS
  • Browser (Chromium based)
  • Browser (Firefox based)
  • Browser (WebKit based)
  • Desktop Linux
  • Desktop Windows
  • Desktop macOS

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Introduces a centralized banner queue/controller for chat so “level up” and “unlocked morph” banners display in a controlled, sequential way (replacing the older snackbar-style implementation).

Changes:

  • Added ChatBannerController to queue/dedupe/time out chat banners.
  • Introduced reusable ChatBannerBuilder plus new LevelUpBanner and UnlockedMorphBanner widgets.
  • Removed legacy unlocked-morph snackbar util and legacy level-up banner util; wired chat page to new controller.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
lib/pangea/common/utils/overlay.dart Adds helper methods to show new top-positioned chat banners via overlays.
lib/pangea/chat/widgets/unlocked_morph_banner.dart New banner widget for unlocked morph notifications (with details navigation + icon rain).
lib/pangea/chat/widgets/level_up_banner.dart New banner widget for level-up notifications (includes audio playback).
lib/pangea/chat/widgets/chat_banner_builder.dart New shared banner container/animation + auto-close + completer signaling.
lib/pangea/chat/utils/unlocked_morphs_snackbar.dart Removes legacy snackbar-based unlocked morph notification implementation.
lib/pangea/chat/chat_banner_controller.dart New centralized queue controller for banner sequencing and timeout behavior.
lib/pangea/analytics_misc/level_up/level_up_banner.dart Removes legacy level-up util/banner implementation.
lib/pages/chat/chat.dart Switches chat page to drive level-up/unlock notifications through ChatBannerController.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +50 to +56
_animation = CurvedAnimation(parent: _controller!, curve: Curves.easeInOut);
_controller!.forward().then((_) {
widget.onAnimatedIn?.call();
_autoCloseTimer = Timer(const Duration(seconds: 10), () {
if (mounted) _close();
});
});
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_controller!.forward().then(...) can run its callback after this State has been disposed (e.g., if the overlay is closed quickly). That would still call onAnimatedIn and start _autoCloseTimer. Add a if (!mounted) return; inside the then callback (or use addStatusListener) before invoking callbacks / allocating timers.

Copilot uses AI. Check for mistakes.
Comment on lines 11 to 14
import 'package:fluffychat/pangea/bot/utils/bot_style.dart';
import 'package:fluffychat/pangea/chat/widgets/level_up_banner.dart';
import 'package:fluffychat/pangea/chat/widgets/unlocked_morph_banner.dart';
import 'package:fluffychat/pangea/choreographer/choreo_constants.dart';
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

overlay.dart now imports unlocked_morph_banner.dart, but UnlockedMorphBanner also imports overlay.dart, creating a circular dependency and coupling a common util (pangea/common/utils) to chat UI widgets. Consider moving showUnlockedConstructBanner/showLevelUpBanner into a chat-specific util/controller file (or invert dependencies by passing a widget builder into OverlayUtil.showOverlay) to break the import cycle.

Copilot uses AI. Check for mistakes.
Comment on lines +42 to +56
_playLevelUpSound();
}

Future<void> _playLevelUpSound() async {
final player = AudioPlayer();
player.setVolume(AppSettings.volume.value);

await player.play(
UrlSource(
"${AppConfig.assetsBaseURL}/${AnalyticsConstants.levelUpAudioFileName}",
),
);

await Future.delayed(const Duration(seconds: 2));
player.dispose();
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_playLevelUpSound() is started from initState() without awaiting, and it doesn't handle errors or ensure the AudioPlayer is disposed in a finally. If player.play(...) throws (network/asset error), this becomes an unhandled async error and the player may leak. Consider wrapping in try/finally, storing the player in state and disposing it in dispose(), and/or using unawaited(...) with internal error handling.

Suggested change
_playLevelUpSound();
}
Future<void> _playLevelUpSound() async {
final player = AudioPlayer();
player.setVolume(AppSettings.volume.value);
await player.play(
UrlSource(
"${AppConfig.assetsBaseURL}/${AnalyticsConstants.levelUpAudioFileName}",
),
);
await Future.delayed(const Duration(seconds: 2));
player.dispose();
unawaited(_playLevelUpSound());
}
Future<void> _playLevelUpSound() async {
final player = AudioPlayer();
try {
player.setVolume(AppSettings.volume.value);
await player.play(
UrlSource(
"${AppConfig.assetsBaseURL}/${AnalyticsConstants.levelUpAudioFileName}",
),
);
await Future.delayed(const Duration(seconds: 2));
} catch (e, stackTrace) {
debugPrint('Error while playing level up sound: $e');
} finally {
await player.dispose();
}

Copilot uses AI. Check for mistakes.
Comment on lines +44 to +50
void addBanner(
BannerExecutor showBanner, {
required String overlayKey,
Duration timeout = const Duration(seconds: 30),
}) {
if (_bannerQueue.any((b) => b.overlayKey == overlayKey)) return;

Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addBanner dedupes only against _bannerQueue, so the same overlayKey can be enqueued again while it is currently showing (since the active banner isn't part of the queue). Track the currently showing overlayKey (or maintain a Set of queued+active keys) to prevent duplicate banners from showing back-to-back.

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +2
// ignore_for_file: depend_on_referenced_packages, implementation_imports

Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The file-level // ignore_for_file: depend_on_referenced_packages, implementation_imports suppression looks unnecessary here (imports are all public packages / app libs). Removing it helps keep lint suppressions meaningful and avoids masking real issues later.

Suggested change
// ignore_for_file: depend_on_referenced_packages, implementation_imports

Copilot uses AI. Check for mistakes.
@override
void dispose() {
_autoCloseTimer?.cancel();
widget.closeCompleter.complete();
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dispose() unconditionally calls widget.closeCompleter.complete(). If the banner times out in ChatBannerController (which already completes the completer) or is completed elsewhere, this will throw a StateError (Completer already completed) during widget disposal and can crash the app. Guard the call with if (!widget.closeCompleter.isCompleted) ... (and similarly consider guarding any other completion sites).

Suggested change
widget.closeCompleter.complete();
if (!widget.closeCompleter.isCompleted) {
widget.closeCompleter.complete();
}

Copilot uses AI. Check for mistakes.
@ggurdin ggurdin merged commit 362e78b into main Mar 6, 2026
5 of 6 checks passed
@ggurdin ggurdin deleted the 5911-if-grammar-improvement-popup-stack-is-more-than-2-3-ignore-others branch March 6, 2026 15:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

If Grammar Improvement popup stack is more than 2-3, ignore others

2 participants