mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-01-22 08:46:18 +01:00
116 lines
2.9 KiB
Dart
116 lines
2.9 KiB
Dart
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<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;
|
|
}
|