Files
plezy/lib/services/device_adjustment_service.dart
T

128 lines
4.3 KiB
Dart

import 'dart:async' show unawaited;
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import '../utils/app_logger.dart';
/// Mobile-only bridge for screen brightness and OS media volume.
///
/// Values are normalized to 0.0-1.0. Unsupported platforms return null for
/// getters and ignore setters.
class DeviceAdjustmentService {
DeviceAdjustmentService({MethodChannel? channel}) : _channel = channel ?? const MethodChannel(_channelName);
static const String _channelName = 'com.plezy/device_adjustment';
static final DeviceAdjustmentService instance = DeviceAdjustmentService();
final MethodChannel _channel;
AppLifecycleListener? _lifecycleListener;
VoidCallback? onResume;
bool _restoreSuppressed = false;
bool _brightnessChanged = false;
int _brightnessGeneration = 0;
Future<void> _brightnessQueue = Future<void>.value();
Future<double?> getBrightness() => _invokeDouble('getBrightness');
Future<void> setBrightness(double value) async {
_ensureLifecycleListener();
if (!value.isFinite) return;
final clamped = value.clamp(0.0, 1.0).toDouble();
final generation = ++_brightnessGeneration;
_brightnessChanged = true;
return _enqueueBrightnessOperation(() async {
if (generation != _brightnessGeneration) return;
try {
await _channel.invokeMethod<void>('setBrightness', clamped);
} on MissingPluginException {
// Unsupported platform: keep gestures harmless.
if (generation == _brightnessGeneration) _brightnessChanged = false;
} on PlatformException catch (error, stackTrace) {
if (generation == _brightnessGeneration) _brightnessChanged = false;
appLogger.w('Failed to set screen brightness', error: error, stackTrace: stackTrace);
}
});
}
Future<void> restoreBrightness() async {
if (!_brightnessChanged) return;
final generation = ++_brightnessGeneration;
return _enqueueBrightnessOperation(() async {
if (generation != _brightnessGeneration || !_brightnessChanged) return;
try {
await _channel.invokeMethod<void>('restoreBrightness');
if (generation == _brightnessGeneration) _brightnessChanged = false;
} on MissingPluginException {
if (generation == _brightnessGeneration) _brightnessChanged = false;
} on PlatformException catch (error, stackTrace) {
appLogger.w('Failed to restore screen brightness', error: error, stackTrace: stackTrace);
}
});
}
Future<void> _enqueueBrightnessOperation(Future<void> Function() operation) {
final next = _brightnessQueue.catchError((_) {}).then((_) => operation());
_brightnessQueue = next.catchError((_) {});
return next;
}
Future<double?> getMediaVolume() => _invokeDouble('getMediaVolume');
Future<void> setMediaVolume(double value) async {
final clamped = value.clamp(0.0, 1.0);
try {
await _channel.invokeMethod<void>('setMediaVolume', clamped);
} on MissingPluginException {
// Unsupported platform: keep gestures harmless.
} on PlatformException catch (error, stackTrace) {
appLogger.w('Failed to set media volume', error: error, stackTrace: stackTrace);
}
}
Future<double?> _invokeDouble(String method) async {
try {
final value = await _channel.invokeMethod<num?>(method);
final normalized = value?.toDouble();
return normalized?.clamp(0.0, 1.0).toDouble();
} on MissingPluginException {
return null;
} on PlatformException catch (error, stackTrace) {
appLogger.w('Failed to call $method', error: error, stackTrace: stackTrace);
return null;
}
}
void _ensureLifecycleListener() {
_lifecycleListener ??= AppLifecycleListener(
onHide: _restoreBrightnessFromLifecycle,
onPause: _restoreBrightnessFromLifecycle,
onResume: _handleResume,
onShow: _handleResume,
onDetach: _restoreBrightnessFromLifecycle,
);
}
void setRestoreSuppressed(bool suppressed) {
_restoreSuppressed = suppressed;
}
void _restoreBrightnessFromLifecycle() {
if (_restoreSuppressed) return;
unawaited(restoreBrightness());
}
void _handleResume() {
onResume?.call();
}
void dispose() {
_lifecycleListener?.dispose();
_lifecycleListener = null;
onResume = null;
_restoreSuppressed = false;
}
}