import 'dart:collection'; import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; import 'package:nc_photos/exception_util.dart'; import 'package:nc_photos/k.dart' as k; import 'package:np_codegen/np_codegen.dart'; part 'snack_bar_manager.g.dart'; /// Showing snack bars /// /// This manager helps showing a snack bar even after the context was /// invalidated by having another widget (presumably top-level) to handle such /// request in a decoupled way @npLog class SnackBarManager { factory SnackBarManager() => _inst; @visibleForTesting SnackBarManager.scoped(); void registerHandler(SnackBarHandler handler) { _handlers.add(handler); } void unregisterHandler(SnackBarHandler handler) { _handlers.remove(handler); } /// Queue a snack bar to be shown ASAP /// /// If the snack bar can't be shown, return null. /// /// If [canBeReplaced] is true, this snackbar will be dismissed by the next /// snack bar void showSnackBar( SnackBar snackBar, { bool canBeReplaced = false, }) { _add(_Item(snackBar, canBeReplaced)); _ensureRunning(); } void showSnackBarForException(Object? exception) { final (text, action) = exceptionToSnackBarData(exception); showSnackBar(SnackBar( content: Text(text), action: action, duration: k.snackBarDurationNormal, )); } void _ensureRunning() { if (!_isRunning) { _isRunning = true; _next(); } } void _add(_Item item) { _queue.add(item); if (_currentItem?.canBeReplaced == true) { _currentItem!.controller?.close(); } } Future _next() async { if (_queue.isEmpty) { _isRunning = false; return; } final item = _queue.removeFirst(); if (item.canBeReplaced && _queue.isNotEmpty) { _log.info("[_next] Skip replaceable snack bar"); return _next(); } // show item for (final h in _handlers.reversed) { final controller = h.showSnackBar(item.snackBar); if (controller != null) { item.controller = controller; _currentItem = item; try { final reason = await controller.closed; _log.fine("[_next] Snack bar closed: ${reason.name}"); } finally { _currentItem = null; } return _next(); } } _log.warning("[_next] No handler available"); return _next(); } final _handlers = []; final Queue<_Item> _queue = DoubleLinkedQueue(); var _isRunning = false; _Item? _currentItem; static final _inst = SnackBarManager.scoped(); } abstract class SnackBarHandler { ScaffoldFeatureController? showSnackBar( SnackBar snackBar); } class _Item { _Item(this.snackBar, this.canBeReplaced); final SnackBar snackBar; final bool canBeReplaced; ScaffoldFeatureController? controller; }