- 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>
336 lines
10 KiB
Dart
336 lines
10 KiB
Dart
import 'package:flutter/material.dart';
|
|
import '../theme/app_theme.dart';
|
|
import '../main.dart' show DrawerMenuButton;
|
|
|
|
class ACScreen extends StatefulWidget {
|
|
const ACScreen({super.key});
|
|
|
|
@override
|
|
State<ACScreen> createState() => _ACScreenState();
|
|
}
|
|
|
|
class _ACScreenState extends State<ACScreen> {
|
|
final List<_ACZone> _zones = [
|
|
_ACZone(name: 'Salon', currentTemp: 19.5, targetTemp: 21, mode: ACMode.heat, isOn: true),
|
|
_ACZone(name: 'Chambre', currentTemp: 18.0, targetTemp: 19, mode: ACMode.cool, isOn: false),
|
|
_ACZone(name: 'Bureau', currentTemp: 20.0, targetTemp: 22, mode: ACMode.heat, isOn: true),
|
|
_ACZone(name: 'Cuisine', currentTemp: 21.5, targetTemp: 21, mode: ACMode.auto, isOn: false),
|
|
];
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
backgroundColor: AppTheme.backgroundGray,
|
|
appBar: AppBar(
|
|
backgroundColor: AppTheme.backgroundGray,
|
|
elevation: 0,
|
|
leading: const DrawerMenuButton(),
|
|
leadingWidth: 56,
|
|
title: const Text(
|
|
'Climatisation / Chauffage',
|
|
style: TextStyle(fontWeight: FontWeight.bold, color: AppTheme.textDark),
|
|
),
|
|
actions: [
|
|
IconButton(
|
|
icon: const Icon(Icons.schedule, color: AppTheme.textDark),
|
|
onPressed: () {},
|
|
),
|
|
],
|
|
),
|
|
body: ListView.separated(
|
|
padding: const EdgeInsets.all(16),
|
|
itemCount: _zones.length,
|
|
separatorBuilder: (_, _) => const SizedBox(height: 12),
|
|
itemBuilder: (context, index) {
|
|
return _ZoneCard(
|
|
zone: _zones[index],
|
|
onChanged: (updated) {
|
|
setState(() => _zones[index] = updated);
|
|
},
|
|
);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
enum ACMode { heat, cool, auto, fan }
|
|
|
|
class _ACZone {
|
|
final String name;
|
|
final double currentTemp;
|
|
final double targetTemp;
|
|
final ACMode mode;
|
|
final bool isOn;
|
|
|
|
const _ACZone({
|
|
required this.name,
|
|
required this.currentTemp,
|
|
required this.targetTemp,
|
|
required this.mode,
|
|
required this.isOn,
|
|
});
|
|
|
|
_ACZone copyWith({
|
|
double? targetTemp,
|
|
ACMode? mode,
|
|
bool? isOn,
|
|
}) =>
|
|
_ACZone(
|
|
name: name,
|
|
currentTemp: currentTemp,
|
|
targetTemp: targetTemp ?? this.targetTemp,
|
|
mode: mode ?? this.mode,
|
|
isOn: isOn ?? this.isOn,
|
|
);
|
|
}
|
|
|
|
class _ZoneCard extends StatelessWidget {
|
|
final _ACZone zone;
|
|
final ValueChanged<_ACZone> onChanged;
|
|
|
|
const _ZoneCard({required this.zone, required this.onChanged});
|
|
|
|
Color get _modeColor {
|
|
switch (zone.mode) {
|
|
case ACMode.heat:
|
|
return Colors.orange;
|
|
case ACMode.cool:
|
|
return Colors.cyan;
|
|
case ACMode.auto:
|
|
return AppTheme.primaryGreen;
|
|
case ACMode.fan:
|
|
return Colors.blueGrey;
|
|
}
|
|
}
|
|
|
|
IconData get _modeIcon {
|
|
switch (zone.mode) {
|
|
case ACMode.heat:
|
|
return Icons.whatshot_rounded;
|
|
case ACMode.cool:
|
|
return Icons.ac_unit_rounded;
|
|
case ACMode.auto:
|
|
return Icons.autorenew_rounded;
|
|
case ACMode.fan:
|
|
return Icons.air_rounded;
|
|
}
|
|
}
|
|
|
|
String get _modeLabel {
|
|
switch (zone.mode) {
|
|
case ACMode.heat:
|
|
return 'Chauffage';
|
|
case ACMode.cool:
|
|
return 'Clim';
|
|
case ACMode.auto:
|
|
return 'Auto';
|
|
case ACMode.fan:
|
|
return 'Ventilation';
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Card(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
children: [
|
|
// Header row
|
|
Row(
|
|
children: [
|
|
Container(
|
|
width: 48,
|
|
height: 48,
|
|
decoration: BoxDecoration(
|
|
color: zone.isOn
|
|
? _modeColor.withValues(alpha:0.15)
|
|
: Colors.grey.shade100,
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: Icon(
|
|
_modeIcon,
|
|
color: zone.isOn ? _modeColor : Colors.grey,
|
|
size: 26,
|
|
),
|
|
),
|
|
const SizedBox(width: 14),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(zone.name,
|
|
style: const TextStyle(
|
|
fontWeight: FontWeight.bold,
|
|
color: AppTheme.textDark,
|
|
fontSize: 16)),
|
|
Text(
|
|
zone.isOn ? _modeLabel : 'Éteint',
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
color:
|
|
zone.isOn ? _modeColor : AppTheme.textLight),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
Switch(
|
|
value: zone.isOn,
|
|
onChanged: (v) => onChanged(zone.copyWith(isOn: v)),
|
|
activeThumbColor: _modeColor,
|
|
),
|
|
],
|
|
),
|
|
|
|
if (zone.isOn) ...[
|
|
const Divider(height: 20),
|
|
|
|
// Temperature
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Column(
|
|
children: [
|
|
const Text('Actuelle',
|
|
style: TextStyle(
|
|
fontSize: 12, color: AppTheme.textLight)),
|
|
Text(
|
|
'${zone.currentTemp.toStringAsFixed(1)}°C',
|
|
style: const TextStyle(
|
|
fontSize: 28,
|
|
fontWeight: FontWeight.bold,
|
|
color: AppTheme.textDark,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
Icon(Icons.arrow_forward_rounded,
|
|
color: Colors.grey.shade400),
|
|
Column(
|
|
children: [
|
|
const Text('Cible',
|
|
style: TextStyle(
|
|
fontSize: 12, color: AppTheme.textLight)),
|
|
Row(
|
|
children: [
|
|
IconButton(
|
|
icon: Icon(Icons.remove_circle_outline,
|
|
color: _modeColor),
|
|
onPressed: () => onChanged(zone.copyWith(
|
|
targetTemp: zone.targetTemp - 0.5)),
|
|
),
|
|
Text(
|
|
'${zone.targetTemp.toStringAsFixed(1)}°C',
|
|
style: TextStyle(
|
|
fontSize: 24,
|
|
fontWeight: FontWeight.bold,
|
|
color: _modeColor,
|
|
),
|
|
),
|
|
IconButton(
|
|
icon: Icon(Icons.add_circle_outline,
|
|
color: _modeColor),
|
|
onPressed: () => onChanged(zone.copyWith(
|
|
targetTemp: zone.targetTemp + 0.5)),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
|
|
// Mode selector
|
|
const SizedBox(height: 8),
|
|
Row(
|
|
children: ACMode.values.map((m) {
|
|
final selected = zone.mode == m;
|
|
final color = _modeColorFor(m);
|
|
final icon = _modeIconFor(m);
|
|
final label = _modeLabelFor(m);
|
|
return Expanded(
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 2),
|
|
child: GestureDetector(
|
|
onTap: () => onChanged(zone.copyWith(mode: m)),
|
|
child: AnimatedContainer(
|
|
duration: const Duration(milliseconds: 200),
|
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
|
decoration: BoxDecoration(
|
|
color: selected
|
|
? color.withValues(alpha:0.15)
|
|
: Colors.grey.shade100,
|
|
borderRadius: BorderRadius.circular(10),
|
|
border: selected
|
|
? Border.all(color: color, width: 1.5)
|
|
: null,
|
|
),
|
|
child: Column(
|
|
children: [
|
|
Icon(icon,
|
|
size: 18,
|
|
color: selected ? color : Colors.grey),
|
|
const SizedBox(height: 2),
|
|
Text(label,
|
|
style: TextStyle(
|
|
fontSize: 10,
|
|
color:
|
|
selected ? color : AppTheme.textLight,
|
|
fontWeight: selected
|
|
? FontWeight.bold
|
|
: FontWeight.normal)),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}).toList(),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Color _modeColorFor(ACMode m) {
|
|
switch (m) {
|
|
case ACMode.heat:
|
|
return Colors.orange;
|
|
case ACMode.cool:
|
|
return Colors.cyan;
|
|
case ACMode.auto:
|
|
return AppTheme.primaryGreen;
|
|
case ACMode.fan:
|
|
return Colors.blueGrey;
|
|
}
|
|
}
|
|
|
|
IconData _modeIconFor(ACMode m) {
|
|
switch (m) {
|
|
case ACMode.heat:
|
|
return Icons.whatshot_rounded;
|
|
case ACMode.cool:
|
|
return Icons.ac_unit_rounded;
|
|
case ACMode.auto:
|
|
return Icons.autorenew_rounded;
|
|
case ACMode.fan:
|
|
return Icons.air_rounded;
|
|
}
|
|
}
|
|
|
|
String _modeLabelFor(ACMode m) {
|
|
switch (m) {
|
|
case ACMode.heat:
|
|
return 'Chauf.';
|
|
case ACMode.cool:
|
|
return 'Clim';
|
|
case ACMode.auto:
|
|
return 'Auto';
|
|
case ACMode.fan:
|
|
return 'Vent.';
|
|
}
|
|
}
|
|
} |