- 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>
174 lines
6.5 KiB
Dart
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;
|
|
}
|
|
}
|