feat: favoris — persistance SharedPreferences + restyle EtmTokens

Persistance (FavoriteWidget)
- FavoriteWidget.fromJson / toJson ajoutés dans nymea_models.dart
- NymeaService : constructeur appelle _loadFavorites() au démarrage
- _saveFavorites() appelé après addFavorite / removeFavorite / reorderFavorites
- Clé SharedPreferences : 'etm_favorites_v1'
- Résistance aux données corrompues (try/catch sur fromJson)

Restyle (favorites_screen.dart)
- AppTheme → EtmTokens sur toutes les couleurs et typographies
- Valeurs en IBM Plex Mono (size 36 pour les grandes métriques)
- Cartes avec EtmTokens.cardShadow + border radius 22
- _EmptyState : bouton vert + étoile + message
- _AddSheet : DraggableScrollableSheet restyled, sans emoji, items visuellement
  distingués (ajouté vs disponible)
- ReorderableDelayedDragStartListener pour le drag discret
- Simulation auto-démarrée si non connecté (comme Dashboard/Things)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Patrick Schurig ETM-Schurig 2026-05-29 22:07:14 +02:00
parent d0a475a5d9
commit 80500e21e6
3 changed files with 508 additions and 387 deletions

View File

@ -281,6 +281,26 @@ class FavoriteWidget {
this.stateTypeId,
this.extra = const {},
});
factory FavoriteWidget.fromJson(Map<String, dynamic> j) => FavoriteWidget(
id: j['id'] as String,
type: FavoriteType.values.firstWhere(
(t) => t.name == j['type'],
orElse: () => FavoriteType.pvPower),
title: j['title'] as String,
thingId: j['thingId'] as String?,
stateTypeId: j['stateTypeId'] as String?,
extra: Map<String, dynamic>.from(j['extra'] as Map? ?? {}),
);
Map<String, dynamic> toJson() => {
'id': id,
'type': type.name,
'title': title,
if (thingId != null) 'thingId': thingId,
if (stateTypeId != null) 'stateTypeId': stateTypeId,
if (extra.isNotEmpty) 'extra': extra,
};
}
// Historique d'un état ───────────────────────────────────────────────────────

File diff suppressed because it is too large Load Diff

View File

@ -75,6 +75,36 @@ class NymeaService extends ChangeNotifier {
List<NymeaThingClass> _thingClasses = [];
List<FavoriteWidget> _favoriteWidgets = [];
static const _favKey = 'etm_favorites_v1';
NymeaService() {
_loadFavorites();
}
Future<void> _loadFavorites() async {
final prefs = await SharedPreferences.getInstance();
final raw = prefs.getString(_favKey);
if (raw != null) {
try {
final list = jsonDecode(raw) as List;
_favoriteWidgets = list
.map((e) => FavoriteWidget.fromJson(e as Map<String, dynamic>))
.toList();
notifyListeners();
} catch (_) {
// Données corrompues on repart de zéro
}
}
}
Future<void> _saveFavorites() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString(
_favKey,
jsonEncode(_favoriteWidgets.map((f) => f.toJson()).toList()),
);
}
// Getters
bool get connected => _connected;
bool get isConnected => _connected; // alias pour les screens
@ -1185,12 +1215,14 @@ class NymeaService extends ChangeNotifier {
if (!_favoriteWidgets.any((f) => f.id == widget.id)) {
_favoriteWidgets.add(widget);
notifyListeners();
_saveFavorites();
}
}
void removeFavorite(String id) {
_favoriteWidgets.removeWhere((f) => f.id == id);
notifyListeners();
_saveFavorites();
}
void reorderFavorites(int oldIndex, int newIndex) {
@ -1198,6 +1230,7 @@ class NymeaService extends ChangeNotifier {
final item = _favoriteWidgets.removeAt(oldIndex);
_favoriteWidgets.insert(newIndex, item);
notifyListeners();
_saveFavorites();
}
//