/// File Upload Card Widget /// /// Reusable widget for uploading image files with preview. library; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:worker/core/constants/ui_constants.dart'; import 'package:worker/core/theme/colors.dart'; /// File Upload Card /// /// A reusable widget for uploading files with preview functionality. /// Features: /// - Dashed border upload area /// - Camera/file icon /// - Title and subtitle /// - Image preview after selection /// - Remove button /// /// Usage: /// ```dart /// FileUploadCard( /// file: selectedFile, /// onTap: () => pickImage(), /// onRemove: () => removeImage(), /// icon: Icons.camera_alt, /// title: 'Chụp ảnh hoặc chọn file', /// subtitle: 'JPG, PNG tối đa 5MB', /// ) /// ``` class FileUploadCard extends StatelessWidget { /// Creates a file upload card const FileUploadCard({ super.key, required this.file, required this.onTap, required this.onRemove, required this.icon, required this.title, required this.subtitle, }); /// Selected file (null if not selected) final File? file; /// Callback when upload area is tapped final VoidCallback onTap; /// Callback to remove selected file final VoidCallback onRemove; /// Icon to display in upload area final IconData icon; /// Title text final String title; /// Subtitle text final String subtitle; /// Format file size in bytes to human-readable string String _formatFileSize(int bytes) { if (bytes == 0) return '0 B'; const suffixes = ['B', 'KB', 'MB', 'GB']; final i = (bytes.bitLength - 1) ~/ 10; final size = bytes / (1 << (i * 10)); return '${size.toStringAsFixed(2)} ${suffixes[i]}'; } /// Get file name from path String _getFileName(String path) { return path.split('/').last; } @override Widget build(BuildContext context) { if (file != null) { // Show preview with remove option return _buildPreview(context); } else { // Show upload area return _buildUploadArea(context); } } /// Build upload area Widget _buildUploadArea(BuildContext context) { return InkWell( onTap: onTap, borderRadius: BorderRadius.circular(AppRadius.lg), child: Container( decoration: BoxDecoration( color: AppColors.white, border: Border.all( color: const Color(0xFFCBD5E1), width: 2, strokeAlign: BorderSide.strokeAlignInside, ), borderRadius: BorderRadius.circular(AppRadius.lg), ), padding: const EdgeInsets.all(AppSpacing.lg), child: Column( children: [ // Icon Icon(icon, size: 32, color: AppColors.grey500), const SizedBox(height: AppSpacing.sm), // Title Text( title, style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: AppColors.grey900, ), ), const SizedBox(height: AppSpacing.xs), // Subtitle Text( subtitle, style: const TextStyle(fontSize: 12, color: AppColors.grey500), ), ], ), ), ); } /// Build preview with remove button Widget _buildPreview(BuildContext context) { return Container( decoration: BoxDecoration( color: AppColors.white, border: Border.all(color: AppColors.grey100, width: 1), borderRadius: BorderRadius.circular(AppRadius.md), ), padding: const EdgeInsets.all(AppSpacing.sm), child: Row( children: [ // Thumbnail ClipRRect( borderRadius: BorderRadius.circular(AppRadius.sm), child: Image.file( file!, width: 50, height: 50, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) { return Container( width: 50, height: 50, color: AppColors.grey100, child: const Icon( FontAwesomeIcons.image, color: AppColors.grey500, size: 24, ), ); }, ), ), const SizedBox(width: AppSpacing.sm), // File info Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text( _getFileName(file!.path), style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w500, color: AppColors.grey900, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 2), FutureBuilder( future: file!.length(), builder: (context, snapshot) { if (snapshot.hasData) { return Text( _formatFileSize(snapshot.data!), style: const TextStyle( fontSize: 12, color: AppColors.grey500, ), ); } return const SizedBox.shrink(); }, ), ], ), ), const SizedBox(width: AppSpacing.xs), // Remove button IconButton( icon: const FaIcon(FontAwesomeIcons.xmark, color: AppColors.danger, size: 18), onPressed: onRemove, padding: EdgeInsets.zero, constraints: const BoxConstraints(), splashRadius: 20, ), ], ), ); } }