Auto dark/bright takes a light sensor where the device allows one
lib/theme/light.dart: dark at 15 lux or less, bright from 60, held 5 s; Settings shows the reading. The Redmi Pad SE refuses apps its only light sensor (Xiaomi's, from the front camera), so there Auto keeps following Android's dark theme. DASH-NEXT.md records the other lux sources. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
// Auto dark/bright from the tablet's light sensor. The Redmi Pad SE has no separate one:
|
||||
// Xiaomi derives it from the front camera ("camera_light_Sensor") and offers it as
|
||||
// Android's ordinary light sensor, the one auto-brightness reads. No camera permission.
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
/// Light levels in lux, about once a second, from MainActivity. Ends at once (an error,
|
||||
/// which [Themes.followLight] ignores) where there is no sensor or no Android side.
|
||||
Stream<double> lightLevels() =>
|
||||
const EventChannel('yukart/light')
|
||||
.receiveBroadcastStream()
|
||||
.map((e) => (e as num).toDouble());
|
||||
|
||||
/// Dark or bright from light levels, with a gap and a hold so dusk, shade and
|
||||
/// passing lights do not flicker it.
|
||||
class LightSwitch {
|
||||
/// Dark at or below this.
|
||||
static const darkAt = 15.0;
|
||||
|
||||
/// Bright at or above this; in between, whatever it was.
|
||||
static const brightAt = 60.0;
|
||||
|
||||
/// How long a level must last before it switches.
|
||||
static const hold = Duration(seconds: 5);
|
||||
|
||||
bool? _dark;
|
||||
DateTime? _since;
|
||||
|
||||
bool? get isDark => _dark;
|
||||
|
||||
/// A change is waiting out its hold.
|
||||
bool get pending => _since != null;
|
||||
|
||||
/// The hold has passed with no reading against it (a sensor that reports only on
|
||||
/// change goes quiet): make the waiting change.
|
||||
void settle() {
|
||||
if (_since == null) return;
|
||||
_dark = !_dark!;
|
||||
_since = null;
|
||||
}
|
||||
|
||||
/// Feed one reading; returns the verdict (null until a reading outside the gap).
|
||||
bool? feed(double lux, DateTime now) {
|
||||
final want = lux <= darkAt
|
||||
? true
|
||||
: lux >= brightAt
|
||||
? false
|
||||
: _dark;
|
||||
if (_dark == null) {
|
||||
// The first clear reading decides at once; one in the gap leaves it open.
|
||||
_dark = want;
|
||||
} else if (want == _dark) {
|
||||
_since = null;
|
||||
} else {
|
||||
_since ??= now;
|
||||
if (now.difference(_since!) >= hold) {
|
||||
_dark = want;
|
||||
_since = null;
|
||||
}
|
||||
}
|
||||
return _dark;
|
||||
}
|
||||
}
|
||||
+52
-7
@@ -15,6 +15,7 @@
|
||||
//
|
||||
// Pick the theme at build time: `--dart-define=YUKART_THEME=default` (default: rei, smart-serow's).
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:math' show max, min;
|
||||
|
||||
@@ -22,6 +23,8 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import 'light.dart';
|
||||
|
||||
/// The HUD face (smart-serow's DIN 1451). Opt-in: a widget names it in its own
|
||||
/// TextStyle; everything else keeps the basic font, which covers CJK where DIN does not.
|
||||
const hudFont = 'DIN1451';
|
||||
@@ -117,12 +120,12 @@ class Palette {
|
||||
);
|
||||
}
|
||||
|
||||
/// Which variant shows: Android's own dark theme (auto), or one held fixed.
|
||||
/// Which variant shows: auto (the light sensor, else Android's dark theme), or one held fixed.
|
||||
enum VariantMode { auto, dark, bright }
|
||||
|
||||
/// A theme: both variants, and which one is showing. Listen to it to follow a switch,
|
||||
/// and, in auto, Android's dark theme turning on or off (by hand or on its schedule).
|
||||
/// The mode is kept across restarts.
|
||||
/// A theme: both variants, and which one is showing. Listen to it to follow a switch.
|
||||
/// In auto it follows the tablet's light sensor ([followLight]); without one, Android's
|
||||
/// dark theme (by hand or on its schedule). The mode is kept across restarts.
|
||||
class Themes extends ChangeNotifier with WidgetsBindingObserver {
|
||||
Themes(this.name, this.dark, this.bright, [this._prefs]) {
|
||||
_mode =
|
||||
@@ -148,10 +151,50 @@ class Themes extends ChangeNotifier with WidgetsBindingObserver {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
final _light = LightSwitch();
|
||||
Timer? _holdTimer;
|
||||
|
||||
/// The last light level, in lux, while a sensor reports (for Settings, to tune by).
|
||||
final lux = ValueNotifier<double?>(null);
|
||||
|
||||
/// Follow a light sensor in auto. Errors (no sensor) leave auto on Android's dark theme.
|
||||
void followLight(Stream<double> levels) {
|
||||
void changed(bool? before) {
|
||||
if (_light.isDark != before && _mode == VariantMode.auto) {
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
void feed(double l) {
|
||||
final before = _light.isDark;
|
||||
_light.feed(l, DateTime.now());
|
||||
changed(before);
|
||||
// A sensor may report only on change: finish a hold even if none comes.
|
||||
_holdTimer?.cancel();
|
||||
if (_light.pending) {
|
||||
_holdTimer = Timer(LightSwitch.hold, () {
|
||||
final before = _light.isDark;
|
||||
_light.settle();
|
||||
changed(before);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
levels.listen(
|
||||
(l) {
|
||||
lux.value = l;
|
||||
feed(l);
|
||||
},
|
||||
onError: (_) => lux.value = null,
|
||||
cancelOnError: true,
|
||||
);
|
||||
}
|
||||
|
||||
bool get isDark => switch (_mode) {
|
||||
VariantMode.auto =>
|
||||
WidgetsBinding.instance.platformDispatcher.platformBrightness ==
|
||||
Brightness.dark,
|
||||
_light.isDark ??
|
||||
WidgetsBinding.instance.platformDispatcher.platformBrightness ==
|
||||
Brightness.dark,
|
||||
VariantMode.dark => true,
|
||||
VariantMode.bright => false,
|
||||
};
|
||||
@@ -163,12 +206,14 @@ class Themes extends ChangeNotifier with WidgetsBindingObserver {
|
||||
|
||||
@override
|
||||
void didChangePlatformBrightness() {
|
||||
if (_mode == VariantMode.auto) notifyListeners();
|
||||
if (_mode == VariantMode.auto && _light.isDark == null) notifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
_holdTimer?.cancel();
|
||||
lux.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user