import 'dart:convert'; import 'package:flutter/material.dart'; import '../application/application.dart'; import '../domain/domain.dart'; import 'theme.dart'; final class ShareInboxScreen extends StatefulWidget { const ShareInboxScreen({required this.shareUseCases, super.key}); final ShareUseCases shareUseCases; @override State createState() => _ShareInboxScreenState(); } final class _ShareInboxScreenState extends State { late Future> _items; @override void initState() { super.initState(); _items = widget.shareUseCases.refreshInbox(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Partages reçus')), body: FutureBuilder>( future: _items, builder: (context, snapshot) { if (snapshot.connectionState != ConnectionState.done) { return const Center(child: CircularProgressIndicator()); } final items = snapshot.data ?? const []; if (items.isEmpty) { return const _EmptyInbox(); } return RefreshIndicator( onRefresh: _reload, child: ListView.separated( padding: const EdgeInsets.all(16), itemBuilder: (context, index) { final item = items[index]; return _ShareInboxTile( item: item, onOpen: item.resourceType == ShareResourceType.pack ? () => _openPack(item) : null, onAccept: item.status == ShareInboxStatus.pending ? () => _accept(item) : null, onDecline: item.status == ShareInboxStatus.pending ? () => _decline(item) : null, ); }, separatorBuilder: (context, index) => const SizedBox(height: 12), itemCount: items.length, ), ); }, ), ); } Future _reload() async { setState(() { _items = widget.shareUseCases.refreshInbox(); }); await _items; } Future _accept(ShareInboxItem item) async { await widget.shareUseCases.acceptShare(item.shareId); if (!mounted) return; await _reload(); if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(_acceptedMessage(item.resourceType))), ); } Future _openPack(ShareInboxItem item) async { await Navigator.of(context).push( MaterialPageRoute( builder: (context) => _SharePackDetailScreen( item: item, onImport: item.status == ShareInboxStatus.pending ? () => _accept(item) : null, onDecline: item.status == ShareInboxStatus.pending ? () => _decline(item) : null, ), ), ); } Future _decline(ShareInboxItem item) async { await widget.shareUseCases.declineShare(item.shareId); if (!mounted) return; await _reload(); if (!mounted) return; ScaffoldMessenger.of( context, ).showSnackBar(const SnackBar(content: Text('Partage refusé.'))); } } final class _ShareInboxTile extends StatelessWidget { const _ShareInboxTile({ required this.item, required this.onOpen, required this.onAccept, required this.onDecline, }); final ShareInboxItem item; final VoidCallback? onOpen; final VoidCallback? onAccept; final VoidCallback? onDecline; @override Widget build(BuildContext context) { final summary = _summaryFromPayload(item); return CourtBlazerAccentPanel( child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Icon(_resourceIcon(item.resourceType)), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( _resourceTitle(item.resourceType), style: Theme.of(context).textTheme.labelLarge, ), const SizedBox(height: 4), Text( summary.name, style: Theme.of(context).textTheme.titleMedium, ), const SizedBox(height: 4), Text('Envoyé par ${item.senderUserId}'), if (summary.detail.isNotEmpty) ...[ const SizedBox(height: 4), Text(summary.detail), ], ], ), ), Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ Chip( visualDensity: VisualDensity.compact, label: Text(_statusLabel(item.status)), ), if (item.resourceType == ShareResourceType.pack) ...[ const SizedBox(height: 4), Chip( visualDensity: VisualDensity.compact, label: Text(_packBadge(summary.itemCount)), ), ], ], ), ], ), if (item.status == ShareInboxStatus.pending) ...[ const SizedBox(height: 16), if (item.resourceType == ShareResourceType.pack) FilledButton(onPressed: onOpen, child: const Text('Voir le pack')) else Row( children: [ Expanded( child: OutlinedButton( onPressed: onDecline, child: const Text('Refuser'), ), ), const SizedBox(width: 8), Expanded( child: FilledButton( onPressed: onAccept, child: const Text('Accepter'), ), ), ], ), ], ], ), ); } } final class _EmptyInbox extends StatelessWidget { const _EmptyInbox(); @override Widget build(BuildContext context) { return const Center( child: Padding( padding: EdgeInsets.all(24), child: Column( mainAxisSize: MainAxisSize.min, children: [ Text('Aucun partage reçu', textAlign: TextAlign.center), SizedBox(height: 8), Text( "Les programmes et séances qu'on t'envoie apparaîtront ici.", textAlign: TextAlign.center, ), ], ), ), ); } } final class _SharePackDetailScreen extends StatelessWidget { const _SharePackDetailScreen({ required this.item, required this.onImport, required this.onDecline, }); final ShareInboxItem item; final VoidCallback? onImport; final VoidCallback? onDecline; @override Widget build(BuildContext context) { final summary = _summaryFromPayload(item); final workouts = _packWorkoutNames(item); return Scaffold( appBar: AppBar(title: const Text('Pack reçu')), body: ListView( padding: const EdgeInsets.all(16), children: [ CourtBlazerAccentPanel( child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text( summary.name, style: Theme.of(context).textTheme.titleLarge, ), const SizedBox(height: 8), Text('Envoyé par ${item.senderUserId}'), const SizedBox(height: 8), Chip( visualDensity: VisualDensity.compact, label: Text(_packBadge(summary.itemCount)), ), const SizedBox(height: 16), Text('Séances', style: Theme.of(context).textTheme.titleMedium), const SizedBox(height: 8), for (final name in workouts) ListTile( contentPadding: EdgeInsets.zero, leading: const Icon(Icons.event_note), title: Text(name), ), if (item.status == ShareInboxStatus.pending) ...[ const SizedBox(height: 16), FilledButton( onPressed: onImport == null ? null : () { onImport!(); Navigator.of(context).pop(); }, child: const Text('Importer'), ), const SizedBox(height: 8), OutlinedButton( onPressed: onDecline == null ? null : () { onDecline!(); Navigator.of(context).pop(); }, child: const Text('Refuser'), ), ], ], ), ), ], ), ); } } final class _InboxSummary { const _InboxSummary({ required this.name, required this.detail, this.itemCount = 0, }); final String name; final String detail; final int itemCount; } _InboxSummary _summaryFromPayload(ShareInboxItem item) { try { final payload = jsonDecode(item.payloadJson) as Map; final name = payload['name'] as String? ?? 'Partage GameTime'; return switch (item.resourceType) { ShareResourceType.program => _InboxSummary( name: name, detail: _programDetail(payload), ), ShareResourceType.workoutTemplate => _InboxSummary( name: name, detail: _templateDetail(payload), ), ShareResourceType.pack => _InboxSummary( name: name, detail: _packDetail(payload), itemCount: _packItemCount(payload), ), }; } on Object { return const _InboxSummary(name: 'Partage GameTime', detail: ''); } } String _programDetail(Map payload) { final exercises = payload['exercises'] as List? ?? const []; final sets = exercises.fold(0, (total, raw) { final exercise = raw as Map; return total + (exercise['setsCount'] as int? ?? 0); }); return '${exercises.length} exercice${exercises.length > 1 ? 's' : ''} · ' '$sets série${sets > 1 ? 's' : ''}'; } String _templateDetail(Map payload) { final programs = payload['programs'] as List? ?? const []; var exerciseCount = 0; for (final raw in programs) { final program = raw as Map; final snapshotJson = program['programSnapshotJson'] as String?; if (snapshotJson == null) continue; final snapshot = jsonDecode(snapshotJson) as Map; exerciseCount += (snapshot['exercises'] as List? ?? const []).length; } return '${programs.length} programme${programs.length > 1 ? 's' : ''} · ' '$exerciseCount exercice${exerciseCount > 1 ? 's' : ''}'; } String _packDetail(Map payload) { final count = _packItemCount(payload); return '$count séance${count > 1 ? 's' : ''}'; } int _packItemCount(Map payload) { return (payload['workouts'] as List? ?? const []).length; } List _packWorkoutNames(ShareInboxItem item) { try { final payload = jsonDecode(item.payloadJson) as Map; final workouts = payload['workouts'] as List? ?? const []; return workouts .whereType() .map((raw) => raw['name'] as String? ?? 'Séance partagée') .toList(growable: false); } on Object { return const []; } } String _packBadge(int count) { return 'Pack · $count séance${count > 1 ? 's' : ''}'; } IconData _resourceIcon(ShareResourceType type) { return switch (type) { ShareResourceType.program => Icons.list_alt, ShareResourceType.workoutTemplate => Icons.event_note, ShareResourceType.pack => Icons.inventory_2_outlined, }; } String _resourceTitle(ShareResourceType type) { return switch (type) { ShareResourceType.program => 'Programme', ShareResourceType.workoutTemplate => 'Séance', ShareResourceType.pack => 'Pack', }; } String _statusLabel(ShareInboxStatus status) { return switch (status) { ShareInboxStatus.pending => 'À traiter', ShareInboxStatus.accepted => 'Accepté', ShareInboxStatus.declined => 'Refusé', ShareInboxStatus.revoked => 'Retiré', }; } String _acceptedMessage(ShareResourceType type) { return switch (type) { ShareResourceType.program => 'Programme ajouté.', ShareResourceType.workoutTemplate => 'Séance ajoutée.', ShareResourceType.pack => 'Pack importé.', }; }