etm-powersync-app/lib/providers/app_settings_provider.dart
pakutz79 c19c9d1a98 feat: navigation drawer, EMS setup, scheduler, tarifs, paramètres app
- Drawer custom (overlay Stack) avec mode installateur PIN SHA-256
- GoRouter + ShellRoute : navigation préservée entre onglets
- 6 providers : NavigationProvider, InstallerModeProvider, AppSettingsProvider,
  EnergySetupProvider, SchedulerProvider, TariffProvider
- Écrans Energy Manager : RoleConfigFlow (3 étapes), Scheduler, Tarifs, Timeline
- Écrans Paramètres : Apparence, Écrans actifs, AppSettingsScreen
- DrawerMenuButton présent dans les 5 AppBars principaux
- Simulation : _thingClasses générées avec interfaces EMS pour filtrage des rôles
- Compteur solaire : ajout smartmeter aux interfaces compatibles
- Thème ETM (etm_theme.dart), ProLockBadge, widgets PowerBar/RoleCard/TimelineSlotCard
- Dépendances : go_router, shared_preferences, crypto, url_launcher

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 14:52:32 +01:00

174 lines
6.5 KiB
Dart

import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
/// Décrit un onglet du menu principal (bottom nav).
class AppScreen {
final String id;
final String label;
final IconData icon;
bool visible;
AppScreen({
required this.id,
required this.label,
required this.icon,
this.visible = true,
});
AppScreen copyWith({bool? visible}) => AppScreen(
id: id, label: label, icon: icon,
visible: visible ?? this.visible,
);
Map<String, dynamic> toJson() => {'id': id, 'visible': visible};
}
/// Gère les préférences d'apparence et les écrans visibles.
class AppSettingsProvider extends ChangeNotifier {
static const _themeKey = 'app_theme_mode';
static const _accentKey = 'app_accent_color';
static const _textScaleKey = 'app_text_scale';
static const _screensKey = 'app_screens_config';
static const _densityKey = 'app_ui_density';
ThemeMode _themeMode = ThemeMode.system;
int _accentIndex = 0; // 0=ETM blue, 1=vert, 2=orange, 3=violet
double _textScale = 1.0;
VisualDensity _density = VisualDensity.standard;
// Ordre et visibilité des écrans principaux
List<AppScreen> _screens = [
AppScreen(id: 'dashboard', label: 'Dashboard', icon: Icons.home_rounded),
AppScreen(id: 'energy', label: 'Énergie', icon: Icons.bar_chart_rounded),
AppScreen(id: 'things', label: 'Things', icon: Icons.device_hub_rounded),
AppScreen(id: 'favorites', label: 'Favoris', icon: Icons.star_rounded),
AppScreen(id: 'groups', label: 'Groupes', icon: Icons.group_work_rounded, visible: false),
AppScreen(id: 'scenes', label: 'Scènes', icon: Icons.auto_awesome_rounded, visible: false),
AppScreen(id: 'media', label: 'Médias', icon: Icons.music_note_rounded, visible: false),
AppScreen(id: 'garages', label: 'Garages', icon: Icons.garage_rounded, visible: false),
AppScreen(id: 'ac', label: 'AC / Climatisation', icon: Icons.ac_unit_rounded),
];
ThemeMode get themeMode => _themeMode;
int get accentIndex => _accentIndex;
double get textScale => _textScale;
VisualDensity get density => _density;
List<AppScreen> get screens => _screens;
List<AppScreen> get visibleScreens =>
_screens.where((s) => s.visible).toList();
static const List<Color> accentColors = [
Color(0xFF2E75B6), // ETM blue
Color(0xFF4CAF50), // vert
Color(0xFFFF7043), // orange
Color(0xFF7B1FA2), // violet
];
Color get accentColor => accentColors[_accentIndex.clamp(0, 3)];
// ── Chargement depuis SharedPreferences ─────────────────────────────────────
Future<void> load() async {
final prefs = await SharedPreferences.getInstance();
final themeVal = prefs.getInt(_themeKey) ?? 0;
_themeMode = ThemeMode.values[themeVal.clamp(0, 2)];
_accentIndex = (prefs.getInt(_accentKey) ?? 0).clamp(0, 3);
_textScale = (prefs.getDouble(_textScaleKey) ?? 1.0).clamp(0.8, 1.4);
final densityVal = prefs.getInt(_densityKey) ?? 1;
_density = _densityFromIndex(densityVal);
// Restaure l'ordre et la visibilité des écrans
final raw = prefs.getString(_screensKey);
if (raw != null) {
try {
final List<dynamic> saved = jsonDecode(raw) as List;
final byId = {for (final s in _screens) s.id: s};
final restored = <AppScreen>[];
for (final item in saved) {
final id = item['id'] as String?;
final visible = item['visible'] as bool? ?? true;
if (id != null && byId.containsKey(id)) {
restored.add(byId[id]!.copyWith(visible: visible));
byId.remove(id);
}
}
// Ajoute les écrans nouveaux non présents dans la config sauvegardée
restored.addAll(byId.values);
_screens = restored;
} catch (_) {}
}
notifyListeners();
}
// ── Sauvegarde ───────────────────────────────────────────────────────────────
Future<void> _save() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setInt(_themeKey, _themeMode.index);
await prefs.setInt(_accentKey, _accentIndex);
await prefs.setDouble(_textScaleKey, _textScale);
await prefs.setInt(_densityKey, _densityIndex(_density));
await prefs.setString(
_screensKey, jsonEncode(_screens.map((s) => s.toJson()).toList()));
}
// ── Setters ──────────────────────────────────────────────────────────────────
void setThemeMode(ThemeMode mode) {
_themeMode = mode; _save(); notifyListeners();
}
void setAccentIndex(int idx) {
_accentIndex = idx.clamp(0, 3); _save(); notifyListeners();
}
void setTextScale(double v) {
_textScale = v.clamp(0.8, 1.4); _save(); notifyListeners();
}
void setDensity(VisualDensity d) {
_density = d; _save(); notifyListeners();
}
void setScreenVisible(String id, bool visible) {
final idx = _screens.indexWhere((s) => s.id == id);
if (idx >= 0) {
_screens[idx] = _screens[idx].copyWith(visible: visible);
_save();
notifyListeners();
}
}
void reorderScreens(int oldIdx, int newIdx) {
final item = _screens.removeAt(oldIdx);
_screens.insert(newIdx, item);
_save();
notifyListeners();
}
void resetAppearance() {
_themeMode = ThemeMode.system;
_accentIndex = 0;
_textScale = 1.0;
_density = VisualDensity.standard;
_save();
notifyListeners();
}
// ── Helpers ──────────────────────────────────────────────────────────────────
VisualDensity _densityFromIndex(int i) => switch (i) {
0 => VisualDensity.compact,
2 => const VisualDensity(horizontal: 2, vertical: 2),
_ => VisualDensity.standard,
};
int _densityIndex(VisualDensity d) {
if (d == VisualDensity.compact) return 0;
if (d.horizontal > 0) return 2;
return 1;
}
}