onufitness_mobile/lib/widgets/bottomsheet/common_upload_option_bottomsheet.dart
2026-01-13 11:36:24 +05:30

320 lines
9.8 KiB
Dart

import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:file_picker/file_picker.dart';
class CommonUploadBottomSheet {
static Future<void> show({
required BuildContext context,
required Function(String filePath, String fileName, String fileExtension)
onFileSelected,
List<String>? allowedFileTypes,
double maxFileSizeMB = 5.0,
bool showCamera = true,
bool showGallery = true,
bool showDocument = true,
String title = "Upload File",
Function(String errorMessage)? onError,
}) async {
await showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (BuildContext context) {
return Container(
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
),
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewPadding.bottom,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Drag Handle
Container(
margin: const EdgeInsets.only(top: 12),
width: 40,
height: 4,
decoration: BoxDecoration(
color: Colors.grey.shade300,
borderRadius: BorderRadius.circular(2),
),
),
const SizedBox(height: 16),
// Title
Text(
title,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 20),
// Options Row
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
if (showCamera)
_UploadOptionButton(
icon: Icons.camera_alt,
label: "Camera",
color: Colors.blue,
onTap: () async {
Navigator.pop(context);
await _pickFromCamera(
onFileSelected: onFileSelected,
onError: onError,
maxFileSizeMB: maxFileSizeMB,
allowedFileTypes: allowedFileTypes,
);
},
),
if (showGallery)
_UploadOptionButton(
icon: Icons.image,
label: "Gallery",
color: Colors.purple,
onTap: () async {
Navigator.pop(context);
await _pickFromGallery(
onFileSelected: onFileSelected,
onError: onError,
maxFileSizeMB: maxFileSizeMB,
allowedFileTypes: allowedFileTypes,
);
},
),
if (showDocument)
_UploadOptionButton(
icon: Icons.insert_drive_file,
label: "Document",
color: Colors.orange,
onTap: () async {
Navigator.pop(context);
await _pickDocument(
onFileSelected: onFileSelected,
onError: onError,
maxFileSizeMB: maxFileSizeMB,
allowedFileTypes: allowedFileTypes,
);
},
),
],
),
),
const SizedBox(height: 20),
Container(
color: Colors.white,
padding: EdgeInsetsDirectional.only(
bottom: MediaQuery.of(context).viewPadding.bottom,
),
),
],
),
);
},
);
}
// Pick from Camera
static Future<void> _pickFromCamera({
required Function(String, String, String) onFileSelected,
Function(String)? onError,
required double maxFileSizeMB,
List<String>? allowedFileTypes,
}) async {
try {
final ImagePicker picker = ImagePicker();
final XFile? image = await picker.pickImage(
source: ImageSource.camera,
imageQuality: 100,
);
if (image != null) {
final fileBytes = await image.readAsBytes();
final fileSizeMB = fileBytes.length / (1024 * 1024);
if (fileSizeMB > maxFileSizeMB) {
onError?.call(
"File size (${fileSizeMB.toStringAsFixed(2)}MB) exceeds maximum allowed size of ${maxFileSizeMB}MB",
);
return;
}
final extension = image.path.split('.').last.toLowerCase();
final fileName = image.path.split('/').last;
if (allowedFileTypes != null && !allowedFileTypes.contains(extension)) {
onError?.call(
"File type .$extension is not allowed. Allowed types: ${allowedFileTypes.join(', ')}",
);
return;
}
onFileSelected(image.path, fileName, extension);
}
} catch (e) {
onError?.call("Failed to capture image: $e");
}
}
// Pick from Gallery
static Future<void> _pickFromGallery({
required Function(String, String, String) onFileSelected,
Function(String)? onError,
required double maxFileSizeMB,
List<String>? allowedFileTypes,
}) async {
try {
final ImagePicker picker = ImagePicker();
// Check if video extensions are in allowedFileTypes
final isVideoOnly =
allowedFileTypes != null &&
allowedFileTypes.every(
(ext) => ['mp4', 'm4v'].contains(ext.toLowerCase()),
);
XFile? file;
if (isVideoOnly) {
// Pick video if only video types are allowed
file = await picker.pickVideo(source: ImageSource.gallery);
} else {
// Pick image for image types
file = await picker.pickImage(
source: ImageSource.gallery,
imageQuality: 100,
);
}
if (file != null) {
final fileBytes = await file.readAsBytes();
final fileSizeMB = fileBytes.length / (1024 * 1024);
if (fileSizeMB > maxFileSizeMB) {
onError?.call(
"File size (${fileSizeMB.toStringAsFixed(2)}MB) exceeds maximum allowed size of ${maxFileSizeMB}MB",
);
return;
}
final extension = file.path.split('.').last.toLowerCase();
final fileName = file.path.split('/').last;
if (allowedFileTypes != null && !allowedFileTypes.contains(extension)) {
onError?.call(
"File type .$extension is not allowed. Allowed types: ${allowedFileTypes.join(', ')}",
);
return;
}
onFileSelected(file.path, fileName, extension);
}
} catch (e) {
onError?.call("Failed to pick file: $e");
}
}
// Pick Document
static Future<void> _pickDocument({
required Function(String, String, String) onFileSelected,
Function(String)? onError,
required double maxFileSizeMB,
List<String>? allowedFileTypes,
}) async {
try {
final result = await FilePicker.platform.pickFiles(
type: allowedFileTypes != null ? FileType.custom : FileType.any,
allowedExtensions: allowedFileTypes,
);
if (result != null && result.files.single.path != null) {
final file = result.files.single;
final filePath = file.path!;
final fileSizeMB = file.size / (1024 * 1024);
final extension = file.extension?.toLowerCase() ?? '';
final fileName = file.name;
if (fileSizeMB > maxFileSizeMB) {
onError?.call(
"File size (${fileSizeMB.toStringAsFixed(2)}MB) exceeds maximum allowed size of ${maxFileSizeMB}MB",
);
return;
}
if (allowedFileTypes != null && !allowedFileTypes.contains(extension)) {
onError?.call(
"File type .$extension is not allowed. Allowed types: ${allowedFileTypes.join(', ')}",
);
return;
}
onFileSelected(filePath, fileName, extension);
}
} catch (e) {
onError?.call("Failed to pick document: $e");
}
}
}
// Upload Option Button Widget
class _UploadOptionButton extends StatelessWidget {
final IconData icon;
final String label;
final Color color;
final VoidCallback onTap;
const _UploadOptionButton({
required this.icon,
required this.label,
required this.color,
required this.onTap,
});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 60,
height: 60,
decoration: BoxDecoration(
color: color.withValues(alpha: 0.1),
shape: BoxShape.circle,
border: Border.all(
color: color.withValues(alpha: 0.3),
width: 1.5,
),
),
child: Icon(icon, color: color, size: 28),
),
const SizedBox(height: 8),
Text(
label,
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade700,
fontWeight: FontWeight.w500,
),
),
],
),
);
}
}