- 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>
169 lines
4.8 KiB
Dart
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});
|
|
}
|