etm-powersync-app/lib/providers/pv_forecast_provider.dart
Patrick Schurig b65b5474fb feat(accueil): choisir le jour de la carte « Journée »
Demande de Patrick du 2026-10-16 (inspiré du sélecteur Victron, réduit) :
‹ jour › avec calendrier, raccourcis Hier / Aujourd'hui / Demain, et un
lien « Plusieurs jours » vers l'écran Énergie — pas de plage de plusieurs
jours dans une carte à colonnes horaires.

- Jour passé : mesures du jour et prévision ENREGISTRÉE telle qu'elle
  était connue à son début (GetPvForecast + knownAt) — sinon la table
  rendrait la dernière révision, faite après coup.
- Demain : prévision seule ; au-delà, flèche désactivée (horizon).
- Météo de maintenant sur aujourd'hui seulement ; rangée horaire si la
  série couvre le jour (aujourd'hui, demain).
- Une réponse tardive d'un jour quitté est jetée.
- Correctif trouvé par le test de navigation : la carte ne relisait ses
  mesures qu'après une prévision lue avec succès ; sur une box sans
  prévision, elle restait sur le sablier. Elle suit désormais le compteur
  de lectures du provider.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 06:12:01 +02:00

101 lines
3.2 KiB
Dart

import 'dart:async';
import 'package:flutter/foundation.dart';
import '../models/pv_forecast.dart';
import '../services/nymea_service.dart';
/// Intervalle de relecture. La prévision ne notifie RIEN (décision moteur) : sans
/// relecture, l'écran laissé ouvert montrerait la courbe du matin le soir.
const Duration kRelecturePrevision = Duration(minutes: 12);
/// **La prévision PV de la box courante**, partagée par l'accueil et l'écran Énergie.
///
/// Une seule lecture et un seul minuteur pour deux écrans : deux cartes qui liraient
/// chacune de leur côté pourraient montrer deux courbes différentes à quelques minutes
/// d'écart, et rien ne dirait laquelle est la plus récente.
///
/// La lecture n'est lancée qu'à la demande ([assurer]) — l'écran qui s'ouvre la déclenche,
/// puis le minuteur prend le relais.
class PvForecastProvider extends ChangeNotifier {
NymeaService? _service;
Timer? _timer;
bool _chargement = false;
bool _luUneFois = false;
PvForecastSnapshot? _snapshot;
String? _erreur;
PvForecastSite? _site;
String? _refusDesignation;
int _lectures = 0;
bool get chargement => _chargement || !_luUneFois;
PvForecastSnapshot? get snapshot => _snapshot;
/// Non nul = la prévision n'a pas été LUE. Ce n'est pas « rien de publié ».
String? get erreur => _erreur;
PvForecastSite? get site => _site;
String? get refusDesignation => _refusDesignation;
/// Nombre de lectures TERMINÉES, réussies ou non. Un écran qui relit ses propres données
/// au rythme de la prévision s'accroche à ce compteur, pas à `capturedAt` : sans
/// prévision lisible, `capturedAt` ne bouge jamais, et l'écran attendrait pour rien.
int get lectures => _lectures;
void attach(NymeaService s) {
if (identical(_service, s)) return;
_service = s;
}
/// À appeler à l'ouverture d'un écran qui montre la prévision : relit tout de suite, et
/// arme la relecture périodique si elle ne l'est pas déjà.
Future<void> assurer() async {
_timer ??= Timer.periodic(kRelecturePrevision, (_) => relire());
await relire();
}
Future<void> relire() async {
final s = _service;
if (s == null || _chargement) return;
_chargement = true;
notifyListeners();
try {
final r = await s.fetchForecastSnapshot();
_snapshot = r.snapshot;
_erreur = r.erreur;
_site = r.snapshot?.etat == PvForecastEtat.siteManquant
? await s.fetchForecastSite()
: null;
} finally {
_chargement = false;
_luUneFois = true;
_lectures++;
notifyListeners();
}
}
/// Désigne le site — geste explicite de l'utilisateur, jamais automatique.
Future<void> designer(String siteId) async {
final s = _service;
if (s == null) return;
_refusDesignation = await s.setForecastSite(siteId);
await relire();
}
/// Remet à zéro au changement d'installation — la prévision appartient à la box quittée.
void clearForSwitch() {
_snapshot = null;
_erreur = null;
_site = null;
_refusDesignation = null;
_luUneFois = false;
notifyListeners();
}
@override
void dispose() {
_timer?.cancel();
super.dispose();
}
}