- 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>
173 lines
5.6 KiB
Dart
173 lines
5.6 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:provider/provider.dart';
|
|
import '../../providers/energy_setup_provider.dart';
|
|
import '../../providers/installer_mode_provider.dart';
|
|
import '../../services/nymea_service.dart';
|
|
import '../../theme/etm_theme.dart';
|
|
import '../../widgets/role_card.dart';
|
|
import 'role_config_flow.dart';
|
|
|
|
/// Écran "Rôles & appareils" — configuration des rôles EMS.
|
|
/// Accessible uniquement en mode installateur.
|
|
class EnergySetupScreen extends StatelessWidget {
|
|
const EnergySetupScreen({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final installer = context.watch<InstallerModeProvider>();
|
|
|
|
return Scaffold(
|
|
backgroundColor: const Color(0xFFF0F2F5),
|
|
appBar: AppBar(
|
|
title: const Text('Rôles & appareils'),
|
|
backgroundColor: Colors.white,
|
|
foregroundColor: const Color(0xFF1A1A2E),
|
|
elevation: 0,
|
|
actions: [
|
|
IconButton(
|
|
icon: const Icon(Icons.help_outline_rounded),
|
|
tooltip: 'Aide',
|
|
onPressed: () => _showHelp(context),
|
|
),
|
|
],
|
|
),
|
|
body: installer.isUnlocked
|
|
? _SetupBody()
|
|
: _LockedOverlay(),
|
|
);
|
|
}
|
|
|
|
void _showHelp(BuildContext context) {
|
|
showModalBottomSheet(
|
|
context: context,
|
|
shape: const RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
|
|
),
|
|
builder: (_) => Padding(
|
|
padding: const EdgeInsets.all(24),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: const [
|
|
Text('Rôles & appareils',
|
|
style: TextStyle(fontSize: 17, fontWeight: FontWeight.bold)),
|
|
SizedBox(height: 12),
|
|
Text(
|
|
'Assignez chaque appareil physique à son rôle dans '
|
|
'le système de gestion d\'énergie.\n\n'
|
|
'Le gestionnaire d\'énergie utilise ces assignations pour '
|
|
'optimiser la consommation et maximiser l\'autoconsommation.',
|
|
style: TextStyle(fontSize: 14),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _LockedOverlay extends StatelessWidget {
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Center(
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Icon(Icons.lock_rounded,
|
|
size: 48, color: Colors.grey.shade400),
|
|
const SizedBox(height: 16),
|
|
const Text(
|
|
'Mode installateur requis',
|
|
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
|
),
|
|
const SizedBox(height: 8),
|
|
const Text(
|
|
'Activez le mode installateur depuis le menu\npour accéder à cet écran.',
|
|
textAlign: TextAlign.center,
|
|
style: TextStyle(color: Color(0xFF6B7280)),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _SetupBody extends StatelessWidget {
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final setup = context.watch<EnergySetupProvider>();
|
|
final service = context.read<NymeaService>();
|
|
|
|
return ListView(
|
|
padding: const EdgeInsets.all(16),
|
|
children: [
|
|
// Bannière d'info
|
|
Container(
|
|
padding: const EdgeInsets.all(12),
|
|
margin: const EdgeInsets.only(bottom: 16),
|
|
decoration: BoxDecoration(
|
|
color: ETMTheme.accentColor.withValues(alpha: 0.1),
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: Border.all(
|
|
color: ETMTheme.accentColor.withValues(alpha: 0.3)),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
const Icon(Icons.info_outline_rounded,
|
|
color: ETMTheme.accentColor, size: 18),
|
|
const SizedBox(width: 10),
|
|
Expanded(
|
|
child: Text(
|
|
'${EmsRole.values.where((r) => setup.isConfigured(r)).length} / '
|
|
'${EmsRole.values.length} rôles configurés',
|
|
style: const TextStyle(
|
|
fontSize: 13,
|
|
color: ETMTheme.accentColor,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
...EmsRole.values.map((role) => RoleCard(
|
|
role: role,
|
|
assignment: setup.assignmentFor(role),
|
|
isReachable: true,
|
|
onConfigure: () => _openConfigFlow(context, role, null),
|
|
onToggle: setup.isConfigured(role)
|
|
? () => setup.setEnabled(
|
|
role,
|
|
!(setup.assignmentFor(role)?.enabled ?? false),
|
|
)
|
|
: null,
|
|
onEdit: setup.isConfigured(role)
|
|
? () => _openConfigFlow(
|
|
context,
|
|
role,
|
|
setup.assignmentFor(role),
|
|
)
|
|
: null,
|
|
onTest: setup.isConfigured(role)
|
|
? () => setup.testConnection(role, service)
|
|
: null,
|
|
)),
|
|
],
|
|
);
|
|
}
|
|
|
|
Future<void> _openConfigFlow(
|
|
BuildContext context, EmsRole role, RoleAssignment? existing) async {
|
|
await showModalBottomSheet(
|
|
context: context,
|
|
isScrollControlled: true,
|
|
useSafeArea: true,
|
|
shape: const RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
|
|
),
|
|
builder: (_) => RoleConfigFlow(role: role, existing: existing),
|
|
);
|
|
}
|
|
}
|