| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140 |
- import 'dart:async';
- import 'package:flutter/material.dart';
- import '../../data/repositories/settings_repository.dart';
- import 'theme_preference.dart';
- /// Resolves the light/dark choice, including the fixed time-of-day schedule.
- ///
- /// Automatic mode is dark from [nightStartMinutes] until [nightEndMinutes],
- /// wrapping around midnight (19:00 -> 07:00 by default). A timer wakes exactly
- /// at the next boundary so the app can switch without polling.
- class ThemeController extends ChangeNotifier {
- ThemeController(this._settings) {
- _preference = _settings.themePreference;
- _nightStartMinutes = _settings.nightStartMinutes;
- _nightEndMinutes = _settings.nightEndMinutes;
- _resolvedDark = _computeDark(DateTime.now());
- _scheduleBoundary();
- }
- final SettingsRepository _settings;
- ThemePreference _preference = ThemePreference.automatic;
- int _nightStartMinutes = 19 * 60;
- int _nightEndMinutes = 7 * 60;
- bool _resolvedDark = false;
- Timer? _boundaryTimer;
- bool _disposed = false;
- ThemePreference get preference => _preference;
- int get nightStartMinutes => _nightStartMinutes;
- int get nightEndMinutes => _nightEndMinutes;
- bool get isDark => _resolvedDark;
- ThemeMode get themeMode => _resolvedDark ? ThemeMode.dark : ThemeMode.light;
- Future<void> setPreference(ThemePreference preference) async {
- if (_preference == preference) return;
- _preference = preference;
- _resolvedDark = _computeDark(DateTime.now());
- await _settings.setThemePreference(preference);
- _scheduleBoundary();
- _notify();
- }
- Future<void> setNightWindow({
- required int startMinutes,
- required int endMinutes,
- }) async {
- _nightStartMinutes = startMinutes.clamp(0, 1439);
- _nightEndMinutes = endMinutes.clamp(0, 1439);
- await _settings.setNightWindow(
- startMinutes: _nightStartMinutes,
- endMinutes: _nightEndMinutes,
- );
- _resolvedDark = _computeDark(DateTime.now());
- _scheduleBoundary();
- _notify();
- }
- /// Re-evaluates the schedule after the app resumes or a settings page closes.
- void refreshNow() {
- if (_preference != ThemePreference.automatic) return;
- final next = _computeDark(DateTime.now());
- if (next != _resolvedDark) {
- _resolvedDark = next;
- _notify();
- }
- _scheduleBoundary();
- }
- bool _computeDark(DateTime now) {
- switch (_preference) {
- case ThemePreference.light:
- return false;
- case ThemePreference.dark:
- return true;
- case ThemePreference.automatic:
- final minutes = now.hour * 60 + now.minute;
- if (_nightStartMinutes == _nightEndMinutes) return false;
- if (_nightStartMinutes < _nightEndMinutes) {
- return minutes >= _nightStartMinutes && minutes < _nightEndMinutes;
- }
- return minutes >= _nightStartMinutes || minutes < _nightEndMinutes;
- }
- }
- void _scheduleBoundary() {
- _boundaryTimer?.cancel();
- _boundaryTimer = null;
- if (_preference != ThemePreference.automatic) return;
- final now = DateTime.now();
- final next = _nextBoundary(now);
- if (next == null) return;
- _boundaryTimer = Timer(
- next.difference(now) + const Duration(seconds: 1),
- () {
- _resolvedDark = _computeDark(DateTime.now());
- _notify();
- _scheduleBoundary();
- },
- );
- }
- DateTime? _nextBoundary(DateTime now) {
- DateTime at(int minutes) {
- return DateTime(
- now.year,
- now.month,
- now.day,
- minutes ~/ 60,
- minutes % 60,
- );
- }
- final candidates = <DateTime>[];
- for (final minutes in {_nightStartMinutes, _nightEndMinutes}) {
- var candidate = at(minutes);
- if (!candidate.isAfter(now)) {
- candidate = candidate.add(const Duration(days: 1));
- }
- candidates.add(candidate);
- }
- if (candidates.isEmpty) return null;
- candidates.sort();
- return candidates.first;
- }
- void _notify() {
- if (!_disposed) notifyListeners();
- }
- @override
- void dispose() {
- _disposed = true;
- _boundaryTimer?.cancel();
- super.dispose();
- }
- }
|