feat(énergie): glisser sur trois jours — fenêtre fixe de 12 h, et la date avec

Demande de Patrick : élargir le glissement. 72 h chargées au quart d'heure, la
fenêtre en montre 12 — on remonte donc sur trois jours sans changer d'onglet.

TROIS CHOSES QUE CE CHANGEMENT RENDAIT NÉCESSAIRES.

La taille de fenêtre était « la moitié du jeu de données ». Avec 24 h ça donnait
12 h ; avec 72 h ça en aurait donné 36, et la fenêtre aurait grandi avec les
données au lieu de rester une fenêtre. Elle est fixée en DURÉE : la densité de
lecture ne dépend plus de la profondeur chargée.

La fenêtre s'ouvre sur le plus RÉCENT. Calée au début, elle aurait montré
avant-hier soir à l'ouverture — la seule vue dont personne n'a besoin en
premier.

Et la plage visible porte sa DATE : « 8/9 21:00 → 9/9 09:00 ». Sur 24 h,
« 09:00 » désignait sans ambiguïté ce matin ; sur trois jours, c'est l'un de
trois matins, et l'axe n'affiche que des heures. Sans cette ligne, l'écran
répondait à côté de la question « c'était quand ? ».

Et les étiquettes des deux BORDS de l'axe sont retirées : fl_chart en pose une à
`min` et à `max` en plus de celles de l'intervalle, et elles se superposaient à
leur voisine — « 20:4500 » à gauche, « 08:1509:00 » à droite. J'avais corrigé le
bord gauche sans penser que le droit avait le même défaut : la recherche
auto-confirmante, appliquée à un axe. On ne perd rien, la ligne de plage donne
les deux bornes exactes avec leur jour.

Vérifié à l'écran : fenêtre 21:15 → 09:00 à l'ouverture, ligne de plage et
indice de geste présents.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GKM8FDNHZWogAsjjfB8pSw
This commit is contained in:
Patrick Schurig 2026-09-09 09:20:21 +02:00
parent f1547f674a
commit 2a1e7f2dbb

View File

@ -79,7 +79,9 @@ class _EnergyScreenState extends State<EnergyScreen> {
/// et personne ne pouvait obtenir la journée que le premier promettait. Corrigé le
/// 2026-09-09 ; l'onglet « Année » manquait, puisque c'est « Mois » qui la montrait.
static const _tabs = [
_Tab('Heures', Duration(hours: 24), 'SampleRate15Mins', showTime: true),
// 72 h au quart d'heure : la fenêtre n'en montre que 12, mais on peut remonter sur
// trois jours sans changer d'onglet (demande de Patrick, 2026-09-09).
_Tab('Heures', Duration(hours: 72), 'SampleRate15Mins', showTime: true),
_Tab('Jour', Duration(days: 1), 'SampleRate1Hour',
showTime: true, rendu: _Rendu.barres, journeeCivile: true),
_Tab('Semaine', Duration(days: 7), 'SampleRate1Day',
@ -110,10 +112,21 @@ class _EnergyScreenState extends State<EnergyScreen> {
/// heures. Les séries restent entières, la fenêtre ne fait que cadrer.
double _fenetreDebut = 0;
/// Nombre de points visibles à la fois. `null` = tout, donc rien à glisser.
/// **Douze heures visibles à la fois**, quelle que soit la profondeur des données.
///
/// C'était « la moitié du jeu » : avec 24 h de données, ça donnait 12 h — mais en passant
/// à 72 h ça en aurait donné 36, et la fenêtre aurait grandi avec les données au lieu de
/// rester une fenêtre. Une durée fixe garde la même densité de lecture quel que soit
/// l'historique chargé.
static const Duration _fenetreDuree = Duration(hours: 12);
int? get _fenetreTaille {
if (!_tabs[_tabIdx].showTime) return null; // seule la vue Heures glisse
return _data.length > 40 ? _data.length ~/ 2 : null;
final tab = _tabs[_tabIdx];
if (tab.rendu != _Rendu.courbes) return null; // seules les courbes glissent
final pas = _pasEchantillon(tab).inSeconds;
if (pas <= 0) return null;
final points = _fenetreDuree.inSeconds ~/ pas;
return _data.length > points + 1 ? points : null;
}
bool get _glissable => _fenetreTaille != null;
@ -264,13 +277,17 @@ class _EnergyScreenState extends State<EnergyScreen> {
]);
if (!mounted) return;
setState(() {
// La fenêtre repart du début à chaque lecture : la garder ferait cadrer une plage
// d'index qui ne désigne plus les mêmes heures.
_fenetreDebut = 0;
_data = results[0] as List<PowerBalanceEntry>;
_socData = results[1] as List<HistoryEntry>;
_conso = results[2] as ConsumerHistory;
_bilan = results[3] as DailyBalance?;
// **La fenêtre s'ouvre sur le plus RÉCENT.** Avec 72 h chargées, la caler au début
// montrerait avant-hier soir à l'ouverture de l'écran — la seule vue dont personne
// n'a besoin en premier. Recalculée à chaque lecture : garder l'ancienne cadrerait
// une plage d'index qui ne désigne plus les mêmes heures.
final taille = _fenetreTaille;
_fenetreDebut =
taille == null ? 0 : max(0, _data.length - 1 - taille).toDouble();
_loading = false;
_noData = _data.isEmpty;
});
@ -504,11 +521,16 @@ class _EnergyScreenState extends State<EnergyScreen> {
),
// L'indice ne sort que si le geste existe : une aide permanente
// cesse d'être lue, et celle-ci serait fausse sur les autres vues.
if (_glissable && _plageVisible() != null)
_NoteEcran(
icon: Icons.date_range_rounded,
texte: _plageVisible()!,
),
if (_glissable)
const _NoteEcran(
icon: Icons.swipe_rounded,
texte: 'Glissez horizontalement sur un graphe pour vous déplacer '
'dans la journée. Les deux gardent le même cadrage.',
texte: 'Glissez horizontalement pour remonter sur trois jours. '
'Les deux graphes gardent le même cadrage.',
),
],
// Les notes parlent des séries par consommateur : elles n'ont aucun
@ -532,6 +554,24 @@ class _EnergyScreenState extends State<EnergyScreen> {
// ── ① Line chart : puissances + SOC ────────────────────────────────────────
/// **Ce qu'on regarde, avec sa date** — `null` hors vue glissable.
///
/// Sur 24 h, « 09:00 » désignait sans ambiguïté ce matin. Sur trois jours, la même
/// étiquette peut être l'un de trois matins, et l'axe seul ne le dit pas : il n'affiche
/// que des heures. La plage visible porte donc le jour, faute de quoi l'écran répond
/// exactement à côté de la question « c'était quand ? ».
String? _plageVisible() {
if (!_glissable || _data.isEmpty) return null;
final a = _data[_fenetreDebut.round().clamp(0, _data.length - 1)].timestamp;
final b = _data[_fenetreFin.round().clamp(0, _data.length - 1)].timestamp;
String jour(DateTime t) => '${t.day}/${t.month}';
String heure(DateTime t) =>
'${t.hour.toString().padLeft(2, '0')}:${t.minute.toString().padLeft(2, '0')}';
return jour(a) == jour(b)
? '${jour(a)} · ${heure(a)} → ${heure(b)}'
: '${jour(a)} ${heure(a)} → ${jour(b)} ${heure(b)}';
}
/// Rend un graphe **glissable à l'horizontale** quand la vue s'y prête.
///
/// Le geste convertit des pixels en points d'échantillon : glisser d'une largeur d'écran
@ -681,11 +721,17 @@ class _EnergyScreenState extends State<EnergyScreen> {
reservedSize: 22,
interval: xInterval,
getTitlesWidget: (v, meta) {
// ⚠️ Pas d'étiquette sur le bord GAUCHE : fl_chart en pose une à `min` en
// plus de celles de l'intervalle, et les deux se superposent quand `min`
// n'est pas un multiple — « 20:4500 » à l'écran le 2026-09-09. Deux heures
// imprimées l'une sur l'autre ne se lisent ni l'une ni l'autre.
if (v <= meta.min) return const SizedBox.shrink();
// ⚠️ **Aucune étiquette sur les deux BORDS.** fl_chart en pose une à `min`
// et à `max` en plus de celles de l'intervalle : quand les bornes ne
// tombent pas sur un multiple, elles se superposent à leur voisine —
// « 20:4500 » à gauche, « 08:1509:00 » à droite, vus à l'écran le
// 2026-09-09. Deux heures imprimées l'une sur l'autre ne se lisent ni
// l'une ni l'autre.
//
// On ne perd rien : la ligne de plage sous le graphe donne les deux bornes
// exactes, avec leur jour — ce que l'axe ne pouvait pas dire de toute
// façon sur trois jours.
if (v <= meta.min || v >= meta.max) return const SizedBox.shrink();
final idx = v.round();
if (idx < 0 || idx >= _data.length) return const SizedBox.shrink();
return SideTitleWidget(