feat(bibliothèque): badge Exemple, catégories et états vides bibliothèque (ticket #80)
Ajoute le badge "Exemple" (example_badge.dart) affiché sur les exercices/programmes/séances-modèles seedés, le filtrage par catégorie dans la bibliothèque d'exercices et les états vides dédiés. Préserve isExample lors de l'édition d'un programme ou d'une séance-modèle existants (ProgramUseCases/WorkoutTemplateUseCases). Validé GO par Main : dart analyze propre, 173 tests (4 échecs préexistants sans rapport avec ce lot). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -5,6 +5,7 @@ import 'package:image_picker/image_picker.dart';
|
||||
|
||||
import '../application/application.dart';
|
||||
import '../domain/domain.dart';
|
||||
import 'example_badge.dart';
|
||||
import 'theme.dart';
|
||||
|
||||
abstract interface class MediaSourcePicker {
|
||||
@ -134,9 +135,9 @@ final class _ExerciseLibraryScreenState extends State<ExerciseLibraryScreen> {
|
||||
SliverFillRemaining(
|
||||
hasScrollBody: false,
|
||||
child: _CenteredMessage(
|
||||
title: 'Aucun exercice pour le moment',
|
||||
title: 'Aucun exercice',
|
||||
message:
|
||||
'Crée ton premier exercice pour composer tes programmes.',
|
||||
'Crée ton premier exercice ou restaure les exemples.',
|
||||
actionLabel: 'Créer un exercice',
|
||||
onAction: () => _openForm(),
|
||||
),
|
||||
@ -275,7 +276,15 @@ final class ExerciseListTile extends StatelessWidget {
|
||||
return ListTile(
|
||||
onTap: onTap,
|
||||
leading: _ExerciseIconAvatar(iconMediaId: iconMediaId),
|
||||
title: Text(exercise.name),
|
||||
title: Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 4,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
Text(exercise.name),
|
||||
if (exercise.isExample) const ExampleBadge(),
|
||||
],
|
||||
),
|
||||
subtitle: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@ -294,6 +303,7 @@ final class ExerciseListTile extends StatelessWidget {
|
||||
spacing: 6,
|
||||
runSpacing: 6,
|
||||
children: [
|
||||
_CategoryBadge(category: exercise.category),
|
||||
...exercise.availableMeasures.map(
|
||||
(measure) => MeasureBadge(
|
||||
measure: measure,
|
||||
@ -327,6 +337,20 @@ final class ExerciseListTile extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
final class _CategoryBadge extends StatelessWidget {
|
||||
const _CategoryBadge({required this.category});
|
||||
|
||||
final ExerciseCategory category;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Chip(
|
||||
visualDensity: VisualDensity.compact,
|
||||
label: Text(category.label),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final class _ExerciseIconAvatar extends StatelessWidget {
|
||||
const _ExerciseIconAvatar({required this.iconMediaId});
|
||||
|
||||
@ -1786,11 +1810,9 @@ final class _MediaImportField extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final fileName = selectedFileName;
|
||||
final status = fileName == null
|
||||
? imported
|
||||
? 'Média déjà importé'
|
||||
: 'Aucun fichier sélectionné'
|
||||
: fileName;
|
||||
final status =
|
||||
fileName ??
|
||||
(imported ? 'Média déjà importé' : 'Aucun fichier sélectionné');
|
||||
return InputDecorator(
|
||||
decoration: InputDecoration(labelText: label),
|
||||
child: Row(
|
||||
@ -1867,3 +1889,18 @@ extension on WorkoutMeasure {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
extension on ExerciseCategory {
|
||||
String get label {
|
||||
return switch (this) {
|
||||
ExerciseCategory.shoot => 'Tir',
|
||||
ExerciseCategory.freeThrows => 'Lancers francs',
|
||||
ExerciseCategory.dribble => 'Dribble',
|
||||
ExerciseCategory.finishing => 'Finition',
|
||||
ExerciseCategory.conditioning => 'Condition physique',
|
||||
ExerciseCategory.defense => 'Défense',
|
||||
ExerciseCategory.mobility => 'Mobilité',
|
||||
ExerciseCategory.uncategorized => 'Non catégorisé',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user