etm d5dc0c7ca5 Initial commit : Flutter app nymea energy monitor
- NymeaService : auth complète (Hello → Authenticate → SetNotificationStatus)
- Token top-level dans chaque requête JSON-RPC (fix critique GetThings)
- Persistance token via shared_preferences par hôte
- Dashboard : champs utilisateur/mot de passe dans le dialog de connexion
- ThingDetailScreen : renommer, réglages (settingsTypes) et supprimer
- NymeaThingClass : champ settingsTypes parsé depuis l'API
- NymeaThing : copyWith(name) + settingValue()
- Fix overflow _StateChip dans ThingsScreen

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-21 16:57:46 +01:00

169 lines
4.8 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import 'services/nymea_service.dart';
import 'screens/dashboard_screen.dart';
import 'screens/energy_screen.dart';
import 'screens/things_screen.dart';
import 'screens/ac_screen.dart';
import 'screens/favorites_screen.dart';
import 'theme/app_theme.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
SystemChrome.setSystemUIOverlayStyle(
const SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
statusBarIconBrightness: Brightness.dark,
),
);
runApp(
ChangeNotifierProvider(
create: (_) => NymeaService(),
child: const NymeaEnergyApp(),
),
);
}
class NymeaEnergyApp extends StatelessWidget {
const NymeaEnergyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Nymea Energy',
debugShowCheckedModeBanner: false,
theme: AppTheme.theme,
home: const MainShell(),
);
}
}
class MainShell extends StatefulWidget {
const MainShell({super.key});
@override
State<MainShell> createState() => _MainShellState();
}
class _MainShellState extends State<MainShell> {
int _currentIndex = 0;
final List<Widget> _screens = const [
DashboardScreen(),
EnergyScreen(),
ThingsScreen(),
ACScreen(),
FavoritesScreen(),
];
@override
Widget build(BuildContext context) {
return Scaffold(
body: IndexedStack(
index: _currentIndex,
children: _screens,
),
bottomNavigationBar: _BottomNav(
currentIndex: _currentIndex,
onTap: (i) => setState(() => _currentIndex = i),
),
);
}
}
class _BottomNav extends StatelessWidget {
final int currentIndex;
final ValueChanged<int> onTap;
const _BottomNav({required this.currentIndex, required this.onTap});
@override
Widget build(BuildContext context) {
final items = [
_NavItem(icon: Icons.home_rounded, label: 'Dashboard'),
_NavItem(icon: Icons.bar_chart_rounded, label: 'Énergie'),
_NavItem(icon: Icons.device_hub_rounded, label: 'Things'),
_NavItem(icon: Icons.ac_unit_rounded, label: 'A/C'),
_NavItem(icon: Icons.star_rounded, label: 'Favoris'),
];
return Container(
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha:0.08),
blurRadius: 12,
offset: const Offset(0, -2),
),
],
),
child: SafeArea(
child: SizedBox(
height: 62,
child: Row(
children: items.asMap().entries.map((e) {
final i = e.key;
final item = e.value;
final isSelected = currentIndex == i;
return Expanded(
child: GestureDetector(
onTap: () => onTap(i),
behavior: HitTestBehavior.opaque,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
AnimatedContainer(
duration: const Duration(milliseconds: 200),
padding: const EdgeInsets.symmetric(
horizontal: 12, vertical: 4),
decoration: BoxDecoration(
color: isSelected
? AppTheme.primaryGreen.withValues(alpha:0.12)
: Colors.transparent,
borderRadius: BorderRadius.circular(12),
),
child: Icon(
item.icon,
color: isSelected
? AppTheme.primaryGreen
: AppTheme.textLight,
size: 22,
),
),
const SizedBox(height: 2),
Text(
item.label,
style: TextStyle(
fontSize: 10,
color: isSelected
? AppTheme.primaryGreen
: AppTheme.textLight,
fontWeight: isSelected
? FontWeight.bold
: FontWeight.normal,
),
),
],
),
),
);
}).toList(),
),
),
),
);
}
}
class _NavItem {
final IconData icon;
final String label;
const _NavItem({required this.icon, required this.label});
}