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 < AssetImageUploadPage > {
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: < Widget > [
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 < AssetProvider > ( 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 ) ;
} ,
) ;
}
}