immich/mobile/lib/utils/debounce.dart
shenlong e52b9d15b5
chore: bump dart sdk to 3.8 (#20355)
* chore: bump dart sdk to 3.8

* chore: make build

* make pigeon

* chore: format files

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2025-07-28 14:04:03 -05:00

95 lines
2.6 KiB
Dart

import 'dart:async';
import 'package:flutter_hooks/flutter_hooks.dart';
/// Used to debounce function calls with the [interval] provided.
/// If [maxWaitTime] is provided, the first [run] call as well as the next call since [maxWaitTime] has passed will be immediately executed, even if [interval] is not satisfied.
class Debouncer {
Debouncer({required this.interval, this.maxWaitTime});
final Duration interval;
final Duration? maxWaitTime;
Timer? _timer;
FutureOr<void> Function()? _lastAction;
DateTime? _lastActionTime;
Future<void>? _actionFuture;
void run(FutureOr<void> Function() action) {
_lastAction = action;
_timer?.cancel();
if (maxWaitTime != null &&
// _actionFuture == null && // TODO: should this check be here?
(_lastActionTime == null || DateTime.now().difference(_lastActionTime!) > maxWaitTime!)) {
_callAndRest();
return;
}
_timer = Timer(interval, _callAndRest);
}
Future<void>? drain() {
if (_timer != null && _timer!.isActive) {
_timer!.cancel();
if (_lastAction != null) {
_callAndRest();
}
}
return _actionFuture;
}
@pragma('vm:prefer-inline')
void _callAndRest() {
_lastActionTime = DateTime.now();
final action = _lastAction;
_lastAction = null;
final result = action!();
if (result is Future) {
_actionFuture = result.whenComplete(() {
_actionFuture = null;
});
}
_timer = null;
}
void dispose() {
_timer?.cancel();
_timer = null;
_lastAction = null;
_lastActionTime = null;
_actionFuture = null;
}
bool get isActive => _actionFuture != null || (_timer != null && _timer!.isActive);
}
/// Creates a [Debouncer] that will be disposed automatically. If no [interval] is provided, a
/// default interval of 300ms is used to debounce the function calls
Debouncer useDebouncer({
Duration interval = const Duration(milliseconds: 300),
Duration? maxWaitTime,
List<Object?>? keys,
}) => use(_DebouncerHook(interval: interval, maxWaitTime: maxWaitTime, keys: keys));
class _DebouncerHook extends Hook<Debouncer> {
const _DebouncerHook({required this.interval, this.maxWaitTime, super.keys});
final Duration interval;
final Duration? maxWaitTime;
@override
HookState<Debouncer, Hook<Debouncer>> createState() => _DebouncerHookState();
}
class _DebouncerHookState extends HookState<Debouncer, _DebouncerHook> {
late final debouncer = Debouncer(interval: hook.interval, maxWaitTime: hook.maxWaitTime);
@override
Debouncer build(_) => debouncer;
@override
void dispose() => debouncer.dispose();
@override
String get debugLabel => 'useDebouncer';
}