- 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>
234 lines
8.7 KiB
Dart
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,
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|