etm-powersync-app/lib/screens/settings/appearance_screen.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

234 lines
8.7 KiB
Dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../providers/app_settings_provider.dart';
import '../../theme/app_theme.dart';
import '../../theme/etm_theme.dart';
/// Écran "Apparence" — thème, couleur d'accent, taille de texte, densité.
class AppearanceScreen extends StatelessWidget {
const AppearanceScreen({super.key});
@override
Widget build(BuildContext context) {
final settings = context.watch<AppSettingsProvider>();
return Scaffold(
backgroundColor: const Color(0xFFF0F2F5),
appBar: AppBar(
title: const Text('Apparence'),
backgroundColor: Colors.white,
foregroundColor: const Color(0xFF1A1A2E),
elevation: 0,
),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
// ── Thème ──────────────────────────────────────────────────────
_SettingsCard(
title: 'Thème',
child: Column(
children: ThemeMode.values.map((m) {
final labels = {
ThemeMode.light: 'Clair',
ThemeMode.dark: 'Sombre',
ThemeMode.system: 'Système',
};
return RadioListTile<ThemeMode>(
dense: true,
contentPadding: EdgeInsets.zero,
title: Text(labels[m]!,
style: const TextStyle(fontSize: 13)),
value: m,
groupValue: settings.themeMode,
activeColor: ETMTheme.accentColor,
onChanged: (v) {
if (v != null)
context
.read<AppSettingsProvider>()
.setThemeMode(v);
},
);
}).toList(),
),
),
const SizedBox(height: 12),
// ── Couleur d'accent ───────────────────────────────────────────
_SettingsCard(
title: 'Couleur d\'accent',
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
('ETM Blue', AppSettingsProvider.accentColors[0]),
('Vert', AppSettingsProvider.accentColors[1]),
('Orange', AppSettingsProvider.accentColors[2]),
('Violet', AppSettingsProvider.accentColors[3]),
].asMap().entries.map((entry) {
final idx = entry.key;
final label = entry.value.$1;
final color = entry.value.$2;
final selected = settings.accentIndex == idx;
return GestureDetector(
onTap: () =>
context.read<AppSettingsProvider>().setAccentIndex(idx),
child: Column(
children: [
Container(
width: 44, height: 44,
decoration: BoxDecoration(
color: color,
shape: BoxShape.circle,
border: selected
? Border.all(
color: Colors.white, width: 3)
: null,
boxShadow: selected
? [
BoxShadow(
color:
color.withValues(alpha: 0.4),
blurRadius: 8,
spreadRadius: 2,
)
]
: null,
),
child: selected
? const Icon(Icons.check_rounded,
color: Colors.white, size: 20)
: null,
),
const SizedBox(height: 6),
Text(label,
style: const TextStyle(fontSize: 11)),
],
),
);
}).toList(),
),
),
const SizedBox(height: 12),
// ── Taille du texte ────────────────────────────────────────────
_SettingsCard(
title: 'Taille du texte',
child: Column(
children: [
Slider(
value: settings.textScale,
min: 0.8, max: 1.4,
divisions: 6,
activeColor: ETMTheme.accentColor,
label:
'${(settings.textScale * 100).toStringAsFixed(0)}%',
onChanged: (v) => context
.read<AppSettingsProvider>()
.setTextScale(v),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: const [
Text('Petit', style: TextStyle(fontSize: 11)),
Text('Normal', style: TextStyle(fontSize: 11)),
Text('Grand', style: TextStyle(fontSize: 11)),
],
),
],
),
),
const SizedBox(height: 12),
// ── Densité de l'interface ─────────────────────────────────────
_SettingsCard(
title: 'Densité de l\'interface',
child: Column(
children: [
(0, 'Compacte', VisualDensity.compact),
(1, 'Normale', VisualDensity.standard),
(2, 'Confortable',
const VisualDensity(horizontal: 2, vertical: 2)),
].map((entry) {
final idx = entry.$1;
final label = entry.$2;
final density = entry.$3;
return RadioListTile<int>(
dense: true,
contentPadding: EdgeInsets.zero,
title: Text(label,
style: const TextStyle(fontSize: 13)),
value: idx,
groupValue: settings.density == VisualDensity.compact
? 0
: settings.density.horizontal > 0
? 2
: 1,
activeColor: ETMTheme.accentColor,
onChanged: (v) {
if (v != null) {
context
.read<AppSettingsProvider>()
.setDensity(density);
}
},
);
}).toList(),
),
),
const SizedBox(height: 16),
// ── Réinitialiser ──────────────────────────────────────────────
SizedBox(
width: double.infinity,
child: OutlinedButton.icon(
icon: const Icon(Icons.restore_rounded, size: 18),
label: const Text('Réinitialiser les paramètres d\'apparence'),
style: OutlinedButton.styleFrom(
foregroundColor: AppTheme.boostRed,
side: BorderSide(
color: AppTheme.boostRed.withValues(alpha: 0.4)),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
padding: const EdgeInsets.symmetric(vertical: 14),
),
onPressed: () => context
.read<AppSettingsProvider>()
.resetAppearance(),
),
),
],
),
);
}
}
class _SettingsCard extends StatelessWidget {
final String title;
final Widget child;
const _SettingsCard({required this.title, required this.child});
@override
Widget build(BuildContext context) {
return Card(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title,
style: const TextStyle(
fontWeight: FontWeight.bold, fontSize: 14)),
const SizedBox(height: 12),
child,
],
),
),
);
}
}