2022-07-25 07:04:22 +02:00
|
|
|
import 'dart:collection';
|
|
|
|
|
2021-04-10 06:28:12 +02:00
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
import 'package:logging/logging.dart';
|
2022-12-16 16:01:04 +01:00
|
|
|
import 'package:np_codegen/np_codegen.dart';
|
|
|
|
|
|
|
|
part 'snack_bar_manager.g.dart';
|
2021-04-10 06:28:12 +02:00
|
|
|
|
|
|
|
/// 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
|
2022-12-16 16:01:04 +01:00
|
|
|
@npLog
|
2021-04-10 06:28:12 +02:00
|
|
|
class SnackBarManager {
|
|
|
|
factory SnackBarManager() => _inst;
|
|
|
|
|
2022-07-25 07:04:22 +02:00
|
|
|
@visibleForTesting
|
|
|
|
SnackBarManager.scoped();
|
2021-04-10 06:28:12 +02:00
|
|
|
|
|
|
|
void registerHandler(SnackBarHandler handler) {
|
|
|
|
_handlers.add(handler);
|
|
|
|
}
|
|
|
|
|
|
|
|
void unregisterHandler(SnackBarHandler handler) {
|
|
|
|
_handlers.remove(handler);
|
|
|
|
}
|
|
|
|
|
2022-07-25 07:04:22 +02:00
|
|
|
/// Queue a snack bar to be shown ASAP
|
2021-04-10 06:28:12 +02:00
|
|
|
///
|
2022-07-25 07:04:22 +02:00
|
|
|
/// If the snack bar can't be shown, return null.
|
2021-12-02 11:45:03 +01:00
|
|
|
///
|
|
|
|
/// If [canBeReplaced] is true, this snackbar will be dismissed by the next
|
|
|
|
/// snack bar
|
2022-07-25 07:04:22 +02:00
|
|
|
void showSnackBar(
|
2021-12-02 11:45:03 +01:00
|
|
|
SnackBar snackBar, {
|
|
|
|
bool canBeReplaced = false,
|
|
|
|
}) {
|
2022-07-25 07:04:22 +02:00
|
|
|
_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();
|
2021-12-02 11:45:03 +01:00
|
|
|
}
|
2022-07-25 07:04:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
2021-04-10 06:28:12 +02:00
|
|
|
for (final h in _handlers.reversed) {
|
2022-07-25 07:04:22 +02:00
|
|
|
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();
|
2021-04-10 06:28:12 +02:00
|
|
|
}
|
|
|
|
}
|
2022-07-25 07:04:22 +02:00
|
|
|
_log.warning("[_next] No handler available");
|
|
|
|
return _next();
|
2021-04-10 06:28:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
final _handlers = <SnackBarHandler>[];
|
2022-07-25 07:04:22 +02:00
|
|
|
final Queue<_Item> _queue = DoubleLinkedQueue();
|
|
|
|
var _isRunning = false;
|
|
|
|
_Item? _currentItem;
|
2021-04-10 06:28:12 +02:00
|
|
|
|
2022-07-25 07:04:22 +02:00
|
|
|
static final _inst = SnackBarManager.scoped();
|
2021-04-10 06:28:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
abstract class SnackBarHandler {
|
2021-07-23 22:05:57 +02:00
|
|
|
ScaffoldFeatureController<SnackBar, SnackBarClosedReason>? showSnackBar(
|
2021-04-10 06:28:12 +02:00
|
|
|
SnackBar snackBar);
|
|
|
|
}
|
2022-07-25 07:04:22 +02:00
|
|
|
|
|
|
|
class _Item {
|
|
|
|
_Item(this.snackBar, this.canBeReplaced);
|
|
|
|
|
|
|
|
final SnackBar snackBar;
|
|
|
|
final bool canBeReplaced;
|
|
|
|
ScaffoldFeatureController<SnackBar, SnackBarClosedReason>? controller;
|
|
|
|
}
|