- 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>
180 lines
6.5 KiB
Dart
180 lines
6.5 KiB
Dart
import 'package:flutter/material.dart';
|
|
import '../providers/energy_setup_provider.dart';
|
|
import '../theme/app_theme.dart';
|
|
import '../theme/etm_theme.dart';
|
|
|
|
/// Carte de statut pour un rôle EMS (EV Charger, Chauffe-eau, Batterie, etc.).
|
|
class RoleCard extends StatelessWidget {
|
|
final EmsRole role;
|
|
final RoleAssignment? assignment;
|
|
final bool isReachable;
|
|
final VoidCallback onConfigure;
|
|
final VoidCallback? onToggle;
|
|
final VoidCallback? onEdit;
|
|
final VoidCallback? onTest;
|
|
|
|
const RoleCard({
|
|
super.key,
|
|
required this.role,
|
|
required this.assignment,
|
|
required this.onConfigure,
|
|
this.isReachable = true,
|
|
this.onToggle,
|
|
this.onEdit,
|
|
this.onTest,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final isConfigured = assignment != null;
|
|
final isEnabled = assignment?.enabled ?? false;
|
|
|
|
return Card(
|
|
margin: const EdgeInsets.only(bottom: 12),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(14),
|
|
side: BorderSide(
|
|
color: isEnabled
|
|
? AppTheme.primaryGreen.withValues(alpha: 0.3)
|
|
: Colors.grey.withValues(alpha: 0.15),
|
|
),
|
|
),
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(14),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// ── En-tête : icône + nom + switch ──────────────────────────────
|
|
Row(
|
|
children: [
|
|
Text(role.icon,
|
|
style: const TextStyle(fontSize: 22)),
|
|
const SizedBox(width: 10),
|
|
Expanded(
|
|
child: Text(
|
|
role.label,
|
|
style: const TextStyle(
|
|
fontWeight: FontWeight.bold,
|
|
fontSize: 15,
|
|
),
|
|
),
|
|
),
|
|
if (isConfigured)
|
|
Switch.adaptive(
|
|
value: isEnabled,
|
|
activeColor: AppTheme.primaryGreen,
|
|
onChanged: onToggle != null
|
|
? (_) => onToggle!()
|
|
: null,
|
|
),
|
|
],
|
|
),
|
|
|
|
const SizedBox(height: 8),
|
|
|
|
// ── Corps : état de la configuration ─────────────────────────────
|
|
if (!isConfigured) ...[
|
|
const Text(
|
|
'Aucun appareil configuré',
|
|
style: TextStyle(
|
|
color: AppTheme.textLight,
|
|
fontSize: 13,
|
|
),
|
|
),
|
|
const SizedBox(height: 10),
|
|
SizedBox(
|
|
width: double.infinity,
|
|
child: OutlinedButton.icon(
|
|
icon: const Icon(Icons.add_rounded, size: 16),
|
|
label: const Text('Configurer'),
|
|
style: OutlinedButton.styleFrom(
|
|
foregroundColor: ETMTheme.accentColor,
|
|
side: BorderSide(
|
|
color: ETMTheme.accentColor.withValues(alpha: 0.5)),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(10)),
|
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
|
),
|
|
onPressed: onConfigure,
|
|
),
|
|
),
|
|
] else ...[
|
|
Row(
|
|
children: [
|
|
Icon(
|
|
isReachable
|
|
? Icons.check_circle_rounded
|
|
: Icons.error_rounded,
|
|
size: 14,
|
|
color: isReachable
|
|
? AppTheme.primaryGreen
|
|
: AppTheme.boostRed,
|
|
),
|
|
const SizedBox(width: 6),
|
|
Expanded(
|
|
child: Text(
|
|
assignment!.thing.name,
|
|
style: const TextStyle(fontSize: 13),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
if (assignment!.params.containsKey('powerW')) ...[
|
|
const SizedBox(height: 4),
|
|
Text(
|
|
'${assignment!.params['powerW']} W · Priorité: ${assignment!.params['priority'] ?? 'Normal'}',
|
|
style: const TextStyle(
|
|
fontSize: 12,
|
|
color: AppTheme.textLight,
|
|
),
|
|
),
|
|
],
|
|
const SizedBox(height: 10),
|
|
Row(
|
|
children: [
|
|
if (onEdit != null)
|
|
Expanded(
|
|
child: OutlinedButton(
|
|
style: OutlinedButton.styleFrom(
|
|
foregroundColor: ETMTheme.accentColor,
|
|
side: BorderSide(
|
|
color:
|
|
ETMTheme.accentColor.withValues(alpha: 0.4)),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(10)),
|
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
|
),
|
|
onPressed: onEdit,
|
|
child: const Text('Modifier',
|
|
style: TextStyle(fontSize: 13)),
|
|
),
|
|
),
|
|
if (onEdit != null && onTest != null)
|
|
const SizedBox(width: 8),
|
|
if (onTest != null)
|
|
Expanded(
|
|
child: OutlinedButton(
|
|
style: OutlinedButton.styleFrom(
|
|
foregroundColor: AppTheme.primaryGreen,
|
|
side: BorderSide(
|
|
color: AppTheme.primaryGreen
|
|
.withValues(alpha: 0.4)),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(10)),
|
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
|
),
|
|
onPressed: onTest,
|
|
child: const Text('Tester',
|
|
style: TextStyle(fontSize: 13)),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|