Merge branch 'refs/heads/main_production_ai_enhancement' into ui_ux_rollout_merge

# Conflicts:
#	lib/controllers/api_routes/urls.dart
#	lib/modules/cm_module/views/components/service_request_detail_view.dart
#	lib/views/widgets/dialogs/loading_dialog.dart
#	lib/views/widgets/equipment/asset_detail_page.dart
#	lib/views/widgets/equipment/asset_item_gridview.dart
#	lib/views/widgets/equipment/asset_item_listview.dart
#	lib/views/widgets/equipment/single_device_picker.dart
#	pubspec.yaml
ui_ux_rollout_merge
Sikander Saleem 2 weeks ago
commit 8d45b02614

@ -0,0 +1,13 @@
<svg width="130" height="150" viewBox="0 0 130 150" xmlns="http://www.w3.org/2000/svg">
<!-- front face -->
<rect x="30" y="46" width="65" height="48" rx="4" fill="none" stroke="#3D3D3A" stroke-width="2"/>
<!-- screen indicator -->
<rect x="40" y="56" width="26" height="16" rx="2" fill="none" stroke="#3D3D3A" stroke-width="1.2"/>
<!-- bottom face (highlighted) -->
<polygon points="30,94 50,114 115,114 95,94" fill="#F5C4B3" stroke="#993C1D" stroke-width="2"/>
<!-- right side face -->
<polygon points="95,46 115,26 115,74 95,94" fill="none" stroke="#3D3D3A" stroke-width="2"/>
<!-- arrow pointing up into bottom face -->
<line x1="73" y1="142" x2="73" y2="118" stroke="#3D3D3A" stroke-width="3" stroke-linecap="round"/>
<polygon points="73,112 66,124 80,124" fill="#3D3D3A"/>
</svg>

After

Width:  |  Height:  |  Size: 808 B

@ -0,0 +1,13 @@
<svg width="150" height="150" viewBox="0 0 150 150" xmlns="http://www.w3.org/2000/svg">
<!-- front face (highlighted, left side facing camera) -->
<rect x="32" y="52" width="56" height="58" rx="4" fill="#9FE1CB" stroke="#0F6E56" stroke-width="2"/>
<!-- screen indicator -->
<rect x="42" y="62" width="24" height="15" rx="2" fill="none" stroke="#0F6E56" stroke-width="1.2"/>
<!-- top face -->
<polygon points="32,52 52,32 118,32 88,52" fill="none" stroke="#3D3D3A" stroke-width="2"/>
<!-- right side face -->
<polygon points="88,52 118,32 118,90 88,110" fill="none" stroke="#3D3D3A" stroke-width="2"/>
<!-- arrow pointing right onto left face -->
<line x1="5" y1="81" x2="28" y2="81" stroke="#3D3D3A" stroke-width="3" stroke-linecap="round"/>
<polygon points="34,81 22,74 22,88" fill="#3D3D3A"/>
</svg>

After

Width:  |  Height:  |  Size: 824 B

@ -0,0 +1,13 @@
<svg width="150" height="150" viewBox="0 0 150 150" xmlns="http://www.w3.org/2000/svg">
<!-- front-left face -->
<polygon points="28,52 58,32 58,90 28,110" fill="none" stroke="#3D3D3A" stroke-width="2"/>
<!-- top face -->
<polygon points="28,52 58,32 95,32 65,52" fill="none" stroke="#3D3D3A" stroke-width="2"/>
<!-- right face highlighted, facing camera -->
<polygon points="65,52 95,32 95,90 65,110" fill="#FAC775" stroke="#854F0B" stroke-width="2"/>
<!-- screen indicator on front-left face for context -->
<rect x="34" y="60" width="16" height="14" rx="2" fill="none" stroke="#3D3D3A" stroke-width="1"/>
<!-- arrow pointing left onto right face -->
<line x1="145" y1="81" x2="122" y2="81" stroke="#3D3D3A" stroke-width="3" stroke-linecap="round"/>
<polygon points="116,81 128,74 128,88" fill="#3D3D3A"/>
</svg>

After

Width:  |  Height:  |  Size: 837 B

@ -0,0 +1,13 @@
<svg width="130" height="150" viewBox="0 0 130 150" xmlns="http://www.w3.org/2000/svg">
<!-- front face -->
<rect x="30" y="68" width="65" height="48" rx="4" fill="none" stroke="#3D3D3A" stroke-width="2"/>
<!-- screen indicator -->
<rect x="40" y="78" width="26" height="16" rx="2" fill="none" stroke="#3D3D3A" stroke-width="1.2"/>
<!-- top face (highlighted) -->
<polygon points="30,68 50,48 115,48 95,68" fill="#B5D4F4" stroke="#185FA5" stroke-width="2"/>
<!-- right side face -->
<polygon points="95,68 115,48 115,96 95,116" fill="none" stroke="#3D3D3A" stroke-width="2"/>
<!-- arrow pointing down onto top face -->
<line x1="73" y1="8" x2="73" y2="38" stroke="#3D3D3A" stroke-width="3" stroke-linecap="round"/>
<polygon points="73,44 66,32 80,32" fill="#3D3D3A"/>
</svg>

After

Width:  |  Height:  |  Size: 797 B

@ -337,10 +337,9 @@ class ApiManager {
}
Future<http.Response> multiPart(String url, {Map<String, String>? headers, required Map<String, String> body, required List<Future<MultipartFile>> files}) async {
Map<String, String> headers0 = const {
'Content-Type': 'multipart/form-data',
};
Map<String, String> headers0 = _headers;
headers0["Content-Type"] = 'multipart/form-data';
headers ??= {};
headers.addAll(headers0);

@ -66,6 +66,7 @@ class URLs {
static get getFaultDescription => "$_baseUrl/Asset/GetDefectForModelDefinition"; // get
static get getAssetById => "$_baseUrl/Asset/GetAssetById?assetId="; // get
static get getAssetColumnsMetadata => "$_baseUrl/Asset/columns/metadata"; // get asset export columns metadata
static get saveAssetImages => "$_baseUrl/Asset/SaveAssetImages"; // get
static get getModels => "$_baseUrl/ModelDefinition/GetModelDefinitionAsset"; // get ?client=2
static get getAllRequestsAndCount => "$_baseUrl/CallRequest/GetAllRequestsAndCount"; // get
static get updateUserContactInformation => "$_baseUrl/Account/UpdateContactInfo"; // get

@ -9,6 +9,7 @@ import 'package:test_sa/controllers/api_routes/http_status_manger.dart';
import 'package:test_sa/controllers/api_routes/urls.dart';
import 'package:test_sa/models/device/asset_by_id_model.dart';
import 'package:test_sa/models/device/asset_search.dart';
import 'package:test_sa/models/generic_attachment_model.dart';
import 'package:test_sa/models/lookup.dart';
import 'package:test_sa/models/user.dart';
import 'package:test_sa/modules/asset_delivery_module/models/technical_inpection_asset_model.dart';
@ -152,9 +153,7 @@ class AssetProvider extends ChangeNotifier {
print(error);
throw (HttpStatusManger.getStatusMessage(status: -1, subtitle: appLocalizations));
}
_stateCode = response.statusCode;
if (response.statusCode >= 200 && response.statusCode < 300) {
// client's request was successfully received
Map<String, dynamic> assetData = json.decode(response.body)["data"];
_assetById = AssetByIdModel.fromJson(assetData);
return _assetById!;
@ -230,4 +229,32 @@ class AssetProvider extends ChangeNotifier {
return [];
}
}
Future<bool> uploadAssetImages(int assetId, TrainingImageData trainingImageData) async {
late Response response;
try {
List<Future<MultipartFile>> files = [];
files.add(MultipartFile.fromPath("LeftImage", trainingImageData.leftImage!.name!));
files.add(MultipartFile.fromPath("RightImage", trainingImageData.rightImage!.name!));
files.add(MultipartFile.fromPath("UpImage", trainingImageData.upImage!.name!));
files.add(MultipartFile.fromPath("DownImage", trainingImageData.downImage!.name!));
for (var i = 0; i < (trainingImageData.additionalImages?.length ?? 0); i++) {
files.add(
MultipartFile.fromPath('AdditionalImage', trainingImageData.additionalImages![i].name!),
);
}
response = await ApiManager.instance.multiPart(URLs.saveAssetImages + "?AssetId=$assetId", body: {}, files: files);
if (response.statusCode >= 200 && response.statusCode < 300) {
// client's request was successfully received
// Map<String, dynamic> assetData = json.decode(response.body)["data"];
// _assetById = AssetByIdModel.fromJson(assetData);
// return _assetById!;
return true;
}
return false;
} catch (error) {
return false;
}
}
}

@ -1,3 +1,4 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:localization/localization.dart';
import 'package:provider/provider.dart';
@ -5,6 +6,7 @@ import 'package:test_sa/controllers/providers/api/user_provider.dart';
import 'package:test_sa/extensions/widget_extensions.dart';
import 'package:test_sa/new_views/swipe_module/dialoge/confirm_dialog.dart';
import 'package:test_sa/new_views/swipe_module/dialoge/info_dialog.dart';
import 'package:test_sa/views/full_screen_image_viewer.dart';
import '../controllers/providers/settings/setting_provider.dart';
import '../l10n/app_localizations.dart';
@ -24,6 +26,15 @@ extension BuildContextExtension on BuildContext {
UserProvider get userProvider => Provider.of<UserProvider>(this, listen: false);
void fullScreenImageViewer(Widget child) {
Navigator.of(this).push(
CupertinoPageRoute(
fullscreenDialog: true,
builder: (_) => FullScreenImageViewer(child),
),
);
}
bool isTablet() {
final mediaQueryData = MediaQuery.of(this);
final double screenWidth = mediaQueryData.size.width;
@ -49,6 +60,7 @@ extension BuildContextExtension on BuildContext {
isScrollControlled: true,
isDismissible: true,
backgroundColor: Colors.transparent,
builder: (context) => SingleChildScrollView(padding: const EdgeInsets.all(0), child: childWidget.bottomSafeArea).bottomSheetContainerNew(context, title: title,showCancelButton: showCancelButton),
builder: (context) =>
SingleChildScrollView(padding: const EdgeInsets.all(0), child: childWidget.bottomSafeArea).bottomSheetContainerNew(context, title: title, showCancelButton: showCancelButton),
);
}

@ -120,12 +120,13 @@ extension WidgetExtensions on Widget {
double padding = 12,
EdgeInsets? paddingObject,
EdgeInsets? margin,
double? width,
}) =>
withShadow
? Container(
padding: paddingObject ?? EdgeInsets.all(padding),
margin: margin,
width: double.infinity,
width: width ?? double.infinity,
decoration: ShapeDecoration(
color: backgroundColor ?? AppColor.background(context),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(borderRadius), side: BorderSide(color: borderColor ?? AppColor.borderColor(context))),

@ -13,7 +13,7 @@ import 'package:nfc_manager/nfc_manager_ios.dart' show MiFareIos, Iso15693Ios;
import 'package:shared_preferences/shared_preferences.dart';
import 'package:test_sa/new_views/common_widgets/app_lazy_loading.dart';
import 'package:test_sa/views/widgets/dialogs/confirm_dialog.dart';
import 'package:test_sa/views/widgets/dialogs/loading_dialog.dart';
import 'package:test_sa/views/widgets/dialogs/loading_with_text_dialog.dart';
// ignore_for_file: avoid_annotating_with_dynamic
@ -103,7 +103,7 @@ class Utils {
_isLoadingVisible = false;
}
static void showUploadingDialog(BuildContext context) {
static void showLoadingWithTextDialog(BuildContext context, {String? message}) {
WidgetsBinding.instance.addPostFrameCallback((_) {
_isLoadingVisible = true;
@ -111,7 +111,7 @@ class Utils {
context: context,
barrierColor: Colors.black.withOpacity(0.5),
useRootNavigator: false,
builder: (BuildContext context) => const UploadingDialog(),
builder: (BuildContext context) => LoadingWithTextDialog(message: message),
).then((value) {
_isLoadingVisible = false;
});

@ -65,6 +65,7 @@ class Asset {
this.comment,
this.tagCode,
this.assetPhoto,
this.hasTrainingImageData,
});
Asset.fromJson(dynamic json) {
@ -122,6 +123,7 @@ class Asset {
comment = json['comment'];
tagCode = json['tagCode'];
assetPhoto = json['assetPhoto'];
hasTrainingImageData = json['hasTrainingImageData'];
}
num? id; // Now nullable
@ -177,6 +179,7 @@ class Asset {
String? comment; // Now nullable
String? tagCode; // Now nullable
String? assetPhoto;
bool? hasTrainingImageData;
Asset copyWith({
num? id,
@ -232,6 +235,7 @@ class Asset {
String? comment,
String? tagCode,
String? assetPhoto,
bool? hasTrainingImageData,
}) =>
Asset(
id: id ?? this.id,
@ -287,6 +291,7 @@ class Asset {
comment: comment ?? this.comment,
tagCode: tagCode ?? this.tagCode,
assetPhoto: assetPhoto ?? this.assetPhoto,
hasTrainingImageData: hasTrainingImageData ?? this.hasTrainingImageData,
);
Map<String, dynamic> toJson() {
@ -381,6 +386,7 @@ class Asset {
map['comment'] = comment;
map['tagCode'] = tagCode;
map['assetPhoto'] = assetPhoto;
map['hasTrainingImageData'] = hasTrainingImageData;
return map;
}
}

@ -1,4 +1,5 @@
import 'package:test_sa/models/device/technical_guidance_book.dart';
import 'package:test_sa/models/generic_attachment_model.dart';
import 'package:test_sa/models/lookup.dart';
class AssetByIdModel {
@ -13,12 +14,12 @@ class AssetByIdModel {
AssetInfo? oldAsset;
AssetReplace? isParent;
AssetInfo? parentAsset;
AssetReplace? assetType;
Lookup? assetType;
DemoRequest? demoRequest;
String? missionCritical;
Site? site;
AssetReplace? building;
AssetReplace? floor;
Lookup? building;
Lookup? floor;
Department? department;
Lookup? room;
num? testsDay;
@ -54,14 +55,17 @@ class AssetByIdModel {
String? comment;
bool? isEnabled;
String? tagCode;
List<String>? assetAttachments;
List<AssetAttachment>? assetAttachments;
String? retirementTypeName;
String? retirementStatusName;
String? retirementDate;
AssetGroup? assetGroup;
String? assetPhoto;
String? modelPhoto;
String? nextPMDate;
String? lastPMDate;
bool? hasTrainingImageData;
TrainingImageData? trainingImageData;
AssetByIdModel(
{this.multiAssets,
@ -122,10 +126,15 @@ class AssetByIdModel {
this.retirementDate,
this.assetGroup,
this.assetPhoto,
this.modelPhoto,
this.nextPMDate,
this.lastPMDate});
this.lastPMDate,
this.trainingImageData,
this.hasTrainingImageData});
AssetByIdModel.fromJson(Map<String, dynamic> json) {
print("assetModel!.fromJson:${json['hasTrainingImageData']}");
if (json['multiAssets'] != null) {
multiAssets = <MultiAssets>[];
json['multiAssets'].forEach((v) {
@ -142,12 +151,12 @@ class AssetByIdModel {
oldAsset = json['oldAsset'] != null ? AssetInfo.fromJson(json['oldAsset']) : null;
isParent = json['isParent'] != null ? AssetReplace.fromJson(json['isParent']) : null;
parentAsset = json['parentAsset'] != null ? AssetInfo.fromJson(json['parentAsset']) : null;
assetType = json['assetType'] != null ? AssetReplace.fromJson(json['assetType']) : null;
assetType = json['assetType'] != null ? Lookup.fromJson(json['assetType']) : null;
demoRequest = json['demoRequest'] != null ? DemoRequest.fromJson(json['demoRequest']) : null;
missionCritical = json['missionCritical'];
site = json['site'] != null ? Site.fromJson(json['site']) : null;
building = json['building'] != null ? AssetReplace.fromJson(json['building']) : null;
floor = json['floor'] != null ? AssetReplace.fromJson(json['floor']) : null;
building = json['building'] != null ? Lookup.fromJson(json['building']) : null;
floor = json['floor'] != null ? Lookup.fromJson(json['floor']) : null;
department = json['department'] != null ? Department.fromJson(json['department']) : null;
room = json['room'] != null ? Lookup.fromJson(json['room']) : null;
testsDay = json['testsDay'];
@ -189,9 +198,9 @@ class AssetByIdModel {
isEnabled = json['isEnabled'];
tagCode = json['tagCode'];
if (json['assetAttachments'] != null) {
assetAttachments = <String>[];
assetAttachments = <AssetAttachment>[];
json['assetAttachments'].forEach((v) {
assetAttachments!.add(v);
assetAttachments!.add(AssetAttachment.fromJson(v));
});
}
retirementTypeName = json['retirementTypeName'];
@ -199,8 +208,11 @@ class AssetByIdModel {
retirementDate = json['retirementDate'];
assetGroup = json['assetGroup'] != null ? AssetGroup.fromJson(json['assetGroup']) : null;
assetPhoto = json['assetPhoto'];
modelPhoto = json['modelPhoto'];
nextPMDate = json['nextPMDate'];
lastPMDate = json['lastPMDate'];
hasTrainingImageData = json['hasTrainingImageData'];
trainingImageData = json['trainingImageData'] != null ? TrainingImageData.fromJson(json['trainingImageData']) : null;
}
Map<String, dynamic> toJson() {
@ -295,8 +307,11 @@ class AssetByIdModel {
data['assetGroup'] = assetGroup?.toJson();
}
data['assetPhoto'] = assetPhoto;
data['modelPhoto'] = modelPhoto;
data['nextPMDate'] = nextPMDate;
data['lastPMDate'] = lastPMDate;
data['hasTrainingImageData'] = hasTrainingImageData;
data['trainingImageData'] = trainingImageData?.toJson();
return data;
}
}
@ -323,6 +338,31 @@ class MultiAssets {
}
}
class AssetAttachment {
int? id;
String? attachmentName;
String? attachmentURL;
String? originalName;
AssetAttachment({this.id, this.attachmentName, this.attachmentURL, this.originalName});
AssetAttachment.fromJson(Map<String, dynamic> json) {
id = json['id'];
attachmentName = json['attachmentName'];
attachmentURL = json['attachmentURL'];
originalName = json['originalName'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['id'] = id;
data['attachmentName'] = attachmentName;
data['attachmentURL'] = attachmentURL;
data['originalName'] = originalName;
return data;
}
}
class ModelDefinition {
int? id;
String? assetName;
@ -546,18 +586,50 @@ class Floors {
class Departments {
int? id;
String? name;
List<Rooms>? rooms;
Departments({this.id, this.name});
Departments.fromJson(Map<String, dynamic> json) {
id = json['id'];
name = json['name'];
if (json['departments'] != null) {
rooms = <Rooms>[];
json['rooms'].forEach((v) {
rooms!.add(Rooms.fromJson(v));
});
}
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['id'] = id;
data['name'] = name;
if (rooms != null) {
data['rooms'] = rooms!.map((v) => v.toJson()).toList();
}
return data;
}
}
class Rooms {
int? id;
String? name;
int? value;
Rooms({this.id, this.name, this.value});
Rooms.fromJson(Map<String, dynamic> json) {
id = json['id'];
name = json['name'];
value = json['value'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['id'] = id;
data['name'] = name;
data['value'] = value;
return data;
}
}
@ -567,14 +639,16 @@ class Department {
String? departmentName;
String? departmentCode;
String? ntCode;
num? operationalHours;
Department({this.id, this.departmentName, this.departmentCode, this.ntCode});
Department({this.id, this.departmentName, this.departmentCode, this.ntCode, this.operationalHours});
Department.fromJson(Map<String, dynamic> json) {
id = json['id'];
departmentName = json['departmentName'];
departmentCode = json['departmentCode'];
ntCode = json['ntCode'];
operationalHours = json['operationalHours'];
}
Map<String, dynamic> toJson() {
@ -583,6 +657,7 @@ class Department {
data['departmentName'] = departmentName;
data['departmentCode'] = departmentCode;
data['ntCode'] = ntCode;
data['operationalHours'] = operationalHours;
return data;
}
}
@ -725,3 +800,37 @@ class DemoRequest {
return data;
}
}
class TrainingImageData {
GenericAttachmentModel? leftImage;
GenericAttachmentModel? rightImage;
GenericAttachmentModel? upImage;
GenericAttachmentModel? downImage;
List<GenericAttachmentModel>? additionalImages;
TrainingImageData({this.leftImage, this.rightImage, this.upImage, this.downImage, this.additionalImages = const []});
TrainingImageData.fromJson(Map<String, dynamic> json) {
leftImage = json['leftImage'] == null ? null : GenericAttachmentModel.fromJson(json['leftImage'], convertToLink: true);
rightImage = json['rightImage'] == null ? null : GenericAttachmentModel.fromJson(json['rightImage'], convertToLink: true);
upImage = json['upImage'] == null ? null : GenericAttachmentModel.fromJson(json['upImage'], convertToLink: true);
downImage = json['downImage'] == null ? null : GenericAttachmentModel.fromJson(json['downImage'], convertToLink: true);
additionalImages = [];
if (json['additionalImages'] != null) {
json['additionalImages'].forEach((v) {
additionalImages!.add(GenericAttachmentModel.fromJson(v, convertToLink: true));
});
}
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['leftImage'] = leftImage?.toJson();
data['rightImage'] = rightImage?.toJson();
data['upImage'] = upImage?.toJson();
data['downImage'] = downImage?.toJson();
data['additionalImages'] = additionalImages?.map((v) => v.toJson()).toList() ?? [];
return data;
}
}

@ -1,9 +1,10 @@
import 'dart:io';
import 'package:test_sa/controllers/api_routes/urls.dart';
import 'package:test_sa/models/lookup.dart';
class GenericAttachmentModel {
GenericAttachmentModel({this.id, this.name, this.originalName, this.createdBy,this.documentTypeId});
GenericAttachmentModel({this.id, this.name, this.originalName, this.createdBy, this.documentTypeId});
int? id;
String? name;
@ -11,11 +12,14 @@ class GenericAttachmentModel {
String? originalName;
Lookup? documentTypeId;
GenericAttachmentModel.fromJson(Map<String, dynamic> json) {
GenericAttachmentModel.fromJson(Map<String, dynamic> json, {bool convertToLink = false}) {
id = json['id'];
name = json['name'];
name = json['name'] ?? json['imageName'];
createdBy = json['createdBy'];
originalName = json['originalName'];
originalName = json['originalName'] ?? json['imageOriginalName'];
if (convertToLink) {
name = URLs.getFileUrl(name);
}
}
Map<String, dynamic> toJson() {

@ -304,17 +304,20 @@ class WorkOrderAsset {
required this.id,
required this.assetNumber,
required this.assetSerialNo,
required this.hasTrainingImageData,
});
int? id;
String? assetNumber;
String? assetSerialNo;
bool? hasTrainingImageData;
factory WorkOrderAsset.fromJson(Map<String, dynamic> json) {
return WorkOrderAsset(
id: json["id"],
assetNumber: json["assetNumber"],
assetSerialNo: json["assetSerialNo"],
hasTrainingImageData: json["hasTrainingImageData"],
);
}
@ -322,6 +325,7 @@ class WorkOrderAsset {
"id": id,
"assetNumber": assetNumber,
"assetSerialNo": assetSerialNo,
"hasTrainingImageData": hasTrainingImageData,
};
}

@ -18,6 +18,7 @@ import 'package:test_sa/modules/cm_module/views/forms/asset_retired/asset_retire
import 'package:test_sa/modules/cm_module/views/forms/cost/cost_detail_form_screen.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/views/widgets/equipment/asset_detail_page.dart';
import 'package:test_sa/views/widgets/images/files_list.dart';
import 'package:test_sa/views/widgets/images/multi_image_picker.dart';
import 'package:test_sa/views/widgets/item_views/info_date_widget.dart';
@ -141,7 +142,7 @@ class _ServiceRequestDetailViewState extends State<ServiceRequestDetailView> {
// spacing: 8,
runSpacing: 8,
children: [
if (Provider.of<SettingProvider>(context, listen: false).showPriority&&workOrder.priority!=null) ...[
if (Provider.of<SettingProvider>(context, listen: false).showPriority && workOrder.priority != null) ...[
StatusLabel(
label: workOrder.priority?.name,
id: workOrder.priority?.value,
@ -192,7 +193,24 @@ class _ServiceRequestDetailViewState extends State<ServiceRequestDetailView> {
],
),
8.height,
InfoHeader16Widget(context.translation.assetInformation),
Row(
spacing: 4,
children: [
Flexible(
child: InfoHeader16Widget(context.translation.assetInformation),
),
const Icon(Icons.open_in_new_rounded, color: AppColor.primary10, size: 16).onPress(() {
Navigator.of(context).pushNamed(AssetDetailPage.id, arguments: workOrder.asset!.id);
})
],
),
if (context.userProvider.isEngineer && !(workOrder.asset!.hasTrainingImageData ?? true)) ...[
Text(
'*Images are required for the asset',
style: AppTextStyles.bodyText.copyWith(color: AppColor.red30),
),
4.height,
],
InfoTextWidget(label: context.translation.assetName, value: workOrder.assetNdModel!.name?.cleanupWhitespace.capitalizeFirstOfEach),
InfoTextWidget(label: context.translation.assetNumber, value: workOrder.asset!.assetNumber),
InfoTextWidget(label: context.translation.serialNo, value: workOrder.asset!.assetSerialNo),

@ -388,7 +388,7 @@ class _ChatPageState extends State<ChatPage> {
clipBehavior: Clip.antiAliasWithSaveLayer,
builder: (BuildContext context) => ChatFilePicker())) as File?;
if (file != null) {
Utils.showUploadingDialog(context);
Utils.showLoadingWithTextDialog(context);
await chatProvider.uploadAttachments(context.userProvider.user!.username!, file, "1");
Utils.hideLoading(context);
}
@ -638,7 +638,7 @@ class _ChatPageState extends State<ChatPage> {
hoverColor: Colors.transparent,
onPressed: () async {
if (chatState == ChatState.voiceRecordingCompleted) {
Utils.showUploadingDialog(context);
Utils.showLoadingWithTextDialog(context);
try {
await chatProvider.uploadAttachments(context.userProvider.user!.username!, File(recordedFilePath!), "1");
Utils.hideLoading(context);

@ -51,7 +51,6 @@ class _DeviceSearchScreenState extends State<DeviceSearchScreen> {
_modelController.dispose();
_supplierController.dispose();
_siteController.dispose();
_siteController.dispose();
_manufactureController.dispose();
_mdController.dispose();
super.dispose();

@ -0,0 +1,20 @@
import 'package:flutter/material.dart';
import 'package:test_sa/extensions/widget_extensions.dart';
class FullScreenImageViewer extends StatelessWidget {
final Widget child;
const FullScreenImageViewer(this.child, {Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
InteractiveViewer(clipBehavior: Clip.none, panEnabled: true, minScale: 0.5, maxScale: 4.0, child: child).center,
Positioned(top: MediaQuery.of(context).padding.top, left: 12.0, child: const Icon(Icons.clear).toShadowContainer(context, width: 40, padding: 8).onPress(() => Navigator.maybePop(context))),
],
),
);
}
}

@ -0,0 +1,41 @@
import 'package:flutter/material.dart';
import 'package:test_sa/extensions/int_extensions.dart';
import 'package:test_sa/new_views/app_style/app_color.dart';
import 'package:test_sa/views/app_style/sizing.dart';
class LoadingWithTextDialog extends StatelessWidget {
final String? message;
const LoadingWithTextDialog({Key? key, this.message}) : super(key: key);
@override
Widget build(BuildContext context) {
return Center(
child: Material(
borderRadius: BorderRadius.circular(20.0),
child: Container(
height: 150.toScreenWidth,
width: 200.toScreenWidth,
alignment: Alignment.center,
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: AppColor.neutral30,
borderRadius: BorderRadius.circular(20.0),
boxShadow: [AppStyle.boxShadow],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const CircularProgressIndicator(strokeWidth: 3),
16.height,
Text(
message ?? "Uploading...",
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500, color: AppColor.textColor(context), height: 35 / 24, letterSpacing: -0.96),
)
],
),
),
),
);
}
}

@ -7,10 +7,17 @@ import 'package:test_sa/extensions/int_extensions.dart';
import 'package:test_sa/extensions/string_extensions.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/views/components/action_button/footer_action_button.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/widgets/item_views/info_header_widget.dart';
import 'package:test_sa/views/widgets/item_views/info_text_widget.dart';
import 'package:test_sa/views/widgets/equipment/asset_image_upload_page.dart';
import 'package:test_sa/views/widgets/images/files_list.dart';
import 'package:test_sa/views/widgets/images/multi_image_picker.dart';
import 'package:test_sa/views/widgets/loaders/app_loading.dart';
import 'package:test_sa/views/widgets/loaders/failed_loading.dart';
@ -48,6 +55,11 @@ class _AssetDetailPageState extends State<AssetDetailPage> with SingleTickerProv
super.dispose();
}
int? assetId;
AssetProvider? _assetProvider;
AssetByIdModel? assetModel;
List<GenericAttachmentModel> attachments = [];
@override
Widget build(BuildContext context) {
@ -57,7 +69,7 @@ class _AssetDetailPageState extends State<AssetDetailPage> with SingleTickerProv
return Scaffold(
appBar: DefaultAppBar(title: context.translation.assetDetails),
body: FutureBuilder<AssetByIdModel>(
future: _assetProvider!.getAssetById(assetId!, context.translation),
future: assetModel == null ? _assetProvider!.getAssetById(assetId!, context.translation) : null,
builder: (BuildContext context, AsyncSnapshot<AssetByIdModel> snapshot) {
if (snapshot.hasError) {
return FailedLoading(
@ -72,6 +84,14 @@ class _AssetDetailPageState extends State<AssetDetailPage> with SingleTickerProv
} else if (snapshot.hasData) {}
if (snapshot.hasData) {
AssetByIdModel assetModel = snapshot.data!;
attachments = assetModel?.assetAttachments
?.map((attachment) => GenericAttachmentModel(
id: attachment.id,
name: attachment.attachmentName,
originalName: attachment.originalName,
))
.toList() ??
[];
// Start animation when data is loaded
_animationController.forward(from: 0.0);
@ -239,6 +259,14 @@ class _AssetDetailPageState extends State<AssetDetailPage> with SingleTickerProv
),
8.height,
if (context.userProvider.isEngineer && !(assetModel!.hasTrainingImageData ?? true)) ...[
Text(
'*Upload Images for this asset',
style: AppTextStyles.bodyText2.copyWith(color: AppColor.red30),
),
4.height,
],
// Details row with animation
FadeTransition(
opacity: detailsAnimation,
@ -313,9 +341,19 @@ class _AssetDetailPageState extends State<AssetDetailPage> with SingleTickerProv
),
]
],
)
),
FilesList(images: attachments.map((toElement) => URLs.getFileUrl(toElement.name!) ?? '').toList()),
],
).toShadowContainer(context),
if (context.userProvider.isEngineer && !(assetModel!.hasTrainingImageData ?? true))
FooterActionButton.footerContainer(
context: context,
child: AppFilledButton(
buttonColor: AppColor.primary10,
label: "Upload Images",
onPressed: _upload,
),
),
);
}
return const Center(child: ALoading());
@ -323,4 +361,12 @@ class _AssetDetailPageState extends State<AssetDetailPage> with SingleTickerProv
),
);
}
void _upload() async {
bool reload = await Navigator.push(context, MaterialPageRoute(builder: (context) => AssetImageUploadPage(assetId: assetId!))) ?? false;
if (reload) {
assetModel = null;
setState(() {});
}
}
}

@ -0,0 +1,250 @@
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);
},
);
}
}

@ -13,7 +13,7 @@ class AssetItemGridView extends StatelessWidget {
final Asset device;
final Function(Asset) onPressed;
const AssetItemGridView({Key? key,required this.device,required this.onPressed}) : super(key: key);
const AssetItemGridView({Key? key, required this.device, required this.onPressed}) : super(key: key);
@override
Widget build(BuildContext context) {
@ -44,6 +44,13 @@ class AssetItemGridView extends StatelessWidget {
overflow: TextOverflow.ellipsis,
style: AppTextStyles.bodyText.copyWith(color: AppColor.textStyleColor(context)),
),
if (context.userProvider.isEngineer && !(device.hasTrainingImageData ?? true)) ...[
2.height,
Text(
'*Upload Images for this asset',
style: AppTextStyles.tinyFont2.copyWith(color: AppColor.red30),
),
],
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,

@ -44,6 +44,13 @@ class AssetItemListView extends StatelessWidget {
children: [
InfoHeaderWidget(device.modelDefinition!.assetName!.cleanupWhitespace),
// device.modelDefinition!.assetName!.cleanupWhitespace.capitalizeFirstOfEach.heading6(context),
if (context.userProvider.isEngineer && !(device.hasTrainingImageData ?? true)) ...[
2.height,
Text(
'*Upload Images for this asset',
style: AppTextStyles.tinyFont2.copyWith(color: AppColor.red30),
),
],
4.height,
InfoTextWidget(label: context.translation.assetNumber, value: device.assetNumber),
InfoTextWidget(label: context.translation.modelName, value: device.modelDefinition!.modelName),

@ -3,11 +3,13 @@ import 'dart:io';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:image_blur_detection/image_blur_detection.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/helper/utils.dart';
import 'package:test_sa/models/generic_attachment_model.dart';
import 'package:test_sa/models/lookup.dart';
import 'package:test_sa/new_views/app_style/app_color.dart';
@ -322,7 +324,10 @@ class AttachmentPicker extends StatefulWidget {
final Function(List<GenericAttachmentModel>)? onChange;
final bool showAsGrid;
final bool showAsListView;
final bool pickMultiple;
Lookup? documentType;
Widget? child;
bool checkQuality;
AttachmentPicker(
{Key? key,
@ -337,6 +342,9 @@ class AttachmentPicker extends StatefulWidget {
this.documentType,
this.showAsGrid = false,
this.showAsListView = false,
this.pickMultiple = false,
this.checkQuality = false,
this.child,
this.buttonColor})
: super(key: key);
@ -347,72 +355,78 @@ class AttachmentPicker extends StatefulWidget {
class _AttachmentPickerState extends State<AttachmentPicker> {
@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)
widget.showAsListView
? ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
padding: EdgeInsets.zero,
itemBuilder: (cxt, index) {
File image = File(widget.attachment[index].name!);
return MultiFilesPickerItem(
file: image,
showAsListView: widget.showAsListView,
enabled: widget.enabled,
documentType: widget.attachment[index].documentTypeId?.name,
onRemoveTap: (image) {
if (!widget.enabled) {
return;
}
widget.attachment.removeAt(index);
if (widget.onChange != null) {
widget.onChange!(widget.attachment);
}
setState(() {});
},
);
},
separatorBuilder: (cxt, index) => 8.height,
itemCount: widget.attachment.length)
: 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(() {});
return widget.child != null
? widget.child!.onPress((widget.enabled == false)
? () {}
: widget.showAsGrid
? () => showFileSourceSheet()
: () => onFilePicker(widget.checkQuality))
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 12,
children: [
AppDashedButton(
title: widget.label,
height: widget.buttonHeight,
buttonColor: widget.buttonColor,
icon: widget.buttonIcon,
onPressed: (widget.enabled == false)
? () {}
: widget.showAsGrid
? () => showFileSourceSheet()
: () => onFilePicker(widget.checkQuality)),
if (widget.attachment.isNotEmpty)
widget.showAsListView
? ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
padding: const EdgeInsets.only(top: 0),
itemBuilder: (cxt, index) {
File image = File(widget.attachment[index].name!);
return MultiFilesPickerItem(
file: image,
showAsListView: widget.showAsListView,
enabled: widget.enabled,
documentType: widget.attachment[index].documentTypeId?.name,
onRemoveTap: (image) {
if (!widget.enabled) {
return;
}
widget.attachment.removeAt(index);
if (widget.onChange != null) {
widget.onChange!(widget.attachment);
}
setState(() {});
},
);
},
);
},
),
),
],
);
separatorBuilder: (cxt, index) => 8.height,
itemCount: widget.attachment.length)
: 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(() {});
},
);
},
),
).paddingOnly(top: 0),
],
);
}
fromFilePicker() async {
@ -466,16 +480,21 @@ class _AttachmentPickerState extends State<AttachmentPicker> {
).paddingAll(21),
)) as ImageSource;
final pickedFile = await ImagePicker().pickImage(source: source, imageQuality: 70, maxWidth: 800, maxHeight: 800);
// if(widget.pickMultiple) {
final pickedFile12 = await ImagePicker().pickMultiImage(imageQuality: 70, maxWidth: 800, maxHeight: 800);
if (pickedFile != null) {
File fileImage = File(pickedFile.path);
widget.attachment.add(GenericAttachmentModel(id: 0, name: fileImage.path, documentTypeId: widget.documentType));
if (widget.onChange != null) {
widget.onChange!(widget.attachment);
}
setState(() {});
}
// // }
//
// 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, documentTypeId: widget.documentType));
// if (widget.onChange != null) {
// widget.onChange!(widget.attachment);
// }
// setState(() {});
// }
}
Widget gridItem(IconData iconData, String title) {
@ -501,7 +520,7 @@ class _AttachmentPickerState extends State<AttachmentPicker> {
);
}
onFilePicker() async {
onFilePicker(bool checkQuality) async {
ImageSource? source = await showModalBottomSheet<ImageSource>(
context: context,
builder: (BuildContext context) {
@ -629,10 +648,37 @@ class _AttachmentPickerState extends State<AttachmentPicker> {
// );
if (source == null) return;
final pickedFile = await ImagePicker().pickImage(source: source, imageQuality: 70, maxWidth: 800, maxHeight: 800);
if (widget.pickMultiple && source == ImageSource.gallery) {
final pickedFiles = await ImagePicker().pickMultiImage(imageQuality: 70);
if (pickedFiles.isNotEmpty) {
pickedFiles.forEach((pickedFile) {
File fileImage = File(pickedFile.path);
widget.attachment.add(GenericAttachmentModel(id: 0, name: fileImage.path, documentTypeId: widget.documentType));
});
if (widget.onChange != null) {
widget.onChange!(widget.attachment);
}
setState(() {});
return;
}
}
final pickedFile = await ImagePicker().pickImage(source: source, imageQuality: 50);
if (pickedFile != null) {
File fileImage = File(pickedFile.path);
if (checkQuality) {
ImageQualityValidator imageQualityValidator = ImageQualityValidator();
final result = await imageQualityValidator.validate(await fileImage.readAsBytes());
if (!result.isValid) {
String issues = "Your image have following problems:";
result.issues.forEach((issue) => (issues += "\n- $issue"));
context.showInfoDialog(issues, title: "Bad Quality Found");
return;
}
}
widget.attachment.add(GenericAttachmentModel(id: 0, name: fileImage.path, documentTypeId: widget.documentType));
if (widget.onChange != null) {
widget.onChange!(widget.attachment);

@ -88,6 +88,7 @@ dependencies:
flutter_timezone: ^3.0.1
device_calendar: ^4.3.3
pie_chart: ^5.4.0
image_blur_detection: ^1.0.1
badges: ^3.1.1
# buttons_tabbar: ^1.1.2
@ -139,6 +140,7 @@ flutter:
- assets/images/
- assets/images/dashboard/
- assets/images/wo/
- assets/images/asset_image_upload/
- assets/lottie/
- assets/subtitles/
- assets/rives/

Loading…
Cancel
Save