diff --git a/lib/widgets/add_exercise/preview_images.dart b/lib/widgets/add_exercise/preview_images.dart index 23f7660c..5a6249a2 100644 --- a/lib/widgets/add_exercise/preview_images.dart +++ b/lib/widgets/add_exercise/preview_images.dart @@ -1,73 +1,111 @@ import 'dart:io'; - import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:wger/providers/add_exercise.dart'; -import 'mixins/image_picker_mixin.dart'; - -class PreviewExerciseImages extends StatelessWidget with ExerciseImagePickerMixin { +/// Widget to preview selected exercise images +/// +/// Displays images in a horizontal scrollable list with thumbnails. +/// Each image shows a preview thumbnail and optionally a delete button. +/// Can optionally include an "add more" button at the end of the list. +class PreviewExerciseImages extends StatelessWidget { final List selectedImages; + final VoidCallback? onAddMore; final bool allowEdit; - const PreviewExerciseImages({super.key, required this.selectedImages, this.allowEdit = true}); + const PreviewExerciseImages({ + Key? key, + required this.selectedImages, + this.onAddMore, + this.allowEdit = true, + }) : super(key: key); @override Widget build(BuildContext context) { + // Calculate item count: images + optional "add more" button + final itemCount = selectedImages.length + (allowEdit && onAddMore != null ? 1 : 0); + return SizedBox( - height: 300, - child: ListView(scrollDirection: Axis.horizontal, children: [ - ...selectedImages.map( - (file) => SizedBox( - height: 200, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Stack( - children: [ - Image.file(file), - if (allowEdit) - Positioned( - bottom: 0, - right: 0, - child: Padding( - padding: const EdgeInsets.all(3.0), - child: Container( - decoration: BoxDecoration( - color: Colors.grey.withValues(alpha: 0.5), - borderRadius: const BorderRadius.all(Radius.circular(20)), - ), - child: IconButton( - iconSize: 20, - onPressed: () => - context.read().removeExercise(file.path), - color: Colors.white, - icon: const Icon(Icons.delete), - ), - ), - ), - ), - ], - ), - ), - ), - ), - const SizedBox(width: 10), - if (allowEdit) - Padding( - padding: const EdgeInsets.all(8.0), - child: Container( - color: Colors.grey, - height: 200, - width: 100, - child: Center( - child: IconButton( - icon: const Icon(Icons.add), - onPressed: () => pickImages(context), - ), - ), - ), - ), - ]), + height: 120, + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: itemCount, + itemBuilder: (context, index) { + // Show "add more" button at the end (only if editing is allowed) + if (index == selectedImages.length) { + return _buildAddMoreButton(context); + } + + // Show image thumbnail + final image = selectedImages[index]; + return _buildImageCard(context, image); + }, + ), ); } -} + + Widget _buildImageCard(BuildContext context, File image) { + return Container( + width: 120, + margin: const EdgeInsets.only(right: 8), + child: Stack( + children: [ + // Image thumbnail + ClipRRect( + borderRadius: BorderRadius.circular(8), + child: Image.file( + image, + width: 120, + height: 120, + fit: BoxFit.cover, + ), + ), + + // Delete button overlay (only shown if editing is allowed) + if (allowEdit) + Positioned( + top: 4, + right: 4, + child: IconButton( + icon: const Icon(Icons.close), + color: Colors.white, + iconSize: 20, + style: IconButton.styleFrom( + backgroundColor: Colors.black.withOpacity(0.6), + padding: const EdgeInsets.all(4), + ), + onPressed: () { + context.read().removeExercise(image.path); + }, + ), + ), + ], + ), + ); + } + + Widget _buildAddMoreButton(BuildContext context) { + return GestureDetector( + onTap: onAddMore, + child: Container( + width: 120, + height: 120, + margin: const EdgeInsets.only(right: 8), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surfaceVariant, + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: Theme.of(context).colorScheme.outline, + width: 2, + style: BorderStyle.solid, + ), + ), + child: Icon( + Icons.add, + size: 48, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/widgets/add_exercise/steps/step5images.dart b/lib/widgets/add_exercise/steps/step5images.dart index 179e0514..e0a23ae2 100644 --- a/lib/widgets/add_exercise/steps/step5images.dart +++ b/lib/widgets/add_exercise/steps/step5images.dart @@ -32,6 +32,39 @@ class _Step5ImagesState extends State with ExerciseImagePickerMixin /// When non-null, ImageDetailsForm is displayed instead of image picker File? _currentImageToAdd; + /// Show dialog to choose between Camera and Gallery + Future _showImageSourceDialog(BuildContext context) async { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text(AppLocalizations.of(context).selectImage), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ListTile( + leading: const Icon(Icons.camera_alt), + title: Text(AppLocalizations.of(context).takePicture), + onTap: () { + Navigator.of(context).pop(); + _pickAndShowImageDetails(context, pickFromCamera: true); + }, + ), + ListTile( + leading: const Icon(Icons.collections), + title: Text(AppLocalizations.of(context).gallery), + onTap: () { + Navigator.of(context).pop(); + _pickAndShowImageDetails(context, pickFromCamera: false); + }, + ), + ], + ), + ); + }, + ); + } + /// Pick image from camera or gallery and show metadata collection form /// /// Validates file format (jpg, jpeg, png, webp) and size (<20MB) before @@ -105,18 +138,10 @@ class _Step5ImagesState extends State with ExerciseImagePickerMixin style: details['style'] ?? '1', ); - // Reset form state + // Reset form state - image is now visible in preview list setState(() { _currentImageToAdd = null; }); - - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Image added with details'), - backgroundColor: Colors.green, - duration: Duration(seconds: 2), - ), - ); } /// Cancel metadata input and return to image picker @@ -159,12 +184,15 @@ class _Step5ImagesState extends State with ExerciseImagePickerMixin // Show preview of images that have been added with metadata return Column( children: [ - PreviewExerciseImages(selectedImages: provider.exerciseImages), + PreviewExerciseImages( + selectedImages: provider.exerciseImages, + onAddMore: () => _showImageSourceDialog(context), + ), const SizedBox(height: 16), ElevatedButton.icon( - onPressed: () => _pickAndShowImageDetails(context), + onPressed: () => _showImageSourceDialog(context), icon: const Icon(Icons.add_photo_alternate), - label: const Text('Add Another Image'), + label: Text(AppLocalizations.of(context).addImage), ), ], ); @@ -190,7 +218,7 @@ class _Step5ImagesState extends State with ExerciseImagePickerMixin ElevatedButton.icon( onPressed: () => _pickAndShowImageDetails(context, pickFromCamera: true), icon: const Icon(Icons.camera_alt), - label: const Text('Camera'), + label: Text(AppLocalizations.of(context).takePicture), style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), ), @@ -199,7 +227,7 @@ class _Step5ImagesState extends State with ExerciseImagePickerMixin ElevatedButton.icon( onPressed: () => _pickAndShowImageDetails(context), icon: const Icon(Icons.collections), - label: const Text('Gallery'), + label: Text(AppLocalizations.of(context).gallery), style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), ), @@ -225,4 +253,4 @@ class _Step5ImagesState extends State with ExerciseImagePickerMixin ), ); } -} +} \ No newline at end of file