2024-04-30 23:38:49 +08:00
|
|
|
import 'dart:async';
|
|
|
|
|
|
2025-09-23 21:02:47 +08:00
|
|
|
import 'package:fl_clash/common/common.dart';
|
2026-09-05 20:09:39 +08:00
|
|
|
import 'package:fl_clash/enum/enum.dart';
|
2026-10-16 14:55:07 +08:00
|
|
|
import 'package:fl_clash/ui/ui.dart';
|
2026-09-05 20:09:39 +08:00
|
|
|
import 'package:flutter/foundation.dart';
|
2026-09-25 03:56:56 +08:00
|
|
|
import 'package:flutter/scheduler.dart';
|
2026-09-05 20:09:39 +08:00
|
|
|
|
|
|
|
|
void _applyAndReport(dynamic tag, Function func, List<dynamic>? args) {
|
|
|
|
|
void report(Object error, StackTrace stackTrace) {
|
|
|
|
|
commonPrint.log(
|
|
|
|
|
'Scheduled task $tag failed: ${compactError(error)}, $stackTrace',
|
|
|
|
|
logLevel: LogLevel.warning,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
final result = Function.apply(func, args);
|
|
|
|
|
if (result is Future) {
|
|
|
|
|
result.then<void>((_) {}, onError: report);
|
|
|
|
|
}
|
|
|
|
|
} catch (error, stackTrace) {
|
|
|
|
|
report(error, stackTrace);
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-05-02 02:24:12 +08:00
|
|
|
|
2024-04-30 23:38:49 +08:00
|
|
|
class Debouncer {
|
2026-09-05 20:09:39 +08:00
|
|
|
final Map<dynamic, Timer> _operations = {};
|
|
|
|
|
final Map<dynamic, ({Function func, List<dynamic>? args})> _pending = {};
|
2024-04-30 23:38:49 +08:00
|
|
|
|
2025-06-07 01:48:34 +08:00
|
|
|
void call(
|
2026-02-02 10:14:13 +08:00
|
|
|
dynamic tag,
|
2024-12-09 01:40:39 +08:00
|
|
|
Function func, {
|
|
|
|
|
List<dynamic>? args,
|
2025-07-31 17:09:18 +08:00
|
|
|
Duration? duration,
|
2024-12-09 01:40:39 +08:00
|
|
|
}) {
|
2026-09-05 20:09:39 +08:00
|
|
|
_operations.remove(tag)?.cancel();
|
|
|
|
|
_pending[tag] = (func: func, args: args);
|
2025-07-31 17:09:18 +08:00
|
|
|
_operations[tag] = Timer(duration ?? const Duration(milliseconds: 600), () {
|
|
|
|
|
_operations.remove(tag);
|
2026-09-05 20:09:39 +08:00
|
|
|
final pending = _pending.remove(tag);
|
|
|
|
|
if (pending == null) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
_applyAndReport(tag, pending.func, pending.args);
|
2025-07-31 17:09:18 +08:00
|
|
|
});
|
2024-12-09 01:40:39 +08:00
|
|
|
}
|
2024-04-30 23:38:49 +08:00
|
|
|
|
2026-09-05 20:09:39 +08:00
|
|
|
void flush(dynamic tag) {
|
|
|
|
|
final timer = _operations.remove(tag);
|
|
|
|
|
timer?.cancel();
|
|
|
|
|
final pending = _pending.remove(tag);
|
|
|
|
|
if (timer == null || pending == null) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
_applyAndReport(tag, pending.func, pending.args);
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-07 01:48:34 +08:00
|
|
|
void cancel(dynamic tag) {
|
2026-09-05 20:09:39 +08:00
|
|
|
_operations.remove(tag)?.cancel();
|
|
|
|
|
_pending.remove(tag);
|
2024-04-30 23:38:49 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-05 10:57:11 +08:00
|
|
|
class SerialTaskScheduler {
|
|
|
|
|
Future<void> _serialTail = Future<void>.value();
|
|
|
|
|
|
|
|
|
|
Future<T> run<T>(Future<T> Function() task) {
|
|
|
|
|
final completer = Completer<T>();
|
|
|
|
|
_serialTail = _serialTail.then((_) async {
|
|
|
|
|
try {
|
|
|
|
|
completer.complete(await task());
|
|
|
|
|
} catch (error, stackTrace) {
|
|
|
|
|
completer.completeError(error, stackTrace);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
return completer.future;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-03 23:32:00 +08:00
|
|
|
class Throttler {
|
2026-09-05 20:09:39 +08:00
|
|
|
final Map<dynamic, Timer> _operations = {};
|
2025-02-03 23:32:00 +08:00
|
|
|
|
2025-06-07 01:48:34 +08:00
|
|
|
bool call(
|
2026-02-02 10:14:13 +08:00
|
|
|
dynamic tag,
|
2025-02-03 23:32:00 +08:00
|
|
|
Function func, {
|
|
|
|
|
List<dynamic>? args,
|
|
|
|
|
Duration duration = const Duration(milliseconds: 600),
|
2025-07-31 17:09:18 +08:00
|
|
|
bool fire = false,
|
2025-02-03 23:32:00 +08:00
|
|
|
}) {
|
2026-09-05 20:09:39 +08:00
|
|
|
if (_operations.containsKey(tag)) {
|
2025-02-03 23:32:00 +08:00
|
|
|
return true;
|
|
|
|
|
}
|
2025-07-31 17:09:18 +08:00
|
|
|
if (fire) {
|
2026-09-05 20:09:39 +08:00
|
|
|
_applyAndReport(tag, func, args);
|
2025-07-31 17:09:18 +08:00
|
|
|
_operations[tag] = Timer(duration, () {
|
2025-02-03 23:32:00 +08:00
|
|
|
_operations.remove(tag);
|
2025-07-31 17:09:18 +08:00
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
_operations[tag] = Timer(duration, () {
|
2026-09-05 20:09:39 +08:00
|
|
|
_applyAndReport(tag, func, args);
|
2025-07-31 17:09:18 +08:00
|
|
|
_operations.remove(tag);
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-02-03 23:32:00 +08:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-07 01:48:34 +08:00
|
|
|
void cancel(dynamic tag) {
|
2026-09-05 20:09:39 +08:00
|
|
|
_operations.remove(tag)?.cancel();
|
2025-02-03 23:32:00 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-09 18:39:38 +08:00
|
|
|
Future<T> retry<T>({
|
|
|
|
|
required Future<T> Function() task,
|
|
|
|
|
int maxAttempts = 3,
|
|
|
|
|
required bool Function(T res) retryIf,
|
2025-09-23 21:02:47 +08:00
|
|
|
Duration delay = midDuration,
|
2025-02-09 18:39:38 +08:00
|
|
|
}) async {
|
|
|
|
|
int attempts = 0;
|
|
|
|
|
while (attempts < maxAttempts) {
|
|
|
|
|
final res = await task();
|
2026-02-02 10:14:13 +08:00
|
|
|
attempts++;
|
2025-02-09 18:39:38 +08:00
|
|
|
if (!retryIf(res) || attempts >= maxAttempts) {
|
|
|
|
|
return res;
|
|
|
|
|
}
|
2026-02-02 10:14:13 +08:00
|
|
|
await Future.delayed(delay);
|
2025-02-09 18:39:38 +08:00
|
|
|
}
|
2026-09-05 20:09:39 +08:00
|
|
|
throw TimeoutException('retry gave up after $maxAttempts attempts');
|
2025-02-09 18:39:38 +08:00
|
|
|
}
|
2025-02-03 23:32:00 +08:00
|
|
|
|
2024-12-09 01:40:39 +08:00
|
|
|
final debouncer = Debouncer();
|
2025-02-03 23:32:00 +08:00
|
|
|
|
2025-02-09 18:39:38 +08:00
|
|
|
final throttler = Throttler();
|
2026-09-05 20:09:39 +08:00
|
|
|
|
2026-09-25 03:56:56 +08:00
|
|
|
/// Frames stop while the window is hidden, so a post-frame callback alone
|
|
|
|
|
/// would hold tray and hotkey work back until the window is shown again.
|
|
|
|
|
void runAfterFrame(VoidCallback callback) {
|
|
|
|
|
final binding = SchedulerBinding.instance;
|
|
|
|
|
if (!binding.framesEnabled) {
|
|
|
|
|
Timer.run(callback);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
binding.addPostFrameCallback((_) => callback());
|
|
|
|
|
binding.scheduleFrame();
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-05 20:09:39 +08:00
|
|
|
FutureOr<T> handleWatch<T>({
|
|
|
|
|
required Function function,
|
|
|
|
|
required void Function() onStart,
|
|
|
|
|
required void Function(T data, int elapsedMilliseconds) onEnd,
|
|
|
|
|
}) async {
|
|
|
|
|
if (kDebugMode && watchExecution) {
|
|
|
|
|
onStart();
|
|
|
|
|
final stopwatch = Stopwatch()..start();
|
|
|
|
|
final res = await function();
|
|
|
|
|
stopwatch.stop();
|
|
|
|
|
onEnd(res, stopwatch.elapsedMilliseconds);
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
return await function();
|
|
|
|
|
}
|