165 lines
4.2 KiB
Dart
165 lines
4.2 KiB
Dart
import 'dart:async';
|
|
import 'dart:io' show Platform;
|
|
import 'dart:ui' show window;
|
|
|
|
import 'package:flutter/widgets.dart';
|
|
import 'package:screen_brightness/screen_brightness.dart';
|
|
import 'package:screen_state/screen_state.dart';
|
|
import 'package:volume_controller/volume_controller.dart';
|
|
|
|
/// Native runtime observation fields backed by platform plugins.
|
|
class RuntimeObservationMonitor {
|
|
final List<StreamSubscription<dynamic>> _subscriptions;
|
|
final WidgetsBindingObserver? _metricsObserver;
|
|
|
|
const RuntimeObservationMonitor(
|
|
this._subscriptions, {
|
|
WidgetsBindingObserver? metricsObserver,
|
|
}) : _metricsObserver = metricsObserver;
|
|
|
|
Future<void> dispose() async {
|
|
for (final subscription in _subscriptions) {
|
|
await subscription.cancel();
|
|
}
|
|
final metricsObserver = _metricsObserver;
|
|
if (metricsObserver != null) {
|
|
try {
|
|
WidgetsBinding.instance.removeObserver(metricsObserver);
|
|
} catch (_) {}
|
|
}
|
|
try {
|
|
VolumeController().removeListener();
|
|
} catch (_) {}
|
|
}
|
|
}
|
|
|
|
Future<Map<String, dynamic>> collectRuntimeObservationFields() async {
|
|
return {
|
|
..._screenObservationFields(),
|
|
'screen_brightness': await _readScreenBrightness(),
|
|
'volume': await _readVolume(),
|
|
'is_screen_on': _initialScreenOn(),
|
|
};
|
|
}
|
|
|
|
RuntimeObservationMonitor startRuntimeObservationMonitoring(
|
|
void Function(Map<String, dynamic> fields) onFields,
|
|
) {
|
|
final subscriptions = <StreamSubscription<dynamic>>[];
|
|
WidgetsBindingObserver? metricsObserver;
|
|
|
|
try {
|
|
metricsObserver = _MetricsObserver(
|
|
onMetricsChanged: () => onFields(_screenObservationFields()),
|
|
);
|
|
WidgetsBinding.instance.addObserver(metricsObserver);
|
|
} catch (_) {}
|
|
|
|
try {
|
|
subscriptions.add(ScreenBrightness().onCurrentBrightnessChanged.listen(
|
|
(value) => onFields({
|
|
'screen_brightness': _normalizedPercent(value),
|
|
}), onError: (_) {
|
|
onFields({'screen_brightness': -1});
|
|
}));
|
|
} catch (_) {}
|
|
|
|
try {
|
|
subscriptions.add(VolumeController().listener((value) {
|
|
onFields({'volume': _normalizedPercent(value)});
|
|
}));
|
|
} catch (_) {}
|
|
|
|
try {
|
|
if (Platform.isAndroid || Platform.isIOS) {
|
|
subscriptions.add(Screen().screenStateStream.listen((event) {
|
|
onFields({'is_screen_on': _screenStateValue(event)});
|
|
}, onError: (_) {
|
|
onFields({'is_screen_on': -1});
|
|
}));
|
|
}
|
|
} catch (_) {}
|
|
|
|
return RuntimeObservationMonitor(
|
|
subscriptions,
|
|
metricsObserver: metricsObserver,
|
|
);
|
|
}
|
|
|
|
Map<String, dynamic> _screenObservationFields() {
|
|
try {
|
|
final size = window.physicalSize;
|
|
final width = size.width.round();
|
|
final height = size.height.round();
|
|
final pixelRatio = (window.devicePixelRatio * 100).round();
|
|
final dpi = (window.devicePixelRatio * 160).round();
|
|
return {
|
|
'screen_width': width,
|
|
'screen_height': height,
|
|
'screen_dpi': dpi,
|
|
'device_pixel_ratio': pixelRatio,
|
|
'orientation': width >= height ? 'landscape' : 'portrait',
|
|
};
|
|
} catch (_) {
|
|
return const {
|
|
'screen_width': -1,
|
|
'screen_height': -1,
|
|
'screen_dpi': -1,
|
|
'device_pixel_ratio': -1,
|
|
'orientation': '',
|
|
};
|
|
}
|
|
}
|
|
|
|
Future<int> _readScreenBrightness() async {
|
|
try {
|
|
return _normalizedPercent(await ScreenBrightness().current);
|
|
} catch (_) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
Future<int> _readVolume() async {
|
|
try {
|
|
return _normalizedPercent(await VolumeController().getVolume());
|
|
} catch (_) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
int _initialScreenOn() {
|
|
try {
|
|
return Platform.isAndroid || Platform.isIOS ? 1 : -1;
|
|
} catch (_) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
int _screenStateValue(ScreenStateEvent event) {
|
|
switch (event) {
|
|
case ScreenStateEvent.SCREEN_ON:
|
|
case ScreenStateEvent.SCREEN_UNLOCKED:
|
|
return 1;
|
|
case ScreenStateEvent.SCREEN_OFF:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
int _normalizedPercent(num value) {
|
|
final numeric = value.toDouble();
|
|
if (!numeric.isFinite) return -1;
|
|
final scaled = numeric <= 1 ? numeric * 100 : numeric;
|
|
return scaled.clamp(0, 100).round();
|
|
}
|
|
|
|
class _MetricsObserver with WidgetsBindingObserver {
|
|
final void Function() onMetricsChanged;
|
|
|
|
const _MetricsObserver({required this.onMetricsChanged});
|
|
|
|
@override
|
|
void didChangeMetrics() {
|
|
onMetricsChanged();
|
|
}
|
|
}
|