import 'dart:async'; import 'package:hiddify/features/log/data/log_data_providers.dart'; import 'package:hiddify/features/log/model/log_entity.dart'; import 'package:hiddify/features/log/model/log_level.dart'; import 'package:hiddify/features/log/overview/logs_overview_state.dart'; import 'package:hiddify/utils/riverpod_utils.dart'; import 'package:hiddify/utils/utils.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:rxdart/rxdart.dart'; part 'logs_overview_notifier.g.dart'; @riverpod class LogsOverviewNotifier extends _$LogsOverviewNotifier with AppLogger { @override LogsOverviewState build() { ref.disposeDelay(const Duration(seconds: 20)); state = const LogsOverviewState(); ref.onDispose( () { loggy.debug("disposing"); _listener?.cancel(); _listener = null; }, ); ref.onCancel( () { if (_listener?.isPaused != true) { loggy.debug("pausing"); _listener?.pause(); } }, ); ref.onResume( () { if (!state.paused && (_listener?.isPaused ?? false)) { loggy.debug("resuming"); _listener?.resume(); } }, ); _addListeners(); return const LogsOverviewState(); } StreamSubscription? _listener; Future _addListeners() async { loggy.debug("adding listeners"); await _listener?.cancel(); _listener = ref .read(logRepositoryProvider) .requireValue .watchLogs() .throttle( (_) => Stream.value(_listener?.isPaused ?? false), leading: false, trailing: true, ) .throttleTime( const Duration(milliseconds: 250), leading: false, trailing: true, ) .asyncMap( (event) async { await event.fold( (f) { _logs = []; state = state.copyWith(logs: AsyncError(f, StackTrace.current)); }, (a) async { _logs = a.reversed; state = state.copyWith(logs: AsyncData(await _computeLogs())); }, ); }, ).listen((event) {}); } Iterable _logs = []; final _debouncer = CallbackDebouncer(const Duration(milliseconds: 200)); LogLevel? _levelFilter; String _filter = ""; Future> _computeLogs() async { if (_levelFilter == null && _filter.isEmpty) return _logs.toList(); return _logs.where((e) { return (_filter.isEmpty || e.message.contains(_filter)) && (_levelFilter == null || e.level == null || e.level!.index >= _levelFilter!.index); }).toList(); } void pause() { loggy.debug("pausing"); _listener?.pause(); state = state.copyWith(paused: true); } void resume() { loggy.debug("resuming"); _listener?.resume(); state = state.copyWith(paused: false); } Future clear() async { loggy.debug("clearing"); await ref.read(logRepositoryProvider).requireValue.clearLogs().match( (l) { loggy.warning("error clearing logs", l); }, (_) { _logs = []; state = state.copyWith(logs: const AsyncData([])); }, ).run(); } void filterMessage(String? filter) { _filter = filter ?? ''; _debouncer( () async { if (state.logs case AsyncData()) { state = state.copyWith( filter: _filter, logs: AsyncData(await _computeLogs()), ); } }, ); } Future filterLevel(LogLevel? level) async { _levelFilter = level; if (state.logs case AsyncData()) { state = state.copyWith( levelFilter: _levelFilter, logs: AsyncData(await _computeLogs()), ); } } }