import 'dart:async'; import 'dart:collection'; import 'dart:math'; import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/models/models.dart'; import 'package:fl_clash/widgets/fade_box.dart'; import 'package:flutter/material.dart'; class MessageManager extends StatefulWidget { final Widget child; const MessageManager({super.key, required this.child}); @override State createState() => MessageManagerState(); } class MessageManagerState extends State { final _messagesNotifier = ValueNotifier>([]); final _bufferMessages = Queue(); final _activeTimers = {}; bool _isDisplayingMessage = false; @override void initState() { super.initState(); } @override void dispose() { _messagesNotifier.dispose(); for (final timer in _activeTimers.values) { timer.cancel(); } _activeTimers.clear(); _bufferMessages.clear(); super.dispose(); } void message(String text) { final commonMessage = CommonMessage(id: utils.uuidV4, text: text); _bufferMessages.add(commonMessage); commonPrint.log('message: $text'); _processQueue(); } void _cancelMessage(String id) { _bufferMessages.removeWhere((msg) => msg.id == id); if (_activeTimers.containsKey(id)) { _removeMessage(id); } } void _processQueue() { if (_isDisplayingMessage || _bufferMessages.isEmpty) { return; } _isDisplayingMessage = true; final message = _bufferMessages.removeFirst(); _messagesNotifier.value = List.from(_messagesNotifier.value)..add(message); final timer = Timer(message.duration, () { _removeMessage(message.id); }); _activeTimers[message.id] = timer; } void _removeMessage(String id) { _activeTimers.remove(id)?.cancel(); final currentMessages = List.from(_messagesNotifier.value); currentMessages.removeWhere((msg) => msg.id == id); _messagesNotifier.value = currentMessages; _isDisplayingMessage = false; _processQueue(); } @override Widget build(BuildContext context) { return Stack( children: [ widget.child, ValueListenableBuilder( valueListenable: _messagesNotifier, builder: (_, messages, _) { return Container( margin: EdgeInsets.only( top: kToolbarHeight + 12, left: 12, right: 12, ), child: FadeThroughBox( alignment: Alignment.topRight, child: messages.isEmpty ? SizedBox() : LayoutBuilder( key: Key(messages.last.id), builder: (_, constraints) { return Dismissible( key: ValueKey(messages.last.id), onDismissed: (_) { _cancelMessage(messages.last.id); }, child: Card( shape: const RoundedSuperellipseBorder( borderRadius: BorderRadius.all( Radius.circular(14), ), ), elevation: 10, color: context.colorScheme.surfaceContainerHigh, child: Container( width: min(constraints.maxWidth, 500), padding: EdgeInsets.symmetric( horizontal: 16, vertical: 8, ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Flexible( child: Text( messages.last.text, maxLines: 3, overflow: TextOverflow.ellipsis, ), ), SizedBox(width: 16), IconButton( padding: EdgeInsets.all(2), visualDensity: VisualDensity.compact, onPressed: () { _cancelMessage(messages.last.id); }, icon: Icon(Icons.close), ), ], ), ), ), ); }, ), ), ); }, ), ], ); } }