You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
251 lines
9.5 KiB
Dart
251 lines
9.5 KiB
Dart
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.showUploadingDialog(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);
|
|
},
|
|
);
|
|
}
|
|
}
|