import 'dart:async'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../models/busylight_color.dart'; import '../models/busylight_status.dart'; import '../services/busylight_service.dart'; // ── Device config ──────────────────────────────────────────────────────────── const _kHostKey = 'busylight_host'; const _kDefaultHost = 'http://igox-busylight.local'; const _kPollIntervalKey = 'busylight_poll_interval'; const _kDefaultPollInterval = 5; // seconds final sharedPreferencesProvider = FutureProvider( (_) => SharedPreferences.getInstance(), ); final deviceHostProvider = StateProvider((ref) { final prefs = ref.watch(sharedPreferencesProvider).valueOrNull; return prefs?.getString(_kHostKey) ?? _kDefaultHost; }); final pollIntervalProvider = StateProvider((ref) { final prefs = ref.watch(sharedPreferencesProvider).valueOrNull; return prefs?.getInt(_kPollIntervalKey) ?? _kDefaultPollInterval; }); // ── Service ────────────────────────────────────────────────────────────────── final busylightServiceProvider = Provider((ref) { final host = ref.watch(deviceHostProvider); return BusylightService(baseUrl: host); }); // ── Startup snapshot ───────────────────────────────────────────────────────── // Loads status + color (which includes brightness) in 2 parallel calls. // No separate GET /api/brightness needed — the color response already has it. class BusylightSnapshot { final BusylightStatus status; final BusylightColor color; const BusylightSnapshot({ required this.status, required this.color, }); double get brightness => color.brightness; } final busylightSnapshotProvider = FutureProvider((ref) async { final service = ref.watch(busylightServiceProvider); final results = await Future.wait([ service.getStatus(), service.getColor(), ]); return BusylightSnapshot( status: results[0] as BusylightStatus, color: results[1] as BusylightColor, ); }); // ── State notifiers ────────────────────────────────────────────────────────── class BusylightStateNotifier extends StateNotifier> { BusylightStateNotifier(this._service, BusylightStatus? initial) : super(initial != null ? AsyncValue.data(initial) : const AsyncValue.loading()) { if (initial == null) refresh(); } final BusylightService _service; Future refresh() async { state = const AsyncValue.loading(); state = await AsyncValue.guard(_service.getStatus); } Future setStatus(BusylightStatus status) async { // Keep current value visible during the API call — no loading state final result = await AsyncValue.guard(() => _service.setStatus(status)); state = result; } /// Update state locally without making an API call (e.g. for `colored`) void setLocalStatus(BusylightStatus status) { state = AsyncValue.data(status); } } final busylightStatusProvider = StateNotifierProvider>( (ref) { final snapshot = ref.watch(busylightSnapshotProvider).valueOrNull; return BusylightStateNotifier( ref.watch(busylightServiceProvider), snapshot?.status, ); }, ); // ── Brightness ─────────────────────────────────────────────────────────────── class BrightnessNotifier extends StateNotifier { BrightnessNotifier(this._service, double initial) : super(initial); final BusylightService _service; Future set(double value) async { state = value; await _service.setBrightness(value); } void silentSet(double value) => state = value; } final brightnessProvider = StateNotifierProvider( (ref) { final snapshot = ref.watch(busylightSnapshotProvider).valueOrNull; return BrightnessNotifier( ref.watch(busylightServiceProvider), snapshot?.brightness ?? 0.3, ); }, ); // ── Color ───────────────────────────────────────────────────────────────────── class ColorNotifier extends StateNotifier { ColorNotifier(this._service, BusylightColor initial) : super(initial); final BusylightService _service; Future set(BusylightColor color) async { state = color; await _service.setColor(color); } void silentSet(BusylightColor color) => state = color; } final colorProvider = StateNotifierProvider( (ref) { final snapshot = ref.watch(busylightSnapshotProvider).valueOrNull; return ColorNotifier( ref.watch(busylightServiceProvider), snapshot?.color ?? BusylightColor.white, ); }, ); // ── Background polling ──────────────────────────────────────────────────────── // Periodically pulls status + color from the device and silently updates state. class PollingNotifier extends StateNotifier { PollingNotifier(this._ref) : super(null) { _start(); } final Ref _ref; Timer? _timer; void _start() { final interval = _ref.read(pollIntervalProvider); _timer?.cancel(); if (interval <= 0) return; _timer = Timer.periodic(Duration(seconds: interval), (_) => _poll()); } void restart() => _start(); Future _poll() async { try { final service = _ref.read(busylightServiceProvider); final results = await Future.wait([ service.getStatus(), service.getColor(), ]); final status = results[0] as BusylightStatus; final color = results[1] as BusylightColor; _ref.read(busylightStatusProvider.notifier).setLocalStatus(status); _ref.read(colorProvider.notifier).silentSet(color); _ref.read(brightnessProvider.notifier).silentSet(color.brightness); } catch (_) { // Silently ignore poll errors — connection issues are shown on manual refresh } } @override void dispose() { _timer?.cancel(); super.dispose(); } } final pollingProvider = StateNotifierProvider( (ref) => PollingNotifier(ref), );