import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../models/nymea_models.dart'; import '../models/thing_category.dart'; import '../services/nymea_service.dart'; import '../theme/app_theme.dart'; import 'thing_detail_screen.dart'; // ───────────────────────────────────────────────────────────────────────────── // CategoryOverviewScreen — NIVEAU 2 // // Liste des things d'une catégorie. // Reproduit smart-meters_overview.png (nymea-app) : // // AppBar : ‹ [NOM CATÉGORIE] (centré) // // Pour chaque thing : // ┌─────────────────────────────────────┐ // │ Nom du thing (bold) ● │ ← fond coloré léger // ├─────────────────────────────────────┤ // │ [icône] Classe appareil valeur ›│ ← fond blanc // └─────────────────────────────────────┘ // ───────────────────────────────────────────────────────────────────────────── class CategoryOverviewScreen extends StatelessWidget { final ThingCategoryInfo info; final List things; final List thingClasses; const CategoryOverviewScreen({ super.key, required this.info, required this.things, required this.thingClasses, }); // ── Helpers ──────────────────────────────────────────────────────────────── NymeaThingClass? _cls(NymeaThing t) { try { return thingClasses.firstWhere((c) => c.id == t.thingClassId); } catch (_) { return null; } } bool _isOnline(NymeaThing t, NymeaThingClass? cls) { final ct = cls?.stateTypes.where((s) => s.name.toLowerCase() == 'connected'); if (ct != null && ct.isNotEmpty) { final v = t.stateValue(ct.first.id); return v == true || v == 'true'; } return t.isSetupComplete; } String? _primaryValue(NymeaThing t, NymeaThingClass? cls) { final p = cls?.primaryStateType; if (p == null) return null; return p.formatValue(t.stateValue(p.id)); } // ── Build ────────────────────────────────────────────────────────────────── @override Widget build(BuildContext context) { // Écoute les mises à jour live via le service final service = context.watch(); final liveThings = things.map((t) { try { return service.things.firstWhere((lt) => lt.id == t.id); } catch (_) { return t; } }).toList(); return Scaffold( backgroundColor: AppTheme.backgroundGray, appBar: AppBar( backgroundColor: AppTheme.backgroundGray, elevation: 0, centerTitle: true, leading: IconButton( icon: const Icon(Icons.chevron_left, color: AppTheme.textDark, size: 30), onPressed: () => Navigator.pop(context), ), title: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(info.icon, color: info.color, size: 20), const SizedBox(width: 8), Text( info.label, style: const TextStyle( color: AppTheme.textDark, fontWeight: FontWeight.bold, fontSize: 18), ), ], ), ), body: ListView.separated( padding: const EdgeInsets.fromLTRB(16, 12, 16, 60), itemCount: liveThings.length, separatorBuilder: (context, i) => const SizedBox(height: 10), itemBuilder: (context, i) { final t = liveThings[i]; final cls = _cls(t); return _ThingListCard( thing: t, thingClass: cls, info: info, isOnline: _isOnline(t, cls), primaryValue: _primaryValue(t, cls), onTap: () => Navigator.push( context, MaterialPageRoute(builder: (_) => ThingDetailScreen(thing: t)), ), ); }, ), ); } } // ───────────────────────────────────────────────────────────────────────────── // _ThingListCard — Card deux lignes style nymea // ───────────────────────────────────────────────────────────────────────────── class _ThingListCard extends StatelessWidget { final NymeaThing thing; final NymeaThingClass? thingClass; final ThingCategoryInfo info; final bool isOnline; final String? primaryValue; final VoidCallback onTap; const _ThingListCard({ required this.thing, required this.thingClass, required this.info, required this.isOnline, required this.primaryValue, required this.onTap, }); @override Widget build(BuildContext context) { return ClipRRect( borderRadius: BorderRadius.circular(AppTheme.cornerRadius), child: Material( color: Colors.transparent, child: InkWell( onTap: onTap, child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(AppTheme.cornerRadius), border: Border.all( color: info.color.withValues(alpha: 0.28), width: 1), ), child: Column( children: [ // ── Ligne 1 : nom du thing ───────────────────────────────── Container( decoration: BoxDecoration( color: info.color.withValues(alpha: 0.10), borderRadius: const BorderRadius.vertical( top: Radius.circular(9)), ), padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 11), child: Row( children: [ Expanded( child: Text( thing.name, style: const TextStyle( fontSize: 15, fontWeight: FontWeight.bold, color: AppTheme.textDark), maxLines: 1, overflow: TextOverflow.ellipsis, ), ), // Pastille statut Container( width: 9, height: 9, decoration: BoxDecoration( color: isOnline ? info.color : Colors.grey[400], shape: BoxShape.circle, boxShadow: isOnline ? [ BoxShadow( color: info.color .withValues(alpha: 0.45), blurRadius: 4, ) ] : null, ), ), ], ), ), // ── Ligne 2 : icône + classe + valeur primaire ───────────── Container( color: AppTheme.cardWhite, padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 12), child: Row( children: [ // Icône Container( width: 32, height: 32, decoration: BoxDecoration( color: info.color .withValues(alpha: isOnline ? 0.12 : 0.06), borderRadius: BorderRadius.circular(8), ), child: Icon( info.icon, size: 18, color: isOnline ? info.color : info.color.withValues(alpha: 0.4), ), ), const SizedBox(width: 12), // Classe appareil Expanded( child: Text( thingClass?.displayName ?? 'Appareil', style: const TextStyle( fontSize: 13, color: AppTheme.textLight), maxLines: 1, overflow: TextOverflow.ellipsis, ), ), // Valeur primaire Text( primaryValue ?? '—', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: isOnline ? info.color : AppTheme.textLight, ), ), const SizedBox(width: 4), // Flèche const Icon(Icons.chevron_right, size: 18, color: AppTheme.textLight), ], ), ), ], ), ), ), ), ); } }