import 'dart:convert'; import 'dart:io'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:test_sa/controllers/providers/api/devices_provider.dart'; import 'package:test_sa/extensions/context_extension.dart'; import 'package:test_sa/extensions/text_extensions.dart'; import 'package:test_sa/extensions/widget_extensions.dart'; import 'package:test_sa/helper/utils.dart'; import 'package:test_sa/models/device/asset_by_id_model.dart'; import 'package:test_sa/models/generic_attachment_model.dart'; import 'package:test_sa/modules/cm_module/cm_request_utils.dart'; import 'package:test_sa/modules/cm_module/views/components/action_button/footer_action_button.dart'; import 'package:test_sa/new_views/app_style/app_color.dart'; import 'package:test_sa/new_views/common_widgets/app_filled_button.dart'; import 'package:test_sa/new_views/common_widgets/default_app_bar.dart'; import 'package:test_sa/views/full_screen_image_viewer.dart'; import 'package:test_sa/views/widgets/dialogs/dialog.dart'; import 'package:test_sa/views/widgets/images/multi_image_picker.dart'; class AssetImageUploadPage extends StatefulWidget { int assetId; TrainingImageData? attachments; AssetImageUploadPage({Key? key, required this.assetId, this.attachments}) : super(key: key); @override _AssetImageUploadPageState createState() { return _AssetImageUploadPageState(); } } class _AssetImageUploadPageState extends State { late TrainingImageData trainingImageData; @override void initState() { super.initState(); trainingImageData = widget.attachments ?? TrainingImageData(additionalImages: []); } @override void dispose() { super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: const DefaultAppBar(title: "Upload Images"), body: Column( children: [ ListView( padding: const EdgeInsets.all(16), children: [ Text( "Instructions", style: AppTextStyles.heading5.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), ), Text( '* Please upload all images of the sides of the asset as mentioned below.\n* Images should be clear and high quality.\n* Avoid uploading blurry, low-light or low-resolution images.', style: AppTextStyles.bodyText2.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), ), GridView( padding: const EdgeInsets.only(top: 16, bottom: 16), shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: (MediaQuery.of(context).size.width < 550) ? 4 : 4, childAspectRatio: 1, crossAxisSpacing: 12, mainAxisSpacing: 12), children: [ gridCard( icon: "asset_image_upload/side_right", title: "Right Side", attachment: trainingImageData.rightImage, onUpdate: (value) { trainingImageData.rightImage = value; setState(() {}); }, ), gridCard( icon: "asset_image_upload/side_left", title: "Left Side", attachment: trainingImageData.leftImage, onUpdate: (value) { trainingImageData.leftImage = value; setState(() {}); }, ), gridCard( icon: "asset_image_upload/side_top", title: "Top Side", attachment: trainingImageData.upImage, onUpdate: (value) { trainingImageData.upImage = value; setState(() {}); }, ), gridCard( icon: "asset_image_upload/side_bottom", title: "Bottom Side", attachment: trainingImageData.downImage, onUpdate: (value) { trainingImageData.downImage = value; setState(() {}); }, ), ], ), AttachmentPicker( label: "Additional Images", attachment: trainingImageData.additionalImages!, buttonColor: AppColor.black10, onlyImages: true, pickMultiple: true, buttonIcon: 'image-plus'.toSvgAsset(color: context.isDark ? AppColor.primary10 : AppColor.neutral120), onChange: (_attachments) { trainingImageData.additionalImages = _attachments; setState(() {}); }, ).toShadowContainer(context), ], ).expanded, FooterActionButton.footerContainer( context: context, child: AppFilledButton( buttonColor: AppColor.primary10, disableButton: trainingImageData.leftImage == null || trainingImageData.rightImage == null || trainingImageData.upImage == null || trainingImageData.downImage == null, label: "Upload", onPressed: () async { bool result = await showDialog( context: context, builder: (_) => AAlertDialog( title: context.translation.confirm, content: "I followed all the instructions and uploading the images in high quality, clear and not blurry.", ), ); if (result) { Utils.showLoadingWithTextDialog(context); result = await Provider.of(context, listen: false).uploadAssetImages(widget.assetId, trainingImageData); Utils.hideLoading(context); if (result) { Navigator.pop(context, result); } } }, ), ), ], )); } Widget gridCard({ required String icon, required String title, required GenericAttachmentModel? attachment, required Function(GenericAttachmentModel?) onUpdate, }) { return Stack( alignment: AlignmentDirectional.topEnd, children: [ Positioned.fill( child: AttachmentPicker( label: "", onlyImages: true, checkQuality: true, enabled: attachment == null, attachment: attachment == null ? [] : [attachment], buttonIcon: 'image-plus'.toSvgAsset(color: context.isDark ? AppColor.primary10 : AppColor.neutral120), onChange: (_attachments) { if (_attachments.isNotEmpty) { onUpdate(_attachments.first); } else { onUpdate(null); } }, child: imageAngleCard(icon, title, attachment), ).toShadowContainer(context, padding: 0), ), if (attachment != null) Positioned( top: 4, right: 4, child: IconButton( padding: EdgeInsets.zero, icon: const Icon(Icons.clear), iconSize: 18, style: IconButton.styleFrom(elevation: 2, tapTargetSize: MaterialTapTargetSize.shrinkWrap, backgroundColor: AppColor.red30, foregroundColor: Colors.white), visualDensity: VisualDensity.compact, constraints: const BoxConstraints(), onPressed: () { onUpdate(null); }, )), ], ); } Widget imageAngleCard(String icon, String title, GenericAttachmentModel? attachment) { if (attachment != null) { Widget imageWidget = _getImageWidget(attachment.name!); return ClipRRect(borderRadius: BorderRadius.circular(12), child: imageWidget.onPress(() => context.fullScreenImageViewer(imageWidget))); } return Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ icon.toSvgAsset(height: 50, width: 50), Text( title, style: AppTextStyles.bodyText2.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), ), ], ); } Widget _getImageWidget(String imageUrl) { // Check if it's base64 encoded (contains | separator) if (imageUrl.contains('|')) { try { final base64Data = imageUrl.split('|').last; return Image.memory( base64Decode(base64Data), fit: BoxFit.cover, ); } catch (e) { return const Icon(Icons.broken_image, size: 48); } } // Check if it's a local file path if (CMRequestUtils.isLocalUrl(imageUrl)) { return Image.file( File(imageUrl), fit: BoxFit.cover, ); } // Otherwise treat as network URL return Image.network( imageUrl, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) { return const Icon(Icons.broken_image, size: 48); }, ); } }