import 'dart:io'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:image_picker/image_picker.dart'; import 'package:test_sa/extensions/context_extension.dart'; import 'package:test_sa/extensions/int_extensions.dart'; import 'package:test_sa/extensions/text_extensions.dart'; import 'package:test_sa/extensions/widget_extensions.dart'; import 'package:test_sa/models/generic_attachment_model.dart'; import 'package:test_sa/new_views/app_style/app_color.dart'; import '../../../new_views/common_widgets/app_dashed_button.dart'; import 'multi_image_picker_item.dart'; class MultiFilesPicker extends StatefulWidget { final String label; final bool error; final List files; final List attachment; final bool enabled, onlyImages; double? buttonHeight; Widget? buttonIcon; Color? buttonColor; final Function(List)? onChange; final bool showAsGrid; MultiFilesPicker( {Key? key, this.files = const [], this.attachment = const [], required this.label, this.error = false, this.buttonHeight, this.buttonIcon, this.enabled = true, this.onlyImages = false, this.onChange, this.showAsGrid = false, this.buttonColor}) : super(key: key); @override State createState() => _MultiFilesPickerState(); } class _MultiFilesPickerState extends State { @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (widget.enabled) ...[ AppDashedButton( title: widget.label, height: widget.buttonHeight, buttonColor: widget.buttonColor, icon: widget.buttonIcon, onPressed: (widget.enabled == false) ? () {} : widget.showAsGrid ? showFileSourceSheet : onFilePicker), 16.height, ], if (widget.files.isNotEmpty) Wrap( spacing: 8.toScreenWidth, children: List.generate( widget.files!.length, (index) { File image = widget.files![index]; return MultiFilesPickerItem( file: image, enabled: widget.enabled, onRemoveTap: (image) { if (!widget.enabled) { return; } widget.files.remove(image); if (widget.onChange != null) { widget.onChange!(widget.files); } setState(() {}); }, ); }, ), ), ], ); } fromFilePicker() async { FilePickerResult? result = await FilePicker.platform.pickFiles( type: FileType.custom, allowMultiple: true, allowedExtensions: widget.onlyImages ? ['jpg', 'jpeg', 'png'] : ['jpg', 'jpeg', 'png', 'pdf', 'doc', 'docx', 'xlsx', 'pptx'], ); if (result != null) { for (var path in result.paths) { widget.files.add(File(path!)); if (widget.onChange != null) { widget.onChange!(widget.files); } } setState(() {}); } } void showFileSourceSheet() async { if (widget.files.length >= 5) { Fluttertoast.showToast(msg: context.translation.maxImagesNumberIs5); return; } ImageSource? source = (await showModalBottomSheet( context: context, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical( top: Radius.circular(20), ), ), clipBehavior: Clip.antiAliasWithSaveLayer, builder: (BuildContext context) => Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ "Attach File".heading4(context), 12.height, GridView( padding: const EdgeInsets.all(0), shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, childAspectRatio: 1, crossAxisSpacing: 12, mainAxisSpacing: 12), children: [ gridItem(Icons.camera_enhance_rounded, context.translation.pickFromCamera).onPress(() => Navigator.of(context).pop(ImageSource.camera)), gridItem(Icons.image_rounded, context.translation.pickFromGallery).onPress(() => Navigator.of(context).pop(ImageSource.gallery)), gridItem(Icons.file_present_rounded, context.translation.pickFromFiles).onPress(() async { await fromFilePicker(); Navigator.pop(context); }), ], ), 12.height, ], ).paddingAll(21), )) as ImageSource?; if (source == null) return; final pickedFile = await ImagePicker().pickImage(source: source, imageQuality: 70, maxWidth: 800, maxHeight: 800); if (pickedFile != null) { File fileImage = File(pickedFile.path); widget.files.add(fileImage); if (widget.onChange != null) { widget.onChange!(widget.files); } setState(() {}); } } Widget gridItem(IconData iconData, String title) { return Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), border: Border.all(color: const Color(0xffF1F1F1), width: 1), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Icon(iconData, color: const Color(0xff7D859A), size: 36), Text( title, style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w500), ), ], ), ); } onFilePicker() async { if (widget.files.length >= 5) { Fluttertoast.showToast(msg: context.translation.maxImagesNumberIs5); return; } ImageSource? source = await showModalBottomSheet( context: context, builder: (BuildContext context) { Widget listCard({required String icon, required String label, required VoidCallback onTap}) { return GestureDetector( onTap: onTap, child: Container( constraints: BoxConstraints(minWidth: 111.toScreenWidth, minHeight: 111.toScreenHeight), padding: EdgeInsets.symmetric(horizontal: 12.toScreenWidth, vertical: 12.toScreenHeight), decoration: BoxDecoration(borderRadius: BorderRadius.circular(12), border: Border.all(width: 1, color: AppColor.white70)), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ icon.toSvgAsset(), 24.height, label.bodyText2(context).custom(color: AppColor.black20), ], ), ), ); } return Container( padding: const EdgeInsets.all(16.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ listCard( icon: 'camera_icon', label: '${context.translation.open}\n${context.translation.camera}', onTap: () { Navigator.of(context).pop(ImageSource.camera); }, ), listCard( icon: 'gallery_icon', label: '${context.translation.open}\n${context.translation.gallery}', onTap: () { Navigator.of(context).pop(ImageSource.gallery); }, ), listCard( icon: 'file_icon', label: '${context.translation.open}\n${context.translation.files}', onTap: () async { await fromFilePicker(); Navigator.pop(context); }, ), ], ), ); }, ); // ImageSource source = await showDialog( // context: context, // builder: (dialogContext) => CupertinoAlertDialog( // actions: [ // TextButton( // child: Text(context.translation.pickFromCamera), // onPressed: () { // Navigator.of(dialogContext).pop(ImageSource.camera); // }, // ), // TextButton( // child: Text(context.translation.pickFromGallery), // onPressed: () { // Navigator.of(dialogContext).pop(ImageSource.gallery); // }, // ), // TextButton( // child: Text(context.translation.pickFromFiles), // onPressed: () async { // await fromFilePicker(); // Navigator.pop(context); // }, // ), // ], // ), // ); if (source == null) return; final pickedFile = await ImagePicker().pickImage(source: source, imageQuality: 70, maxWidth: 800, maxHeight: 800); if (pickedFile != null) { File fileImage = File(pickedFile.path); if (fileImage != null) { widget.files.add(fileImage); if (widget.onChange != null) { widget.onChange!(widget.files); } setState(() {}); } } setState(() {}); } } class AttachmentModel { int id = 0; File? file; AttachmentModel(this.id, this.file); factory AttachmentModel.fromJson(Map json) { return AttachmentModel( json['id'] ?? 0, json['file'] != null ? File(json['file']) : null, ); } Map toJson() { return { 'id': id, 'file': file?.path, }; } } class AttachmentPicker extends StatefulWidget { final String label; final bool error; final List attachment; final bool enabled, onlyImages; double? buttonHeight; Widget? buttonIcon; Color? buttonColor; final Function(List)? onChange; final bool showAsGrid; AttachmentPicker( {Key? key, this.attachment = const [], required this.label, this.error = false, this.buttonHeight, this.buttonIcon, this.enabled = true, this.onlyImages = false, this.onChange, this.showAsGrid = false, this.buttonColor}) : super(key: key); @override State createState() => _AttachmentPickerState(); } class _AttachmentPickerState extends State { @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ AppDashedButton( title: widget.label, height: widget.buttonHeight, buttonColor: widget.buttonColor, icon: widget.buttonIcon, onPressed: (widget.enabled == false) ? () {} : widget.showAsGrid ? showFileSourceSheet : onFilePicker), 16.height, if (widget.attachment.isNotEmpty) Wrap( spacing: 8.toScreenWidth, children: List.generate( widget.attachment.length, (index) { File image = File(widget.attachment[index].name!); return MultiFilesPickerItem( file: image, enabled: widget.enabled, onRemoveTap: (image) { if (!widget.enabled) { return; } widget.attachment.removeAt(index); if (widget.onChange != null) { widget.onChange!(widget.attachment); } setState(() {}); }, ); }, ), ), ], ); } fromFilePicker() async { FilePickerResult? result = await FilePicker.platform.pickFiles( type: FileType.custom, allowMultiple: true, allowedExtensions: widget.onlyImages ? ['jpg', 'jpeg', 'png'] : ['jpg', 'jpeg', 'png', 'pdf', 'doc', 'docx', 'xlsx', 'pptx'], ); if (result != null) { for (var path in result.paths) { widget.attachment.add(GenericAttachmentModel(id: 0,name: File(path!).path)); } if (widget.onChange != null) { widget.onChange!(widget.attachment); } setState(() {}); } } void showFileSourceSheet() async { if (widget.attachment.length >= 5) { Fluttertoast.showToast(msg: context.translation.maxImagesNumberIs5); return; } ImageSource source = (await showModalBottomSheet( context: context, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical( top: Radius.circular(20), ), ), clipBehavior: Clip.antiAliasWithSaveLayer, builder: (BuildContext context) => Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ "Attach File".heading4(context), 12.height, GridView( padding: const EdgeInsets.all(0), shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, childAspectRatio: 1, crossAxisSpacing: 12, mainAxisSpacing: 12), children: [ gridItem(Icons.camera_enhance_rounded, context.translation.pickFromCamera).onPress(() => Navigator.of(context).pop(ImageSource.camera)), gridItem(Icons.image_rounded, context.translation.pickFromGallery).onPress(() => Navigator.of(context).pop(ImageSource.gallery)), gridItem(Icons.file_present_rounded, context.translation.pickFromFiles).onPress(() async { await fromFilePicker(); Navigator.pop(context); }), ], ), 12.height, ], ).paddingAll(21), )) as ImageSource; final pickedFile = await ImagePicker().pickImage(source: source, imageQuality: 70, maxWidth: 800, maxHeight: 800); if (pickedFile != null) { File fileImage = File(pickedFile.path); widget.attachment.add(GenericAttachmentModel(id: 0,name: fileImage.path)); if (widget.onChange != null) { widget.onChange!(widget.attachment); } setState(() {}); } } Widget gridItem(IconData iconData, String title) { return Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), border: Border.all(color: const Color(0xffF1F1F1), width: 1), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Icon(iconData, color: const Color(0xff7D859A), size: 36), Text( title, style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w500), ), ], ), ); } onFilePicker() async { if (widget.attachment.length >= 5) { Fluttertoast.showToast(msg: context.translation.maxImagesNumberIs5); return; } ImageSource? source = await showModalBottomSheet( context: context, builder: (BuildContext context) { Widget listCard({required String icon, required String label, required VoidCallback onTap}) { return GestureDetector( onTap: onTap, child: Container( constraints: BoxConstraints(minWidth: 111.toScreenWidth, minHeight: 111.toScreenHeight), padding: EdgeInsets.symmetric(horizontal: 12.toScreenWidth, vertical: 12.toScreenHeight), decoration: BoxDecoration(borderRadius: BorderRadius.circular(12), border: Border.all(width: 1, color: AppColor.white70)), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ icon.toSvgAsset(), 24.height, label.bodyText2(context).custom(color: AppColor.black20), ], ), ), ); } return Container( padding: const EdgeInsets.all(16.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ listCard( icon: 'camera_icon', label: '${context.translation.open}\n${context.translation.camera}', onTap: () { Navigator.of(context).pop(ImageSource.camera); }, ), listCard( icon: 'gallery_icon', label: '${context.translation.open}\n${context.translation.gallery}', onTap: () { Navigator.of(context).pop(ImageSource.gallery); }, ), listCard( icon: 'file_icon', label: '${context.translation.open}\n${context.translation.files}', onTap: () async { await fromFilePicker(); Navigator.pop(context); }, ), ], ), ); }, ); // ImageSource source = await showDialog( // context: context, // builder: (dialogContext) => CupertinoAlertDialog( // actions: [ // TextButton( // child: Text(context.translation.pickFromCamera), // onPressed: () { // Navigator.of(dialogContext).pop(ImageSource.camera); // }, // ), // TextButton( // child: Text(context.translation.pickFromGallery), // onPressed: () { // Navigator.of(dialogContext).pop(ImageSource.gallery); // }, // ), // TextButton( // child: Text(context.translation.pickFromFiles), // onPressed: () async { // await fromFilePicker(); // Navigator.pop(context); // }, // ), // ], // ), // ); if (source == null) return; final pickedFile = await ImagePicker().pickImage(source: source, imageQuality: 70, maxWidth: 800, maxHeight: 800); if (pickedFile != null) { File fileImage = File(pickedFile.path); widget.attachment.add(GenericAttachmentModel(id: 0, name: fileImage.path)); if (widget.onChange != null) { widget.onChange!(widget.attachment); } setState(() {}); } setState(() {}); } }