system_controller.dart 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. import 'dart:async';
  2. import 'package:flutter/foundation.dart';
  3. import '../../data/models/system_status.dart';
  4. import '../../data/services/native_bridge.dart';
  5. /// Polls the system for battery, Wi-Fi and media volume.
  6. ///
  7. /// Polling keeps the app free of long-lived broadcast receivers and works
  8. /// identically on Android 10 and 15. Timers are stopped while the launcher is
  9. /// in the background.
  10. class SystemController extends ChangeNotifier {
  11. SystemController(this._platform);
  12. final LauncherPlatform _platform;
  13. SystemStatus _status = const SystemStatus();
  14. VolumeInfo _volume = const VolumeInfo();
  15. Timer? _volumeTimer;
  16. Timer? _statusTimer;
  17. bool _active = false;
  18. bool _volumeBusy = false;
  19. bool _statusBusy = false;
  20. bool _disposed = false;
  21. int _lastNonZeroVolume = 0;
  22. SystemStatus get status => _status;
  23. VolumeInfo get volume => _volume;
  24. int get volumePercent => _volume.percent;
  25. bool get isMuted => _volume.index <= 0;
  26. Future<void> start() async {
  27. if (_active) return;
  28. _active = true;
  29. await refreshAll();
  30. _volumeTimer = Timer.periodic(
  31. const Duration(milliseconds: 900),
  32. (_) => _pollVolume(),
  33. );
  34. _statusTimer = Timer.periodic(
  35. const Duration(seconds: 20),
  36. (_) => _pollStatus(),
  37. );
  38. }
  39. void stop() {
  40. _active = false;
  41. _volumeTimer?.cancel();
  42. _volumeTimer = null;
  43. _statusTimer?.cancel();
  44. _statusTimer = null;
  45. }
  46. Future<void> refreshAll() async {
  47. await Future.wait([_pollVolume(), _pollStatus()]);
  48. }
  49. Future<void> _pollVolume() async {
  50. if (_volumeBusy || !_active) return;
  51. _volumeBusy = true;
  52. try {
  53. final next = await _platform.getVolume();
  54. if (next.index != _volume.index || next.max != _volume.max) {
  55. _volume = next;
  56. if (next.index > 0) _lastNonZeroVolume = next.index;
  57. _notify();
  58. }
  59. } finally {
  60. _volumeBusy = false;
  61. }
  62. }
  63. Future<void> _pollStatus() async {
  64. if (_statusBusy) return;
  65. _statusBusy = true;
  66. try {
  67. final next = await _platform.getStatus();
  68. if (next.batteryLevel != _status.batteryLevel ||
  69. next.charging != _status.charging ||
  70. next.wifi != _status.wifi) {
  71. _status = next;
  72. _notify();
  73. }
  74. } finally {
  75. _statusBusy = false;
  76. }
  77. }
  78. Future<void> setVolume(int index) async {
  79. final max = _volume.max <= 0 ? 15 : _volume.max;
  80. final clamped = index.clamp(0, max);
  81. _volume = VolumeInfo(index: clamped, max: max);
  82. if (clamped > 0) _lastNonZeroVolume = clamped;
  83. _notify();
  84. await _platform.setVolume(clamped);
  85. }
  86. Future<void> adjustVolume(int delta) => setVolume(_volume.index + delta);
  87. Future<void> toggleMute() async {
  88. if (_volume.index > 0) {
  89. _lastNonZeroVolume = _volume.index;
  90. await setVolume(0);
  91. } else {
  92. final restore = _lastNonZeroVolume > 0
  93. ? _lastNonZeroVolume
  94. : (_volume.max <= 0
  95. ? 1
  96. : (_volume.max / 2).round().clamp(1, _volume.max));
  97. await setVolume(restore);
  98. }
  99. }
  100. void _notify() {
  101. if (!_disposed) notifyListeners();
  102. }
  103. @override
  104. void dispose() {
  105. _disposed = true;
  106. stop();
  107. super.dispose();
  108. }
  109. }