Auto dark/bright takes a light sensor where the device allows one
/ crosscheck (push) Successful in 1s
CI / Analyze & Test (push) Successful in 21s
CI / Debug APK (push) Successful in 41s

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:
2026-10-15 22:24:36 +09:00
parent dcafc04cbc
commit 3398da3823
8 changed files with 215 additions and 13 deletions
+63
View File
@@ -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;
}
}