nc-photos/app/lib/snack_bar_manager.dart

117 lines
2.9 KiB
Dart
Raw Normal View History

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';
2024-06-19 08:58:08 +02:00
import 'package:nc_photos/exception_util.dart';
import 'package:nc_photos/k.dart' as k;
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.
///
/// If [canBeReplaced] is true, this snackbar will be dismissed by the next
/// snack bar
2022-07-25 07:04:22 +02:00
void showSnackBar(
SnackBar snackBar, {
bool canBeReplaced = false,
}) {
2022-07-25 07:04:22 +02:00
_add(_Item(snackBar, canBeReplaced));
_ensureRunning();
}
2024-06-19 08:58:08 +02:00
void showSnackBarForException(Object? exception) {
final (text, action) = exceptionToSnackBarData(exception);
showSnackBar(SnackBar(
content: Text(text),
action: action,
duration: k.snackBarDurationNormal,
));
}
2022-07-25 07:04:22 +02:00
void _ensureRunning() {
if (!_isRunning) {
_isRunning = true;
_next();
}
}
void _add(_Item item) {
_queue.add(item);
if (_currentItem?.canBeReplaced == true) {
_currentItem!.controller?.close();
}
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;
}