chore(wip): consolidation intermédiaire multi-tickets (sprints Statistiques, UI, Bug resolution, Serveur-client)
Regroupe l'état de travail en cours réalisé dans un même worktree sur plusieurs tickets/sprints (#85, #136, #145, #155-160, #162-164), mélangeant des tickets QA et inProgress. Ne constitue pas une feature terminée : commit de sauvegarde avant triage/split par ticket en branches feature/* dédiées. Exclut les dossiers d'environnement de build locaux et le heap dump parasite (.gitignore mis à jour). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -46,6 +46,9 @@ final class _ShareInboxScreenState extends State<ShareInboxScreen> {
|
||||
final item = items[index];
|
||||
return _ShareInboxTile(
|
||||
item: item,
|
||||
onOpen: item.resourceType == ShareResourceType.pack
|
||||
? () => _openPack(item)
|
||||
: null,
|
||||
onAccept: item.status == ShareInboxStatus.pending
|
||||
? () => _accept(item)
|
||||
: null,
|
||||
@ -80,6 +83,22 @@ final class _ShareInboxScreenState extends State<ShareInboxScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _openPack(ShareInboxItem item) async {
|
||||
await Navigator.of(context).push<void>(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => _SharePackDetailScreen(
|
||||
item: item,
|
||||
onImport: item.status == ShareInboxStatus.pending
|
||||
? () => _accept(item)
|
||||
: null,
|
||||
onDecline: item.status == ShareInboxStatus.pending
|
||||
? () => _decline(item)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _decline(ShareInboxItem item) async {
|
||||
await widget.shareUseCases.declineShare(item.shareId);
|
||||
if (!mounted) return;
|
||||
@ -94,11 +113,13 @@ final class _ShareInboxScreenState extends State<ShareInboxScreen> {
|
||||
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;
|
||||
|
||||
@ -112,11 +133,7 @@ final class _ShareInboxTile extends StatelessWidget {
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(
|
||||
item.resourceType == ShareResourceType.program
|
||||
? Icons.list_alt
|
||||
: Icons.event_note,
|
||||
),
|
||||
Icon(_resourceIcon(item.resourceType)),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
@ -140,31 +157,46 @@ final class _ShareInboxTile extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
),
|
||||
Chip(
|
||||
visualDensity: VisualDensity.compact,
|
||||
label: Text(_statusLabel(item.status)),
|
||||
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),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: OutlinedButton(
|
||||
onPressed: onDecline,
|
||||
child: const Text('Refuser'),
|
||||
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'),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: FilledButton(
|
||||
onPressed: onAccept,
|
||||
child: const Text('Accepter'),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
@ -196,11 +228,91 @@ final class _EmptyInbox extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
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});
|
||||
const _InboxSummary({
|
||||
required this.name,
|
||||
required this.detail,
|
||||
this.itemCount = 0,
|
||||
});
|
||||
|
||||
final String name;
|
||||
final String detail;
|
||||
final int itemCount;
|
||||
}
|
||||
|
||||
_InboxSummary _summaryFromPayload(ShareInboxItem item) {
|
||||
@ -216,6 +328,11 @@ _InboxSummary _summaryFromPayload(ShareInboxItem item) {
|
||||
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: '');
|
||||
@ -247,10 +364,45 @@ String _templateDetail(Map<String, dynamic> payload) {
|
||||
'$exerciseCount exercice${exerciseCount > 1 ? 's' : ''}';
|
||||
}
|
||||
|
||||
String _packDetail(Map<String, dynamic> payload) {
|
||||
final count = _packItemCount(payload);
|
||||
return '$count séance${count > 1 ? 's' : ''}';
|
||||
}
|
||||
|
||||
int _packItemCount(Map<String, dynamic> payload) {
|
||||
return (payload['workouts'] as List<dynamic>? ?? const []).length;
|
||||
}
|
||||
|
||||
List<String> _packWorkoutNames(ShareInboxItem item) {
|
||||
try {
|
||||
final payload = jsonDecode(item.payloadJson) as Map<String, dynamic>;
|
||||
final workouts = payload['workouts'] as List<dynamic>? ?? const [];
|
||||
return workouts
|
||||
.whereType<Map>()
|
||||
.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',
|
||||
};
|
||||
}
|
||||
|
||||
@ -267,5 +419,6 @@ String _acceptedMessage(ShareResourceType type) {
|
||||
return switch (type) {
|
||||
ShareResourceType.program => 'Programme ajouté.',
|
||||
ShareResourceType.workoutTemplate => 'Séance ajoutée.',
|
||||
ShareResourceType.pack => 'Pack importé.',
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user