diff --git a/lib/dashboard_latest/dashboard_view.dart b/lib/dashboard_latest/dashboard_view.dart index 122038b7..d6fa9157 100644 --- a/lib/dashboard_latest/dashboard_view.dart +++ b/lib/dashboard_latest/dashboard_view.dart @@ -14,6 +14,7 @@ import 'package:test_sa/dashboard_latest/widgets/app_bar_widget.dart'; import 'package:test_sa/dashboard_latest/widgets/progress_fragment.dart'; import 'package:test_sa/dashboard_latest/widgets/requests_fragment.dart'; import 'package:test_sa/extensions/int_extensions.dart'; +import 'package:test_sa/models/enums/user_types.dart'; import 'package:test_sa/models/user.dart'; import 'package:test_sa/service_request_latest/request_detail_provider.dart'; @@ -113,6 +114,7 @@ class _DashboardViewState extends State { @override Widget build(BuildContext context) { + bool isNurse = (Provider.of(context, listen: false).user!.type) == UsersTypes.normal_user; return Scaffold( // backgroundColor: AppColor.background(context), appBar: PreferredSize( @@ -133,7 +135,7 @@ class _DashboardViewState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, - children: [16.height, ProgressFragment(), const RequestsFragment(), const RequestCategoryFragment()], + children: [16.height, ProgressFragment(), 16.height, if (!isNurse) const RequestsFragment(), const RequestCategoryFragment()], ), ), ), diff --git a/lib/dashboard_latest/widgets/requests_fragment.dart b/lib/dashboard_latest/widgets/requests_fragment.dart index df1323b0..16a8fac5 100644 --- a/lib/dashboard_latest/widgets/requests_fragment.dart +++ b/lib/dashboard_latest/widgets/requests_fragment.dart @@ -19,7 +19,7 @@ class RequestsFragment extends StatelessWidget { Widget build(BuildContext context) { return Consumer( builder: (context, snapshot, _) => GridView( - padding: const EdgeInsets.only(top: 16, left: 16, right: 16), + padding: const EdgeInsets.only( left: 16, right: 16), physics: const NeverScrollableScrollPhysics(), shrinkWrap: true, gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 4, childAspectRatio: 72 / 84, crossAxisSpacing: 2, mainAxisSpacing: 12), diff --git a/lib/service_request_latest/views/forms/spare_part/spare_part_request.dart b/lib/service_request_latest/views/forms/spare_part/spare_part_request.dart index 81ace39b..c9b2d338 100644 --- a/lib/service_request_latest/views/forms/spare_part/spare_part_request.dart +++ b/lib/service_request_latest/views/forms/spare_part/spare_part_request.dart @@ -4,7 +4,6 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import 'package:test_sa/controllers/api_routes/urls.dart'; import 'package:test_sa/controllers/providers/api/parts_provider.dart'; import 'package:test_sa/extensions/context_extension.dart'; import 'package:test_sa/extensions/int_extensions.dart'; @@ -17,10 +16,9 @@ import 'package:test_sa/new_views/common_widgets/single_item_drop_down_menu.dart import 'package:test_sa/providers/loading_list_notifier.dart'; import 'package:test_sa/service_request_latest/request_detail_provider.dart'; import 'package:test_sa/service_request_latest/views/components/action_button/footer_action_button.dart'; -import 'package:test_sa/service_request_latest/views/components/activities_list_view.dart'; -import 'package:test_sa/service_request_latest/views/request_detail_main_view.dart'; -import 'package:test_sa/views/widgets/images/multi_image_picker.dart'; +import 'package:test_sa/views/widgets/images/new_multi_image_picker.dart'; import 'package:test_sa/views/widgets/loaders/loading_manager.dart'; + import '../../../../../../models/service_request/spare_parts.dart'; import '../../../../../../new_views/common_widgets/app_text_form_field.dart'; import '../../../../../../new_views/common_widgets/default_app_bar.dart'; @@ -42,7 +40,7 @@ class _SparePartRequestState extends State with TickerProvider bool _isLoading = false; List _spareParts = []; - List _files = []; + List _files = []; final GlobalKey _formKey = GlobalKey(); final GlobalKey _scaffoldKey = GlobalKey(); final TextEditingController _partQtyController = TextEditingController(); @@ -55,22 +53,21 @@ class _SparePartRequestState extends State with TickerProvider _partsProvider = Provider.of(context, listen: false); _requestDetailProvider = Provider.of(context, listen: false); _requestDetailProvider?.sparePartHelperModel = SparePartHelperModel( - id: _requestDetailProvider?.sparePartHelperModel?.id??0, - workOrderId:_requestDetailProvider?.sparePartHelperModel?.workOrderId?? _requestDetailProvider?.currentWorkOrder?.data?.requestId, - sparePartAttachments:_requestDetailProvider?.sparePartHelperModel?.sparePartAttachments?? [], - sparePart:_requestDetailProvider?.sparePartHelperModel?.sparePart?? SparePart(), + id: _requestDetailProvider?.sparePartHelperModel?.id ?? 0, + workOrderId: _requestDetailProvider?.sparePartHelperModel?.workOrderId ?? _requestDetailProvider?.currentWorkOrder?.data?.requestId, + sparePartAttachments: _requestDetailProvider?.sparePartHelperModel?.sparePartAttachments ?? [], + sparePart: _requestDetailProvider?.sparePartHelperModel?.sparePart ?? SparePart(), quantity: _requestDetailProvider?.sparePartHelperModel?.quantity, activityStatusId: _requestDetailProvider?.sparePartHelperModel?.activityStatusId, - comment: _requestDetailProvider?.sparePartHelperModel?.comment??'', + comment: _requestDetailProvider?.sparePartHelperModel?.comment ?? '', ); - _partQtyController.text =_requestDetailProvider?.sparePartHelperModel?.quantity!=null? _requestDetailProvider!.sparePartHelperModel!.quantity.toString():''; - _oracleNoController.text = _requestDetailProvider?.sparePartHelperModel?.sparePart?.oracleCode!=null?_requestDetailProvider!.sparePartHelperModel!.sparePart!.oracleCode!:''; - _descriptionController.text =_requestDetailProvider?.sparePartHelperModel?.comment!=null? _requestDetailProvider!.sparePartHelperModel!.comment!:''; + _partQtyController.text = _requestDetailProvider?.sparePartHelperModel?.quantity != null ? _requestDetailProvider!.sparePartHelperModel!.quantity.toString() : ''; + _oracleNoController.text = _requestDetailProvider?.sparePartHelperModel?.sparePart?.oracleCode != null ? _requestDetailProvider!.sparePartHelperModel!.sparePart!.oracleCode! : ''; + _descriptionController.text = _requestDetailProvider?.sparePartHelperModel?.comment != null ? _requestDetailProvider!.sparePartHelperModel!.comment! : ''; scheduleMicrotask(() async { _isLoading = true; - _files = _requestDetailProvider?.sparePartHelperModel?.sparePartAttachments?.map((e) => File(e.name!)).toList() ?? []; - + _files = _requestDetailProvider?.sparePartHelperModel?.sparePartAttachments?.map((e) => MultiFilesPickerModel(e.id!, File(e.name!))).toList() ?? []; setState(() {}); _spareParts = await _partsProvider!.getPartsList(assetId: _requestDetailProvider?.currentWorkOrder?.data?.asset?.id); @@ -95,6 +92,11 @@ class _SparePartRequestState extends State with TickerProvider super.dispose(); } + bool _isLocalUrl(String url) { + if (url.isEmpty != false) return false; + return url.startsWith("/") || url.startsWith("file://") || url.substring(1).startsWith(':\\'); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -129,10 +131,10 @@ class _SparePartRequestState extends State with TickerProvider initialValue: requestDetailProvider.sparePartHelperModel?.sparePart, backgroundColor: context.isDark ? AppColor.neutral20 : AppColor.neutral90, onSelect: (part) { - if(part!=null){ + if (part != null) { requestDetailProvider.sparePartHelperModel?.sparePart = part; - _oracleNoController.text = part.oracleCode??''; - requestDetailProvider.updateSparePartHelperModel(_requestDetailProvider?.sparePartHelperModel); + _oracleNoController.text = part.oracleCode ?? ''; + requestDetailProvider.updateSparePartHelperModel(_requestDetailProvider?.sparePartHelperModel); } }, ), @@ -185,14 +187,14 @@ class _SparePartRequestState extends State with TickerProvider }, ), 12.height, - MultiFilesPicker( + NewMultiFilesPicker( label: context.translation.attachQuotation, files: _files, buttonIcon: 'quotation_icon'.toSvgAsset(), buttonColor: AppColor.primary10, - onChange: (List file){ - for (var element in file) { - print('path is ${element.path}'); + onChange: (List list) { + for (var element in list) { + print('path is ${element.file.path}'); } }, ), @@ -205,41 +207,23 @@ class _SparePartRequestState extends State with TickerProvider ).paddingAll(12).expanded, FooterActionButton.footerContainer( child: AppFilledButton( - label:_requestDetailProvider?.sparePartHelperModel?.id==0? context.translation.addSparePartActivity:context.translation.updateSparePartActivity, + label: _requestDetailProvider?.sparePartHelperModel?.id == 0 ? context.translation.addSparePartActivity : context.translation.updateSparePartActivity, buttonColor: AppColor.green70, onPressed: () async { - // List attachmentList = []; - // List localFile = []; - // for (var file in _files) { - // print('loop executed..${file.path}'); - // if(_requestDetailProvider?.sparePartHelperModel?.sparePartAttachments!=null){ - // for (var attachment in _requestDetailProvider!.sparePartHelperModel!.sparePartAttachments!) { - // if(file.path==attachment.name){ - // print('name i got is ${attachment.name}'); - // attachmentList.add(attachment); - // } - // } - // } - // else{ - // print('file path i got is ${file.path}'); - // localFile.add(file); - // } - // attachmentList.add(SparePartAttachments(id: 0, name: "${file.path.split("/").last}|${base64Encode(file.readAsBytesSync())}")); - // requestDetailProvider.sparePartHelperModel?.sparePartAttachments - // ?.add(SparePartAttachments(id: 0, name: "${file.path.split("/").last}|${base64Encode(file.readAsBytesSync())}")); - // } - // for(var item in localFile){ - // attachmentList.add(SparePartAttachments(id: 0, name: "${item.path.split("/").last}|${base64Encode(item.readAsBytesSync())}")); - // } + requestDetailProvider.sparePartHelperModel?.sparePartAttachments?.clear(); + for (var pickerObject in _files) { + String fileData = _isLocalUrl(pickerObject.file.path) + ? ("${pickerObject.file.path.split("/").last}|${base64Encode(File(pickerObject.file.path).readAsBytesSync())}") + : pickerObject.file.path; + requestDetailProvider.sparePartHelperModel?.sparePartAttachments?.add(SparePartAttachments(id: pickerObject.id, name: fileData)); + } showDialog(context: context, barrierDismissible: false, builder: (context) => const AppLazyLoading()); int status = -1; - if(_requestDetailProvider?.sparePartHelperModel?.id==0){ + if (_requestDetailProvider?.sparePartHelperModel?.id == 0) { status = await requestDetailProvider.createActivitySparePart(); - } - else{ + } else { status = await requestDetailProvider.updateActivitySparePart(); - } if (status == 200) { await requestDetailProvider.getWorkOrderById(id: requestDetailProvider.currentWorkOrder!.data!.requestId!); diff --git a/lib/views/widgets/images/new_multi_image_picker.dart b/lib/views/widgets/images/new_multi_image_picker.dart new file mode 100644 index 00000000..10487230 --- /dev/null +++ b/lib/views/widgets/images/new_multi_image_picker.dart @@ -0,0 +1,291 @@ +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/new_views/app_style/app_color.dart'; + +import '../../../new_views/common_widgets/app_dashed_button.dart'; +import 'multi_image_picker_item.dart'; + +class MultiFilesPickerModel { + int id; + File file; + + MultiFilesPickerModel(this.id, this.file); +} + +class NewMultiFilesPicker extends StatefulWidget { + final String label; + final bool error; + final List files; + final bool enabled, onlyImages; + double? buttonHeight; + Widget? buttonIcon; + Color? buttonColor; + final Function(List)? onChange; + final bool showAsGrid; + + NewMultiFilesPicker( + {Key? key, + this.files = 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: [ + 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].file; + 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(MultiFilesPickerModel(0, File(path!))); + } + 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: EdgeInsets.all(0), + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: 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); + if (fileImage != null) { + widget.files.add(MultiFilesPickerModel(0, fileImage)); + if (widget.onChange != null) { + widget.onChange!(widget.files); + } + setState(() {}); + } + } + } + + Widget gridItem(IconData iconData, String title) { + return Container( + padding: EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Color(0xffF1F1F1), width: 1), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Icon(iconData, color: Color(0xff7D859A), size: 36), + Text( + title, + style: 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(MultiFilesPickerModel(0, fileImage)); + if (widget.onChange != null) { + widget.onChange!(widget.files); + } + setState(() {}); + } + } + + setState(() {}); + } +}