// File: shared_widgets/reusable_image_picker.dart import 'dart:io'; import 'dart:ui'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:iconly/iconly.dart'; import 'package:image_picker/image_picker.dart'; import 'package:mobile_pos/Const/api_config.dart'; // Assuming you have a l10n package for lang.S.of(context) import 'package:mobile_pos/generated/l10n.dart' as lang; import 'package:mobile_pos/constant.dart'; // kMainColor, kNeutral800 etc. class ReusableImagePicker extends StatefulWidget { final File? initialImage; final String? existingImageUrl; // NEW: Image URL for editing final Function(File?) onImagePicked; final Function()? onImageRemoved; // NEW: Callback for explicit removal const ReusableImagePicker({ super.key, this.initialImage, this.existingImageUrl, // Added to constructor required this.onImagePicked, this.onImageRemoved, // Added to constructor }); @override State createState() => _ReusableImagePickerState(); } class _ReusableImagePickerState extends State { File? _pickedImage; String? _existingImageUrl; // State for the image URL final ImagePicker _picker = ImagePicker(); @override void initState() { super.initState(); // Prioritize new file if passed, otherwise use existing URL _pickedImage = widget.initialImage; _existingImageUrl = widget.existingImageUrl; } // Update state if parent widget sends new values (e.g., when switching between edit screens) @override void didUpdateWidget(covariant ReusableImagePicker oldWidget) { super.didUpdateWidget(oldWidget); if (widget.initialImage != oldWidget.initialImage || widget.existingImageUrl != oldWidget.existingImageUrl) { // Keep the new image if present, otherwise load the URL _pickedImage = widget.initialImage; _existingImageUrl = widget.existingImageUrl; } } Future _pickImage(ImageSource source, BuildContext dialogContext) async { final XFile? xFile = await _picker.pickImage(source: source); // Close the dialog after selection attempt Navigator.of(dialogContext).pop(); if (xFile != null) { final newFile = File(xFile.path); setState(() { _pickedImage = newFile; _existingImageUrl = null; // A new file means we discard the existing URL }); widget.onImagePicked(newFile); // Notify parent screen } } // Custom Cupertino Dialog for image source selection (unchanged) void _showImageSourceDialog() { final textTheme = Theme.of(context).textTheme; showCupertinoDialog( context: context, builder: (BuildContext contexts) => BackdropFilter( filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5), child: CupertinoAlertDialog( insetAnimationCurve: Curves.bounceInOut, title: Text( lang.S.of(context).uploadImage, // Assuming this string exists textAlign: TextAlign.center, style: textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.bold), ), actions: [ CupertinoDialogAction( child: Column( children: [ const Icon(IconlyLight.image, size: 30.0), Text( lang.S.of(context).useGallery, // Assuming this string exists textAlign: TextAlign.center, style: textTheme.bodySmall?.copyWith(fontWeight: FontWeight.bold), ) ], ), onPressed: () => _pickImage(ImageSource.gallery, contexts), ), CupertinoDialogAction( child: Column( children: [ const Icon(IconlyLight.camera, size: 30.0), Text( lang.S.of(context).openCamera, // Assuming this string exists textAlign: TextAlign.center, style: textTheme.bodySmall?.copyWith(fontWeight: FontWeight.bold), ) ], ), onPressed: () => _pickImage(ImageSource.camera, contexts), ), ], ), ), ); } @override Widget build(BuildContext context) { // Determine the source to display final bool hasImage = _pickedImage != null || (_existingImageUrl?.isNotEmpty ?? false); return Container( height: 100, decoration: BoxDecoration( border: Border.all(color: Colors.grey.shade400), borderRadius: BorderRadius.circular(5), ), child: InkWell( onTap: _showImageSourceDialog, // Always allow tapping to change/add image child: !hasImage ? Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(IconlyLight.image, size: 30), const SizedBox(height: 5), Text(lang.S.of(context).addImage, style: Theme.of(context).textTheme.bodyMedium), ], ), ) : Stack( fit: StackFit.expand, children: [ ClipRRect( borderRadius: BorderRadius.circular(5), // Conditional Image Widget child: _pickedImage != null ? Image.file(_pickedImage!, fit: BoxFit.cover) // Display new file : Image.network( // Display existing image from URL '${APIConfig.domain}${_existingImageUrl!}', fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) => const Center( child: Icon(Icons.error_outline, color: Colors.red)), // Show error icon on failed load loadingBuilder: (context, child, loadingProgress) { if (loadingProgress == null) return child; return const Center(child: CircularProgressIndicator()); }, ), ), Positioned( top: 4, right: 4, child: GestureDetector( onTap: () { setState(() { _pickedImage = null; _existingImageUrl = null; // Crucial: clear URL as well }); // Notify parent that the image (file or url) is removed widget.onImagePicked(null); if (widget.onImageRemoved != null) { widget.onImageRemoved!(); } }, child: const CircleAvatar( radius: 12, backgroundColor: Colors.black54, child: Icon(Icons.close, size: 16, color: Colors.white), ), ), ), ], ), ), ); } }