nc-photos/app/lib/snack_bar_manager.dart
2022-12-16 23:01:04 +08:00

105 lines
2.5 KiB
Dart

import 'dart:collection';
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
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 _ensureRunning() {
if (!_isRunning) {
_isRunning = true;
_next();
}
}
void _add(_Item item) {
_queue.add(item);
if (_currentItem?.canBeReplaced == true) {
_currentItem!.controller?.close();
}
}
Future<void> _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 = <SnackBarHandler>[];
final Queue<_Item> _queue = DoubleLinkedQueue();
var _isRunning = false;
_Item? _currentItem;
static final _inst = SnackBarManager.scoped();
}
abstract class SnackBarHandler {
ScaffoldFeatureController<SnackBar, SnackBarClosedReason>? showSnackBar(
SnackBar snackBar);
}
class _Item {
_Item(this.snackBar, this.canBeReplaced);
final SnackBar snackBar;
final bool canBeReplaced;
ScaffoldFeatureController<SnackBar, SnackBarClosedReason>? controller;
}