fix(feedback-dialogs): add queue system to prevent dialog stacking

This commit is contained in:
2026-04-26 08:25:24 +02:00
parent a9a34fbbc5
commit 4422f93903

View File

@@ -1,17 +1,39 @@
import 'dart:async';
import 'dart:collection';
import 'package:flutter/material.dart';
import 'package:sf_localizations/sf_localizations.dart';
enum _FeedbackKind { error, success, info }
final _queue = Queue<_FeedbackRequest>();
bool _isShowing = false;
class _FeedbackRequest {
final BuildContext context;
final String messageKey;
final Map<String, dynamic>? args;
final _FeedbackKind kind;
final Duration autoDismiss;
final Completer<void> completer;
_FeedbackRequest({
required this.context,
required this.messageKey,
required this.args,
required this.kind,
required this.autoDismiss,
required this.completer,
});
}
Future<void> showErrorDialog(
BuildContext context,
String messageKey, {
Map<String, dynamic>? args,
Duration autoDismiss = const Duration(seconds: 3),
}) {
return _showPillFeedback(
return _enqueue(
context,
messageKey: messageKey,
args: args,
@@ -26,7 +48,7 @@ Future<void> showSuccessDialog(
Map<String, dynamic>? args,
Duration autoDismiss = const Duration(seconds: 3),
}) {
return _showPillFeedback(
return _enqueue(
context,
messageKey: messageKey,
args: args,
@@ -41,7 +63,7 @@ Future<void> showInfoDialog(
Map<String, dynamic>? args,
Duration autoDismiss = const Duration(seconds: 3),
}) {
return _showPillFeedback(
return _enqueue(
context,
messageKey: messageKey,
args: args,
@@ -50,6 +72,50 @@ Future<void> showInfoDialog(
);
}
Future<void> _enqueue(
BuildContext context, {
required String messageKey,
required Map<String, dynamic>? args,
required _FeedbackKind kind,
required Duration autoDismiss,
}) {
final completer = Completer<void>();
_queue.add(_FeedbackRequest(
context: context,
messageKey: messageKey,
args: args,
kind: kind,
autoDismiss: autoDismiss,
completer: completer,
));
_processQueue();
return completer.future;
}
Future<void> _processQueue() async {
if (_isShowing || _queue.isEmpty) return;
while (_queue.isNotEmpty && !_queue.first.context.mounted) {
_queue.removeFirst().completer.complete();
}
if (_queue.isEmpty) return;
_isShowing = true;
final request = _queue.removeFirst();
await _showPillFeedback(
request.context,
messageKey: request.messageKey,
args: request.args,
kind: request.kind,
autoDismiss: request.autoDismiss,
);
request.completer.complete();
_isShowing = false;
_processQueue();
}
Future<void> _showPillFeedback(
BuildContext context, {
required String messageKey,
@@ -57,6 +123,7 @@ Future<void> _showPillFeedback(
required _FeedbackKind kind,
required Duration autoDismiss,
}) async {
if (!context.mounted) return;
final overlay = Overlay.of(context);
final resolved = context.translate(messageKey, args: args);
@@ -78,6 +145,7 @@ Future<void> _showPillFeedback(
),
};
final completer = Completer<void>();
late final OverlayEntry entry;
late final AnimationController controller;
@@ -93,9 +161,11 @@ Future<void> _showPillFeedback(
);
void dismiss() {
if (completer.isCompleted) return;
controller.reverse().then((_) {
entry.remove();
controller.dispose();
completer.complete();
});
}
@@ -192,8 +262,7 @@ Future<void> _showPillFeedback(
controller.forward();
await Future.delayed(autoDismiss);
if (controller.isAnimating ||
controller.status == AnimationStatus.completed) {
dismiss();
}
dismiss();
return completer.future;
}