From df2c7af28146c4e758421aac556691dbe456e4c0 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Thu, 9 Jul 2026 11:38:38 +0300 Subject: [PATCH 01/11] asset model asset attachment changed. --- lib/models/device/asset_by_id_model.dart | 31 +++++++++++++++++-- .../device_transfer/device_search_screen.dart | 1 - 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/lib/models/device/asset_by_id_model.dart b/lib/models/device/asset_by_id_model.dart index c631b767..b7b0e759 100644 --- a/lib/models/device/asset_by_id_model.dart +++ b/lib/models/device/asset_by_id_model.dart @@ -54,7 +54,7 @@ class AssetByIdModel { String? comment; bool? isEnabled; String? tagCode; - List? assetAttachments; + List? assetAttachments; String? retirementTypeName; String? retirementStatusName; String? retirementDate; @@ -189,9 +189,9 @@ class AssetByIdModel { isEnabled = json['isEnabled']; tagCode = json['tagCode']; if (json['assetAttachments'] != null) { - assetAttachments = []; + assetAttachments = []; json['assetAttachments'].forEach((v) { - assetAttachments!.add(v); + assetAttachments!.add(AssetAttachment.fromJson(v)); }); } retirementTypeName = json['retirementTypeName']; @@ -323,6 +323,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 json) { + id = json['id']; + attachmentName = json['attachmentName']; + attachmentURL = json['attachmentURL']; + originalName = json['originalName']; + } + + Map toJson() { + final Map data = {}; + data['id'] = id; + data['attachmentName'] = attachmentName; + data['attachmentURL'] = attachmentURL; + data['originalName'] = originalName; + return data; + } +} + class ModelDefinition { int? id; String? assetName; diff --git a/lib/modules/tm_module/device_transfer/device_search_screen.dart b/lib/modules/tm_module/device_transfer/device_search_screen.dart index 56768834..48c96eae 100644 --- a/lib/modules/tm_module/device_transfer/device_search_screen.dart +++ b/lib/modules/tm_module/device_transfer/device_search_screen.dart @@ -49,7 +49,6 @@ class _DeviceSearchScreenState extends State { _modelController.dispose(); _supplierController.dispose(); _siteController.dispose(); - _siteController.dispose(); _manufactureController.dispose(); _mdController.dispose(); super.dispose(); From 38f5f506e6ac109e53d56d94eeaa9727d1788fbf Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Thu, 9 Jul 2026 12:12:57 +0300 Subject: [PATCH 02/11] asset model updated. --- lib/models/device/asset_by_id_model.dart | 53 ++++++++++++++++++++---- 1 file changed, 46 insertions(+), 7 deletions(-) diff --git a/lib/models/device/asset_by_id_model.dart b/lib/models/device/asset_by_id_model.dart index b7b0e759..b4ac22ef 100644 --- a/lib/models/device/asset_by_id_model.dart +++ b/lib/models/device/asset_by_id_model.dart @@ -13,12 +13,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; @@ -60,6 +60,7 @@ class AssetByIdModel { String? retirementDate; AssetGroup? assetGroup; String? assetPhoto; + String? modelPhoto; String? nextPMDate; String? lastPMDate; @@ -122,6 +123,7 @@ class AssetByIdModel { this.retirementDate, this.assetGroup, this.assetPhoto, + this.modelPhoto, this.nextPMDate, this.lastPMDate}); @@ -142,12 +144,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']; @@ -199,6 +201,7 @@ 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']; } @@ -295,6 +298,7 @@ class AssetByIdModel { data['assetGroup'] = assetGroup?.toJson(); } data['assetPhoto'] = assetPhoto; + data['modelPhoto'] = modelPhoto; data['nextPMDate'] = nextPMDate; data['lastPMDate'] = lastPMDate; return data; @@ -571,18 +575,50 @@ class Floors { class Departments { int? id; String? name; + List? rooms; Departments({this.id, this.name}); Departments.fromJson(Map json) { id = json['id']; name = json['name']; + if (json['departments'] != null) { + rooms = []; + json['rooms'].forEach((v) { + rooms!.add(Rooms.fromJson(v)); + }); + } } Map toJson() { final Map data = {}; 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 json) { + id = json['id']; + name = json['name']; + value = json['value']; + } + + Map toJson() { + final Map data = {}; + data['id'] = id; + data['name'] = name; + data['value'] = value; return data; } } @@ -592,14 +628,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 json) { id = json['id']; departmentName = json['departmentName']; departmentCode = json['departmentCode']; ntCode = json['ntCode']; + operationalHours = json['operationalHours']; } Map toJson() { @@ -608,6 +646,7 @@ class Department { data['departmentName'] = departmentName; data['departmentCode'] = departmentCode; data['ntCode'] = ntCode; + data['operationalHours'] = operationalHours; return data; } } From ace00bfa858ffc2a22d30c97cb3f56635e9a5b5f Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Sun, 12 Jul 2026 09:45:23 +0300 Subject: [PATCH 03/11] asset images upload for ai training. --- lib/controllers/api_routes/api_manager.dart | 33 ++- lib/controllers/api_routes/urls.dart | 9 +- .../providers/api/devices_provider.dart | 19 ++ .../widgets/equipment/asset_detail_page.dart | 193 +++++++++++------- .../equipment/asset_image_upload_page.dart | 81 ++++++++ .../widgets/images/multi_image_picker.dart | 7 +- 6 files changed, 246 insertions(+), 96 deletions(-) create mode 100644 lib/views/widgets/equipment/asset_image_upload_page.dart diff --git a/lib/controllers/api_routes/api_manager.dart b/lib/controllers/api_routes/api_manager.dart index 4d85989a..89e4b3e0 100644 --- a/lib/controllers/api_routes/api_manager.dart +++ b/lib/controllers/api_routes/api_manager.dart @@ -18,9 +18,9 @@ import 'package:test_sa/new_views/pages/login_page.dart'; class _CachedResponse { final http.Response response; final DateTime timestamp; - + _CachedResponse(this.response, this.timestamp); - + bool isValid(Duration cacheDuration) { return DateTime.now().difference(timestamp) < cacheDuration; } @@ -40,26 +40,26 @@ class ApiManager { User? user; AssetGroup? assetGroup; - + // OPTIMIZATION: HTTP Connection Pooling - Reuse connections instead of creating new ones // This improves performance by 100-200ms on subsequent API calls static final http.Client _httpClient = http.Client(); - + // OPTIMIZATION: Response Caching - Cache lookup/static data to reduce network calls // This reduces API calls by 60% for frequently accessed but rarely changing data static final Map _cache = {}; static const Duration _defaultCacheDuration = Duration(hours: 1); - + /// Clear all cached responses (call on logout or when needed) void clearCache() { _cache.clear(); } - + /// Clear specific cache entry void clearCacheEntry(String url) { _cache.remove(_generateCacheKey(url)); } - + /// Generate cache key from URL and user context String _generateCacheKey(String url) { // Include user and asset group in cache key to avoid cross-user pollution @@ -67,7 +67,7 @@ class ApiManager { } /// GET request with optional caching support - /// + /// /// [url] - API endpoint URL /// [headers] - Optional additional headers /// [enableToastMessage] - Show toast message from API response @@ -85,12 +85,12 @@ class ApiManager { headers ??= {}; headers.addAll(_headers); Uri url0 = Uri.parse(url); - + // OPTIMIZATION: Check cache first if enabled if (useCache && !forceRefresh) { final cacheKey = _generateCacheKey(url); final cachedResponse = _cache[cacheKey]; - + if (cachedResponse != null && cachedResponse.isValid(cacheDuration ?? _defaultCacheDuration)) { if (kDebugMode) { print('📦 Cache HIT: $url'); @@ -98,10 +98,10 @@ class ApiManager { return cachedResponse.response; } } - + // OPTIMIZATION: Use persistent HTTP client for connection pooling http.Response response = await _httpClient.get(url0, headers: headers); - + // OPTIMIZATION: Cache the response if caching is enabled if (useCache && response.statusCode >= 200 && response.statusCode < 300) { final cacheKey = _generateCacheKey(url); @@ -248,7 +248,7 @@ class ApiManager { Uri url0 = Uri.parse(url); // print(headers); // log(json.encode(body)); - + // OPTIMIZATION: Use persistent HTTP client for connection pooling var request = http.Request('PUT', url0); request.body = json.encode(body); @@ -275,10 +275,9 @@ class ApiManager { } Future multiPart(String url, {Map? headers, required Map body, required List> files}) async { - Map headers0 = const { - 'Content-Type': 'multipart/form-data', - }; + Map headers0 = _headers; + headers0["Content-Type"] = 'multipart/form-data'; headers ??= {}; headers.addAll(headers0); @@ -360,7 +359,7 @@ class ApiManager { Navigator.pop(context); Navigator.of(context).pushNamedAndRemoveUntil(LoginPage.routeName, (routes) => true); } - + /// Dispose HTTP client (call when app is closing if needed) /// Note: In most cases, keep client alive for app lifetime for best performance void dispose() { diff --git a/lib/controllers/api_routes/urls.dart b/lib/controllers/api_routes/urls.dart index 43998ee8..e0820b57 100644 --- a/lib/controllers/api_routes/urls.dart +++ b/lib/controllers/api_routes/urls.dart @@ -3,13 +3,13 @@ class URLs { static const String appReleaseBuildNumber = "44"; - static const host1 = "https://atomsm.hmg.com"; // production url + // static const host1 = "https://atomsm.hmg.com"; // production url // static const host1 = "https://atomsmdev.hmg.com"; // local DEV url - // static const host1 = "https://atomsmuat.hmg.com"; // local UAT url + static const host1 = "https://atomsmuat.hmg.com"; // local UAT url - // static final String _baseUrl = "$_host/mobile"; // host local UAT + static final String _baseUrl = "$_host/mobile"; // host local UAT // static final String _baseUrl = "$_host/v2/mobile"; // new V2 apis - static final String _baseUrl = "$_host/v3/mobile"; // v3 for production CM,PM,TM + // static final String _baseUrl = "$_host/v3/mobile"; // v3 for production CM,PM,TM // static final String _baseUrl = "$_host/v4/mobile"; // v4 for Demo module // static final String _baseUrl = "$_host/v5/mobile"; // v5 for data segregation // static final String _baseUrl = "$_host/v6/mobile"; // for asset delivery module @@ -65,6 +65,7 @@ class URLs { static get getAssets => "$_baseUrl/Asset/GetAssets"; // get static get getFaultDescription => "$_baseUrl/Asset/GetDefectForModelDefinition"; // get static get getAssetById => "$_baseUrl/Asset/GetAssetById?assetId="; // get + 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 diff --git a/lib/controllers/providers/api/devices_provider.dart b/lib/controllers/providers/api/devices_provider.dart index 218ebf80..f933a5a9 100644 --- a/lib/controllers/providers/api/devices_provider.dart +++ b/lib/controllers/providers/api/devices_provider.dart @@ -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'; @@ -230,4 +231,22 @@ class AssetProvider extends ChangeNotifier { return []; } } + + Future uploadAssetImages(int assetId, List attachments) async { + late Response response; + try { + response = + await ApiManager.instance.multiPart(URLs.saveAssetImages + "?AssetId=$assetId", body: {}, files: attachments.map((attachment) => MultipartFile.fromPath("Files", attachment.name!)).toList()); + if (response.statusCode >= 200 && response.statusCode < 300) { + // client's request was successfully received + // Map assetData = json.decode(response.body)["data"]; + // _assetById = AssetByIdModel.fromJson(assetData); + // return _assetById!; + return true; + } + return false; + } catch (error) { + return false; + } + } } diff --git a/lib/views/widgets/equipment/asset_detail_page.dart b/lib/views/widgets/equipment/asset_detail_page.dart index f0b095fb..5beea1c5 100644 --- a/lib/views/widgets/equipment/asset_detail_page.dart +++ b/lib/views/widgets/equipment/asset_detail_page.dart @@ -8,7 +8,13 @@ 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/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/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'; @@ -39,6 +45,9 @@ class _AssetDetailPageState extends State { int? assetId; AssetProvider? _assetProvider; + AssetByIdModel? assetModel; + + List attachments = []; @override Widget build(BuildContext context) { @@ -48,7 +57,7 @@ class _AssetDetailPageState extends State { return Scaffold( appBar: DefaultAppBar(title: context.translation.assetDetails), body: FutureBuilder( - future: _assetProvider!.getAssetById(assetId!, context.translation), + future: assetModel == null ? _assetProvider!.getAssetById(assetId!, context.translation) : null, builder: (BuildContext context, AsyncSnapshot snapshot) { if (snapshot.hasError) { return FailedLoading( @@ -62,87 +71,108 @@ class _AssetDetailPageState extends State { return const Center(child: ALoading()); } else if (snapshot.hasData) {} if (snapshot.hasData) { - AssetByIdModel assetModel = snapshot.data!; - return SingleChildScrollView( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - AspectRatio( - aspectRatio: 159 / 94, - child: Container( - width: 95, - height: 95, - decoration: ShapeDecoration( - color: AppColor.neutral30, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), - image: DecorationImage( - fit: BoxFit.cover, - image: NetworkImage(assetModel.assetPhoto != null ? URLs.getFileUrl(assetModel.assetPhoto!)! : "https://www.lasteelcraft.com/images/no-image-available.png"), - )), - ), - ), - 6.height, + assetModel = snapshot.data!; + attachments = assetModel?.assetAttachments + ?.map((attachment) => GenericAttachmentModel( + id: attachment.id, + name: attachment.attachmentName, + originalName: attachment.originalName, + )) + .toList() ?? + []; + return Column( + children: [ + ListView(padding: const EdgeInsets.all(16), children: [ Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (assetModel.commissioningStatus != null) - StatusLabel( - label: assetModel.commissioningStatus!.name, - textColor: AppColor.getRequestStatusTextColorByName(context, assetModel.commissioningStatus!.name!), - backgroundColor: AppColor.getRequestStatusColorByName(context, assetModel.commissioningStatus!.name!), + AspectRatio( + aspectRatio: 159 / 94, + child: Container( + width: 95, + height: 95, + decoration: ShapeDecoration( + color: AppColor.neutral30, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + image: DecorationImage( + fit: BoxFit.cover, + image: NetworkImage(assetModel?.assetPhoto != null ? URLs.getFileUrl(assetModel!.assetPhoto!)! : "https://www.lasteelcraft.com/images/no-image-available.png"), + )), ), - if (assetModel.commissioningStatus != null) 8.height, - (assetModel.modelDefinition?.assetName?.cleanupWhitespace.capitalizeFirstOfEach ?? "-").heading5(context), - 8.height, - Row( + ), + 6.height, + Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - "${context.translation.assetNo}: ${assetModel.multiAssets!.first.assetNumber}".bodyText(context), - "${context.translation.modelName}: ${assetModel.modelDefinition!.modelName}".bodyText(context), - "${context.translation.supplier}: ${assetModel.supplier?.suppliername ?? "-"}".bodyText(context), - "${context.translation.manufacture}: ${assetModel.modelDefinition!.manufacturerName}".bodyText(context), - //"${context.translation.location}: ${assetModel.site.custName?.cleanupWhitespace?.capitalizeFirstOfEach}".bodyText(context), - ], - ).expanded, - 8.width, - Column( + if (assetModel?.commissioningStatus != null) + StatusLabel( + label: assetModel!.commissioningStatus!.name, + textColor: AppColor.getRequestStatusTextColorByName(context, assetModel!.commissioningStatus!.name!), + backgroundColor: AppColor.getRequestStatusColorByName(context, assetModel!.commissioningStatus!.name!), + ), + if (assetModel?.commissioningStatus != null) 8.height, + (assetModel?.modelDefinition?.assetName?.cleanupWhitespace.capitalizeFirstOfEach ?? "-").heading5(context), + 8.height, + Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - /// TODO: theres no [ORACLE CODE] available to preview - // "${context.translation.oracleCode}: ${"-"}".bodyText(context), - "${context.translation.snNo}: ${assetModel.multiAssets!.first.assetSerialNo}".bodyText(context), - "${context.translation.site}: ${assetModel.site?.custName?.cleanupWhitespace.capitalizeFirstOfEach ?? "-"}".bodyText(context), - "${context.translation.building}: ${assetModel.building?.name?.cleanupWhitespace.capitalizeFirstOfEach ?? "-"}".bodyText(context), - "${context.translation.floor}: ${assetModel.floor?.name?.cleanupWhitespace.capitalizeFirstOfEach ?? "-"}".bodyText(context), - "${context.translation.md}: ${assetModel.department?.departmentName?.cleanupWhitespace.capitalizeFirstOfEach ?? "-"}".bodyText(context), - "${context.translation.room}: ${assetModel.room?.name?.cleanupWhitespace.capitalizeFirstOfEach ?? "-"}".bodyText(context), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "${context.translation.assetNo}: ${assetModel!.multiAssets!.first.assetNumber}".bodyText(context), + "${context.translation.modelName}: ${assetModel!.modelDefinition!.modelName}".bodyText(context), + "${context.translation.supplier}: ${assetModel!.supplier?.suppliername ?? "-"}".bodyText(context), + "${context.translation.manufacture}: ${assetModel!.modelDefinition!.manufacturerName}".bodyText(context), + //"${context.translation.location}: ${assetModel.site.custName?.cleanupWhitespace?.capitalizeFirstOfEach}".bodyText(context), + ], + ).expanded, + 8.width, + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + /// TODO: theres no [ORACLE CODE] available to preview + // "${context.translation.oracleCode}: ${"-"}".bodyText(context), + "${context.translation.snNo}: ${assetModel!.multiAssets!.first.assetSerialNo}".bodyText(context), + "${context.translation.site}: ${assetModel!.site?.custName?.cleanupWhitespace.capitalizeFirstOfEach ?? "-"}".bodyText(context), + "${context.translation.building}: ${assetModel!.building?.name?.cleanupWhitespace.capitalizeFirstOfEach ?? "-"}".bodyText(context), + "${context.translation.floor}: ${assetModel!.floor?.name?.cleanupWhitespace.capitalizeFirstOfEach ?? "-"}".bodyText(context), + "${context.translation.md}: ${assetModel!.department?.departmentName?.cleanupWhitespace.capitalizeFirstOfEach ?? "-"}".bodyText(context), + "${context.translation.room}: ${assetModel!.room?.name?.cleanupWhitespace.capitalizeFirstOfEach ?? "-"}".bodyText(context), + ], + ).expanded, ], - ).expanded, + ), + 8.height, + const Divider(color: AppColor.neutral30, height: 1, thickness: 1), + 8.height, + "${context.translation.installationDate}: ${assetModel!.installationDate?.toAssetDetailsFormat ?? "-"}".bodyText(context), + "${context.translation.nextPmDate}: ${assetModel!.nextPMDate?.toAssetDetailsFormat ?? "-"}".bodyText(context), + "${context.translation.lastPmDate}: ${assetModel!.lastPMDate?.toAssetDetailsFormat ?? "-"}".bodyText(context), + if ((assetModel?.modelDefinition?.assetDescription ?? "").isNotEmpty) ...[ + 8.height, + const Divider(color: AppColor.neutral30, height: 1, thickness: 1), + 8.height, + assetModel!.modelDefinition!.assetDescription!.bodyText(context), + ], + FilesList(images: attachments.map((toElement) => URLs.getFileUrl(toElement.name!) ?? '').toList()), ], - ), - 8.height, - const Divider(color: AppColor.neutral30, height: 1, thickness: 1), - 8.height, - "${context.translation.installationDate}: ${assetModel.installationDate?.toAssetDetailsFormat ?? "-"}".bodyText(context), - "${context.translation.nextPmDate}: ${assetModel.nextPMDate?.toAssetDetailsFormat ?? "-"}".bodyText(context), - "${context.translation.lastPmDate}: ${assetModel.lastPMDate?.toAssetDetailsFormat ?? "-"}".bodyText(context), - if ((assetModel.modelDefinition?.assetDescription ?? "").isNotEmpty) ...[ - 8.height, - const Divider(color: AppColor.neutral30, height: 1, thickness: 1), - 8.height, - assetModel.modelDefinition!.assetDescription!.bodyText(context), - ] + ) ], - ) - ], - ).toShadowContainer(context), + ).toShadowContainer(context), + ]).expanded, + if (context.userProvider.isEngineer) + FooterActionButton.footerContainer( + context: context, + child: AppFilledButton( + buttonColor: AppColor.primary10, + label: "Upload Images", + onPressed: _upload, + ), + ), + ], ); } return const Center(child: ALoading()); @@ -150,4 +180,25 @@ class _AssetDetailPageState extends State { ), ); } + + void _upload() async { + bool reload = await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => AssetImageUploadPage( + assetId: assetId!, + attachments: assetModel?.assetAttachments + ?.map((attachment) => GenericAttachmentModel( + id: attachment.id, + name: attachment.attachmentName, + originalName: attachment.originalName, + )) + .toList() ?? + []))) ?? + false; + if (reload) { + assetModel = null; + setState(() {}); + } + } } diff --git a/lib/views/widgets/equipment/asset_image_upload_page.dart b/lib/views/widgets/equipment/asset_image_upload_page.dart new file mode 100644 index 00000000..314a7b65 --- /dev/null +++ b/lib/views/widgets/equipment/asset_image_upload_page.dart @@ -0,0 +1,81 @@ +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/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/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/widgets/images/multi_image_picker.dart'; + +class AssetImageUploadPage extends StatefulWidget { + int assetId; + List attachments; + + AssetImageUploadPage({Key? key, required this.assetId, required this.attachments}) : super(key: key); + + @override + _AssetImageUploadPageState createState() { + return _AssetImageUploadPageState(); + } +} + +class _AssetImageUploadPageState extends State { + List attachments = []; + + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: const DefaultAppBar(title: "Upload"), + body: Column( + children: [ + ListView( + padding: const EdgeInsets.all(16), + children: [ + AttachmentPicker( + label: "Upload Images", + attachment: attachments, + buttonColor: AppColor.black10, + onlyImages: true, + showAsListView: true, + buttonIcon: 'image-plus'.toSvgAsset(color: context.isDark ? AppColor.primary10 : AppColor.neutral120), + onChange: (_attachments) { + attachments = _attachments; + setState(() {}); + }, + ).toShadowContainer(context), + ], + ).expanded, + FooterActionButton.footerContainer( + context: context, + child: AppFilledButton( + buttonColor: AppColor.primary10, + label: "Upload Images", + onPressed: () async { + Utils.showUploadingDialog(context); + bool result = await Provider.of(context, listen: false).uploadAssetImages(widget.assetId, attachments); + Utils.hideLoading(context); + if (result) { + Navigator.pop(context, result); + } + }, + ), + ), + ], + )); + } +} diff --git a/lib/views/widgets/images/multi_image_picker.dart b/lib/views/widgets/images/multi_image_picker.dart index aa9ecd79..a7de6c03 100644 --- a/lib/views/widgets/images/multi_image_picker.dart +++ b/lib/views/widgets/images/multi_image_picker.dart @@ -348,7 +348,7 @@ class _AttachmentPickerState extends State { @override Widget build(BuildContext context) { return Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start,spacing: 12, children: [ AppDashedButton( title: widget.label, @@ -360,13 +360,12 @@ class _AttachmentPickerState extends State { : widget.showAsGrid ? showFileSourceSheet : onFilePicker), - 16.height, if (widget.attachment.isNotEmpty) widget.showAsListView ? ListView.separated( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), - padding: EdgeInsets.zero, + padding: const EdgeInsets.only(top: 0), itemBuilder: (cxt, index) { File image = File(widget.attachment[index].name!); return MultiFilesPickerItem( @@ -410,7 +409,7 @@ class _AttachmentPickerState extends State { ); }, ), - ), + ).paddingOnly(top: 0), ], ); } From dbc53d62f8a0c6968fc64c7c69412930e9700cad Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Mon, 13 Jul 2026 10:03:48 +0300 Subject: [PATCH 04/11] apk provided to FM for asset image training --- lib/controllers/api_routes/urls.dart | 8 +- .../service_request_detail_view.dart | 89 +++++++++++-------- lib/views/widgets/dialogs/loading_dialog.dart | 43 ++++----- .../widgets/equipment/asset_detail_page.dart | 3 +- .../equipment/asset_image_upload_page.dart | 22 +++-- .../widgets/images/multi_image_picker.dart | 42 ++++++--- 6 files changed, 128 insertions(+), 79 deletions(-) diff --git a/lib/controllers/api_routes/urls.dart b/lib/controllers/api_routes/urls.dart index e0820b57..84b68233 100644 --- a/lib/controllers/api_routes/urls.dart +++ b/lib/controllers/api_routes/urls.dart @@ -3,13 +3,13 @@ class URLs { static const String appReleaseBuildNumber = "44"; - // static const host1 = "https://atomsm.hmg.com"; // production url + static const host1 = "https://atomsm.hmg.com"; // production url // static const host1 = "https://atomsmdev.hmg.com"; // local DEV url - static const host1 = "https://atomsmuat.hmg.com"; // local UAT url + // static const host1 = "https://atomsmuat.hmg.com"; // local UAT url - static final String _baseUrl = "$_host/mobile"; // host local UAT + // static final String _baseUrl = "$_host/mobile"; // host local UAT // static final String _baseUrl = "$_host/v2/mobile"; // new V2 apis - // static final String _baseUrl = "$_host/v3/mobile"; // v3 for production CM,PM,TM + static final String _baseUrl = "$_host/v3/mobile"; // v3 for production CM,PM,TM // static final String _baseUrl = "$_host/v4/mobile"; // v4 for Demo module // static final String _baseUrl = "$_host/v5/mobile"; // v5 for data segregation // static final String _baseUrl = "$_host/v6/mobile"; // for asset delivery module diff --git a/lib/modules/cm_module/views/components/service_request_detail_view.dart b/lib/modules/cm_module/views/components/service_request_detail_view.dart index 026fac67..8bc088b8 100644 --- a/lib/modules/cm_module/views/components/service_request_detail_view.dart +++ b/lib/modules/cm_module/views/components/service_request_detail_view.dart @@ -20,6 +20,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/loaders/no_data_found.dart'; @@ -73,43 +74,43 @@ class _ServiceRequestDetailViewState extends State { : requestProvider.currentWorkOrder == null ? const NoDataFound() : Column( - children: [ - SingleChildScrollView( - padding: EdgeInsets.symmetric(horizontal: 16.toScreenWidth), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - workOrderDetailCard(context, requestProvider.currentWorkOrder!.data!, _userProvider, requestProvider), - initialVisitCard(requestDetailProvider: requestProvider, userProvider: _userProvider), - 12.height, - assetDetailCard(requestDetailProvider: requestProvider, userProvider: _userProvider), - 12.height, - if (context.userProvider.user!.type == UsersTypes.engineer && - !requestProvider.isReadOnlyRequest && - requestProvider.currentWorkOrder!.data!.nextStep?.workOrderNextStepEnum == WorkOrderNextStepEnum.activity) ...[ - costCard(context, requestProvider), + children: [ + SingleChildScrollView( + padding: EdgeInsets.symmetric(horizontal: 16.toScreenWidth), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + workOrderDetailCard(context, requestProvider.currentWorkOrder!.data!, _userProvider, requestProvider), + initialVisitCard(requestDetailProvider: requestProvider, userProvider: _userProvider), 12.height, + assetDetailCard(requestDetailProvider: requestProvider, userProvider: _userProvider), + 12.height, + if (context.userProvider.user!.type == UsersTypes.engineer && + !requestProvider.isReadOnlyRequest && + requestProvider.currentWorkOrder!.data!.nextStep?.workOrderNextStepEnum == WorkOrderNextStepEnum.activity) ...[ + costCard(context, requestProvider), + 12.height, + ], + contactInfoCard(context, requestProvider.currentWorkOrder!.data!), + 20.height, ], - contactInfoCard(context, requestProvider.currentWorkOrder!.data!), - 20.height, - ], - ), - ).expanded, - SafeArea( - top: false, - right: false, - left: false, - child: FooterActionButton.requestDetailsFooterWidget( - workOrderNextStepStatus: requestProvider.currentWorkOrder!.data!.nextStep!.workOrderNextStepEnum!, - status: requestProvider.currentWorkOrder?.data?.status, - isEmpIsAssigned: requestProvider.currentWorkOrder!.data!.assignedEmployee != null, - activities: requestProvider.currentWorkOrder!.data?.activities ?? [], - userProvider: _userProvider, - context: context), - ).toShadowContainer(context, padding: 0, showShadow: false, borderRadius: 0), - ], - ); + ), + ).expanded, + SafeArea( + top: false, + right: false, + left: false, + child: FooterActionButton.requestDetailsFooterWidget( + workOrderNextStepStatus: requestProvider.currentWorkOrder!.data!.nextStep!.workOrderNextStepEnum!, + status: requestProvider.currentWorkOrder?.data?.status, + isEmpIsAssigned: requestProvider.currentWorkOrder!.data!.assignedEmployee != null, + activities: requestProvider.currentWorkOrder!.data?.activities ?? [], + userProvider: _userProvider, + context: context), + ).toShadowContainer(context, padding: 0, showShadow: false, borderRadius: 0), + ], + ); }); } @@ -135,7 +136,7 @@ class _ServiceRequestDetailViewState extends State { // spacing: 8, runSpacing: 8, children: [ - if (Provider.of(context, listen: false).showPriority&&workOrder.priority!=null) ...[ + if (Provider.of(context, listen: false).showPriority && workOrder.priority != null) ...[ StatusLabel( label: workOrder.priority?.name, id: workOrder.priority?.value, @@ -190,9 +191,19 @@ class _ServiceRequestDetailViewState extends State { ], ), 8.height, - Text( - context.translation.assetInformation, - style: AppTextStyles.heading4.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), + Row( + spacing: 4, + children: [ + Flexible( + child: Text( + context.translation.assetInformation, + style: AppTextStyles.heading4.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), + ), + ), + const Icon(Icons.open_in_new_rounded, color: AppColor.primary10, size: 21).onPress(() { + Navigator.of(context).pushNamed(AssetDetailPage.id, arguments: workOrder.asset!.id); + }) + ], ), 8.height, Text( diff --git a/lib/views/widgets/dialogs/loading_dialog.dart b/lib/views/widgets/dialogs/loading_dialog.dart index 1a72e452..a8a6880e 100644 --- a/lib/views/widgets/dialogs/loading_dialog.dart +++ b/lib/views/widgets/dialogs/loading_dialog.dart @@ -9,26 +9,29 @@ class UploadingDialog extends StatelessWidget { @override Widget build(BuildContext context) { return Center( - 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( - "Uploading...", - style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500, color: AppColor.textColor(context), height: 35 / 24, letterSpacing: -0.96), - ) - ], + 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( + "Uploading...", + style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500, color: AppColor.textColor(context), height: 35 / 24, letterSpacing: -0.96), + ) + ], + ), ), ), ); diff --git a/lib/views/widgets/equipment/asset_detail_page.dart b/lib/views/widgets/equipment/asset_detail_page.dart index 5beea1c5..151df55e 100644 --- a/lib/views/widgets/equipment/asset_detail_page.dart +++ b/lib/views/widgets/equipment/asset_detail_page.dart @@ -7,6 +7,7 @@ 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'; @@ -168,7 +169,7 @@ class _AssetDetailPageState extends State { context: context, child: AppFilledButton( buttonColor: AppColor.primary10, - label: "Upload Images", + label: "Upload Images (AI Training)", onPressed: _upload, ), ), diff --git a/lib/views/widgets/equipment/asset_image_upload_page.dart b/lib/views/widgets/equipment/asset_image_upload_page.dart index 314a7b65..cd0caae9 100644 --- a/lib/views/widgets/equipment/asset_image_upload_page.dart +++ b/lib/views/widgets/equipment/asset_image_upload_page.dart @@ -10,6 +10,7 @@ import 'package:test_sa/modules/cm_module/views/components/action_button/footer_ 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/widgets/dialogs/dialog.dart'; import 'package:test_sa/views/widgets/images/multi_image_picker.dart'; class AssetImageUploadPage extends StatefulWidget { @@ -30,6 +31,7 @@ class _AssetImageUploadPageState extends State { @override void initState() { super.initState(); + // attachments = widget.attachments; } @override @@ -47,10 +49,11 @@ class _AssetImageUploadPageState extends State { padding: const EdgeInsets.all(16), children: [ AttachmentPicker( - label: "Upload Images", + label: "Import", attachment: attachments, buttonColor: AppColor.black10, onlyImages: true, + pickMultiple: true, showAsListView: true, buttonIcon: 'image-plus'.toSvgAsset(color: context.isDark ? AppColor.primary10 : AppColor.neutral120), onChange: (_attachments) { @@ -64,13 +67,22 @@ class _AssetImageUploadPageState extends State { context: context, child: AppFilledButton( buttonColor: AppColor.primary10, + disableButton: attachments.length < 4, label: "Upload Images", onPressed: () async { - Utils.showUploadingDialog(context); - bool result = await Provider.of(context, listen: false).uploadAssetImages(widget.assetId, attachments); - Utils.hideLoading(context); + bool result = await showDialog( + context: context, + builder: (_) => AAlertDialog( + title: context.translation.confirm, + content: "For more enhancement, these images will be used to train AI model.\n\nAre you sure you are uploading clear and high quality images?"), + ); if (result) { - Navigator.pop(context, result); + Utils.showUploadingDialog(context); + result = await Provider.of(context, listen: false).uploadAssetImages(widget.assetId, attachments); + Utils.hideLoading(context); + if (result) { + Navigator.pop(context, result); + } } }, ), diff --git a/lib/views/widgets/images/multi_image_picker.dart b/lib/views/widgets/images/multi_image_picker.dart index a7de6c03..cead4b1a 100644 --- a/lib/views/widgets/images/multi_image_picker.dart +++ b/lib/views/widgets/images/multi_image_picker.dart @@ -322,6 +322,7 @@ class AttachmentPicker extends StatefulWidget { final Function(List)? onChange; final bool showAsGrid; final bool showAsListView; + final bool pickMultiple; Lookup? documentType; AttachmentPicker( @@ -337,6 +338,7 @@ class AttachmentPicker extends StatefulWidget { this.documentType, this.showAsGrid = false, this.showAsListView = false, + this.pickMultiple = false, this.buttonColor}) : super(key: key); @@ -348,7 +350,8 @@ class _AttachmentPickerState extends State { @override Widget build(BuildContext context) { return Column( - crossAxisAlignment: CrossAxisAlignment.start,spacing: 12, + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 12, children: [ AppDashedButton( title: widget.label, @@ -465,16 +468,21 @@ class _AttachmentPickerState extends State { ).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) { @@ -632,6 +640,20 @@ class _AttachmentPickerState extends State { // ); if (source == null) return; + if (widget.pickMultiple && source == ImageSource.gallery) { + final pickedFiles = await ImagePicker().pickMultiImage(imageQuality: 70, maxWidth: 800, maxHeight: 800); + 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: 70, maxWidth: 800, maxHeight: 800); if (pickedFile != null) { From fbc14318e5f8e2f914d21bda7165e4fbaa6f14b5 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Thu, 16 Jul 2026 12:05:54 +0300 Subject: [PATCH 05/11] asset images upload functionality added for ai training. --- .../images/asset_image_upload/side_bottom.svg | 13 ++ .../images/asset_image_upload/side_left.svg | 13 ++ .../images/asset_image_upload/side_right.svg | 13 ++ assets/images/asset_image_upload/side_top.svg | 13 ++ lib/controllers/api_routes/urls.dart | 8 +- .../providers/api/devices_provider.dart | 16 +- lib/models/device/asset.dart | 6 + lib/models/device/asset_by_id_model.dart | 47 ++++- lib/models/generic_attachment_model.dart | 12 +- .../new_models/work_order_detail_model.dart | 4 + .../service_request_detail_view.dart | 7 + .../device_transfer/search_device_page.dart | 119 ++++++----- .../widgets/equipment/asset_detail_page.dart | 26 +-- .../equipment/asset_image_upload_page.dart | 192 ++++++++++++++++-- .../equipment/asset_item_gridview.dart | 11 +- .../equipment/asset_item_listview.dart | 9 +- .../equipment/single_device_picker.dart | 2 +- .../widgets/images/multi_image_picker.dart | 156 ++++++++------ pubspec.yaml | 2 + 19 files changed, 498 insertions(+), 171 deletions(-) create mode 100644 assets/images/asset_image_upload/side_bottom.svg create mode 100644 assets/images/asset_image_upload/side_left.svg create mode 100644 assets/images/asset_image_upload/side_right.svg create mode 100644 assets/images/asset_image_upload/side_top.svg diff --git a/assets/images/asset_image_upload/side_bottom.svg b/assets/images/asset_image_upload/side_bottom.svg new file mode 100644 index 00000000..7d9ec605 --- /dev/null +++ b/assets/images/asset_image_upload/side_bottom.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/assets/images/asset_image_upload/side_left.svg b/assets/images/asset_image_upload/side_left.svg new file mode 100644 index 00000000..bdf7e6d9 --- /dev/null +++ b/assets/images/asset_image_upload/side_left.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/assets/images/asset_image_upload/side_right.svg b/assets/images/asset_image_upload/side_right.svg new file mode 100644 index 00000000..f8db13a1 --- /dev/null +++ b/assets/images/asset_image_upload/side_right.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/assets/images/asset_image_upload/side_top.svg b/assets/images/asset_image_upload/side_top.svg new file mode 100644 index 00000000..52841a4e --- /dev/null +++ b/assets/images/asset_image_upload/side_top.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/lib/controllers/api_routes/urls.dart b/lib/controllers/api_routes/urls.dart index 84b68233..e0820b57 100644 --- a/lib/controllers/api_routes/urls.dart +++ b/lib/controllers/api_routes/urls.dart @@ -3,13 +3,13 @@ class URLs { static const String appReleaseBuildNumber = "44"; - static const host1 = "https://atomsm.hmg.com"; // production url + // static const host1 = "https://atomsm.hmg.com"; // production url // static const host1 = "https://atomsmdev.hmg.com"; // local DEV url - // static const host1 = "https://atomsmuat.hmg.com"; // local UAT url + static const host1 = "https://atomsmuat.hmg.com"; // local UAT url - // static final String _baseUrl = "$_host/mobile"; // host local UAT + static final String _baseUrl = "$_host/mobile"; // host local UAT // static final String _baseUrl = "$_host/v2/mobile"; // new V2 apis - static final String _baseUrl = "$_host/v3/mobile"; // v3 for production CM,PM,TM + // static final String _baseUrl = "$_host/v3/mobile"; // v3 for production CM,PM,TM // static final String _baseUrl = "$_host/v4/mobile"; // v4 for Demo module // static final String _baseUrl = "$_host/v5/mobile"; // v5 for data segregation // static final String _baseUrl = "$_host/v6/mobile"; // for asset delivery module diff --git a/lib/controllers/providers/api/devices_provider.dart b/lib/controllers/providers/api/devices_provider.dart index f933a5a9..d3e841ff 100644 --- a/lib/controllers/providers/api/devices_provider.dart +++ b/lib/controllers/providers/api/devices_provider.dart @@ -232,11 +232,21 @@ class AssetProvider extends ChangeNotifier { } } - Future uploadAssetImages(int assetId, List attachments) async { + Future uploadAssetImages(int assetId, TrainingImageData trainingImageData) async { late Response response; try { - response = - await ApiManager.instance.multiPart(URLs.saveAssetImages + "?AssetId=$assetId", body: {}, files: attachments.map((attachment) => MultipartFile.fromPath("Files", attachment.name!)).toList()); + List> 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 assetData = json.decode(response.body)["data"]; diff --git a/lib/models/device/asset.dart b/lib/models/device/asset.dart index 1e41dc31..91c86dc9 100644 --- a/lib/models/device/asset.dart +++ b/lib/models/device/asset.dart @@ -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 toJson() { @@ -381,6 +386,7 @@ class Asset { map['comment'] = comment; map['tagCode'] = tagCode; map['assetPhoto'] = assetPhoto; + map['hasTrainingImageData'] = hasTrainingImageData; return map; } } diff --git a/lib/models/device/asset_by_id_model.dart b/lib/models/device/asset_by_id_model.dart index b4ac22ef..ab177b23 100644 --- a/lib/models/device/asset_by_id_model.dart +++ b/lib/models/device/asset_by_id_model.dart @@ -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 { @@ -63,6 +64,8 @@ class AssetByIdModel { String? modelPhoto; String? nextPMDate; String? lastPMDate; + bool? hasTrainingImageData; + TrainingImageData? trainingImageData; AssetByIdModel( {this.multiAssets, @@ -125,9 +128,13 @@ class AssetByIdModel { this.assetPhoto, this.modelPhoto, this.nextPMDate, - this.lastPMDate}); + this.lastPMDate, + this.trainingImageData, + this.hasTrainingImageData}); AssetByIdModel.fromJson(Map json) { + print("assetModel!.fromJson:${json['hasTrainingImageData']}"); + if (json['multiAssets'] != null) { multiAssets = []; json['multiAssets'].forEach((v) { @@ -204,6 +211,8 @@ class AssetByIdModel { modelPhoto = json['modelPhoto']; nextPMDate = json['nextPMDate']; lastPMDate = json['lastPMDate']; + hasTrainingImageData = json['hasTrainingImageData']; + trainingImageData = json['trainingImageData'] != null ? TrainingImageData.fromJson(json['trainingImageData']) : null; } Map toJson() { @@ -301,6 +310,8 @@ class AssetByIdModel { data['modelPhoto'] = modelPhoto; data['nextPMDate'] = nextPMDate; data['lastPMDate'] = lastPMDate; + data['hasTrainingImageData'] = hasTrainingImageData; + data['trainingImageData'] = trainingImageData?.toJson(); return data; } } @@ -789,3 +800,37 @@ class DemoRequest { return data; } } + +class TrainingImageData { + GenericAttachmentModel? leftImage; + GenericAttachmentModel? rightImage; + GenericAttachmentModel? upImage; + GenericAttachmentModel? downImage; + List? additionalImages; + + TrainingImageData({this.leftImage, this.rightImage, this.upImage, this.downImage, this.additionalImages = const []}); + + TrainingImageData.fromJson(Map 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 toJson() { + final Map data = {}; + 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; + } +} diff --git a/lib/models/generic_attachment_model.dart b/lib/models/generic_attachment_model.dart index dde1d74d..6ba126e1 100644 --- a/lib/models/generic_attachment_model.dart +++ b/lib/models/generic_attachment_model.dart @@ -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 json) { + GenericAttachmentModel.fromJson(Map 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 toJson() { diff --git a/lib/models/new_models/work_order_detail_model.dart b/lib/models/new_models/work_order_detail_model.dart index 1936113a..984edca5 100644 --- a/lib/models/new_models/work_order_detail_model.dart +++ b/lib/models/new_models/work_order_detail_model.dart @@ -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 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, }; } diff --git a/lib/modules/cm_module/views/components/service_request_detail_view.dart b/lib/modules/cm_module/views/components/service_request_detail_view.dart index 8bc088b8..6ebd657c 100644 --- a/lib/modules/cm_module/views/components/service_request_detail_view.dart +++ b/lib/modules/cm_module/views/components/service_request_detail_view.dart @@ -206,6 +206,13 @@ class _ServiceRequestDetailViewState extends State { ], ), 8.height, + if (context.userProvider.isEngineer && !(workOrder.asset!.hasTrainingImageData ?? true)) ...[ + Text( + '*Images are required for the asset', + style: AppTextStyles.bodyText.copyWith(color: AppColor.red30), + ), + 4.height, + ], Text( '${context.translation.assetName}: ${workOrder.assetNdModel!.name?.cleanupWhitespace?.capitalizeFirstOfEach}', style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral10 : AppColor.neutral120), diff --git a/lib/modules/tm_module/device_transfer/search_device_page.dart b/lib/modules/tm_module/device_transfer/search_device_page.dart index cc76e370..93a6f523 100644 --- a/lib/modules/tm_module/device_transfer/search_device_page.dart +++ b/lib/modules/tm_module/device_transfer/search_device_page.dart @@ -171,68 +171,67 @@ class _SearchDevicePageState extends State { ).expanded, if (widget.multiSelection && selectedAssets.isNotEmpty) FooterActionButton.footerContainer( - context: context, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Expanded( - child: AppFilledButton( - label: "Clear", - buttonColor: AppColor.white60, - textColor: AppColor.black10, - loading: false, - onPressed: () async { - selectedAssets.clear(); - setState(() {}); - }, + context: context, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Expanded( + child: AppFilledButton( + label: "Clear", + buttonColor: AppColor.white60, + textColor: AppColor.black10, + loading: false, + onPressed: () async { + selectedAssets.clear(); + setState(() {}); + }, + ), ), - ), - 12.width, - Expanded( - child: AppFilledButton( - label: context.translation.select, - buttonColor: AppColor.primary10, - loading: false, - onPressed: () async { - Navigator.of(context).pop(); - List assets = _searchableList.where((asset) => selectedAssets.contains(asset.id)).toList(); - Navigator.of(context).pop(assets); - }, + 12.width, + Expanded( + child: AppFilledButton( + label: context.translation.select, + buttonColor: AppColor.primary10, + loading: false, + onPressed: () async { + Navigator.of(context).pop(); + List assets = _searchableList.where((asset) => selectedAssets.contains(asset.id)).toList(); + Navigator.of(context).pop(assets); + }, + ), ), - ), - ], - ) - ), - // Row( - // mainAxisSize: MainAxisSize.min, - // children: [ - // Expanded( - // child: AppFilledButton( - // label: "Clear", - // buttonColor: AppColor.white60, - // textColor: AppColor.black10, - // loading: false, - // onPressed: () async { - // selectedAssets.clear(); - // setState(() {}); - // }, - // ), - // ), - // 12.width, - // Expanded( - // child: AppFilledButton( - // label: context.translation.select, - // buttonColor: AppColor.primary10, - // loading: false, - // onPressed: () async { - // Navigator.of(context).pop(); - // List assets = _searchableList.where((asset) => selectedAssets.contains(asset.id)).toList(); - // Navigator.of(context).pop(assets); - // }, - // ), - // ), - // ], - // ).toShadowContainer(context), + ], + )), + // Row( + // mainAxisSize: MainAxisSize.min, + // children: [ + // Expanded( + // child: AppFilledButton( + // label: "Clear", + // buttonColor: AppColor.white60, + // textColor: AppColor.black10, + // loading: false, + // onPressed: () async { + // selectedAssets.clear(); + // setState(() {}); + // }, + // ), + // ), + // 12.width, + // Expanded( + // child: AppFilledButton( + // label: context.translation.select, + // buttonColor: AppColor.primary10, + // loading: false, + // onPressed: () async { + // Navigator.of(context).pop(); + // List assets = _searchableList.where((asset) => selectedAssets.contains(asset.id)).toList(); + // Navigator.of(context).pop(assets); + // }, + // ), + // ), + // ], + // ).toShadowContainer(context), ], ), ), diff --git a/lib/views/widgets/equipment/asset_detail_page.dart b/lib/views/widgets/equipment/asset_detail_page.dart index 151df55e..9c9c0d97 100644 --- a/lib/views/widgets/equipment/asset_detail_page.dart +++ b/lib/views/widgets/equipment/asset_detail_page.dart @@ -117,6 +117,13 @@ class _AssetDetailPageState extends State { if (assetModel?.commissioningStatus != null) 8.height, (assetModel?.modelDefinition?.assetName?.cleanupWhitespace.capitalizeFirstOfEach ?? "-").heading5(context), 8.height, + if (context.userProvider.isEngineer && !(assetModel!.hasTrainingImageData ?? true)) ...[ + Text( + '*Upload Images for this asset', + style: AppTextStyles.bodyText2.copyWith(color: AppColor.red30), + ), + 4.height, + ], Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -164,12 +171,12 @@ class _AssetDetailPageState extends State { ], ).toShadowContainer(context), ]).expanded, - if (context.userProvider.isEngineer) + if (context.userProvider.isEngineer && !(assetModel!.hasTrainingImageData ?? true)) FooterActionButton.footerContainer( context: context, child: AppFilledButton( buttonColor: AppColor.primary10, - label: "Upload Images (AI Training)", + label: "Upload Images", onPressed: _upload, ), ), @@ -183,20 +190,7 @@ class _AssetDetailPageState extends State { } void _upload() async { - bool reload = await Navigator.push( - context, - MaterialPageRoute( - builder: (context) => AssetImageUploadPage( - assetId: assetId!, - attachments: assetModel?.assetAttachments - ?.map((attachment) => GenericAttachmentModel( - id: attachment.id, - name: attachment.attachmentName, - originalName: attachment.originalName, - )) - .toList() ?? - []))) ?? - false; + bool reload = await Navigator.push(context, MaterialPageRoute(builder: (context) => AssetImageUploadPage(assetId: assetId!))) ?? false; if (reload) { assetModel = null; setState(() {}); diff --git a/lib/views/widgets/equipment/asset_image_upload_page.dart b/lib/views/widgets/equipment/asset_image_upload_page.dart index cd0caae9..65d1efc9 100644 --- a/lib/views/widgets/equipment/asset_image_upload_page.dart +++ b/lib/views/widgets/equipment/asset_image_upload_page.dart @@ -1,3 +1,6 @@ +import 'dart:convert'; +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:test_sa/controllers/providers/api/devices_provider.dart'; @@ -5,7 +8,9 @@ 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'; @@ -15,9 +20,9 @@ import 'package:test_sa/views/widgets/images/multi_image_picker.dart'; class AssetImageUploadPage extends StatefulWidget { int assetId; - List attachments; + TrainingImageData? attachments; - AssetImageUploadPage({Key? key, required this.assetId, required this.attachments}) : super(key: key); + AssetImageUploadPage({Key? key, required this.assetId, this.attachments}) : super(key: key); @override _AssetImageUploadPageState createState() { @@ -26,12 +31,12 @@ class AssetImageUploadPage extends StatefulWidget { } class _AssetImageUploadPageState extends State { - List attachments = []; + late TrainingImageData trainingImageData; @override void initState() { super.initState(); - // attachments = widget.attachments; + trainingImageData = widget.attachments ?? TrainingImageData(additionalImages: []); } @override @@ -48,16 +53,68 @@ class _AssetImageUploadPageState extends State { 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: "Import", - attachment: attachments, + label: "Additional Images", + attachment: trainingImageData.additionalImages!, buttonColor: AppColor.black10, onlyImages: true, pickMultiple: true, - showAsListView: true, buttonIcon: 'image-plus'.toSvgAsset(color: context.isDark ? AppColor.primary10 : AppColor.neutral120), onChange: (_attachments) { - attachments = _attachments; + trainingImageData.additionalImages = _attachments; setState(() {}); }, ).toShadowContainer(context), @@ -67,18 +124,19 @@ class _AssetImageUploadPageState extends State { context: context, child: AppFilledButton( buttonColor: AppColor.primary10, - disableButton: attachments.length < 4, - label: "Upload Images", + 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: "For more enhancement, these images will be used to train AI model.\n\nAre you sure you are uploading clear and high quality images?"), + 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(context, listen: false).uploadAssetImages(widget.assetId, attachments); + result = await Provider.of(context, listen: false).uploadAssetImages(widget.assetId, trainingImageData); Utils.hideLoading(context); if (result) { Navigator.pop(context, result); @@ -90,4 +148,112 @@ class _AssetImageUploadPageState extends State { ], )); } + + 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(() { + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => Scaffold( + appBar: const DefaultAppBar(), + body: SafeArea(child: InteractiveViewer(child: imageWidget).center), + ), + ), + ); + })); + } + 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); + }, + ); + } } diff --git a/lib/views/widgets/equipment/asset_item_gridview.dart b/lib/views/widgets/equipment/asset_item_gridview.dart index cc463097..61e89ddc 100644 --- a/lib/views/widgets/equipment/asset_item_gridview.dart +++ b/lib/views/widgets/equipment/asset_item_gridview.dart @@ -12,7 +12,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) { @@ -43,6 +43,13 @@ class AssetItemGridView extends StatelessWidget { overflow: TextOverflow.ellipsis, style: AppTextStyles.bodyText.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), ), + 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, @@ -71,6 +78,6 @@ class AssetItemGridView extends StatelessWidget { ], ).expanded ], - ).onPress(() => onPressed(device)).toShadowContainer(context,padding: 12); + ).onPress(() => onPressed(device)).toShadowContainer(context, padding: 12); } } diff --git a/lib/views/widgets/equipment/asset_item_listview.dart b/lib/views/widgets/equipment/asset_item_listview.dart index f2aab423..2c8f96c1 100644 --- a/lib/views/widgets/equipment/asset_item_listview.dart +++ b/lib/views/widgets/equipment/asset_item_listview.dart @@ -41,7 +41,14 @@ class AssetItemListView extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ device.modelDefinition!.assetName!.cleanupWhitespace.capitalizeFirstOfEach.heading6(context), - 8.height, + if (context.userProvider.isEngineer && !(device.hasTrainingImageData ?? true)) ...[ + 2.height, + Text( + '*Upload Images for this asset', + style: AppTextStyles.tinyFont2.copyWith(color: AppColor.red30), + ), + ], + 6.height, "${context.translation.assetNumber} : ${device.assetNumber}".bodyText(context), "${context.translation.modelName} : ${device.modelDefinition!.modelName}".cleanupWhitespace.capitalizeFirstOfEach.bodyText(context), ], diff --git a/lib/views/widgets/equipment/single_device_picker.dart b/lib/views/widgets/equipment/single_device_picker.dart index bd709317..7bd79936 100644 --- a/lib/views/widgets/equipment/single_device_picker.dart +++ b/lib/views/widgets/equipment/single_device_picker.dart @@ -119,7 +119,7 @@ class _MyAssetsPageState extends State { }).expanded, 16.width, Icon( - Icons.grid_view_outlined, + showListView ? Icons.view_list_rounded : Icons.grid_view_rounded, color: context.isDark ? AppColor.neutral10 : AppColor.neutral20, ).onPress(() => setState(() { showListView = !showListView; diff --git a/lib/views/widgets/images/multi_image_picker.dart b/lib/views/widgets/images/multi_image_picker.dart index cead4b1a..b50b2b3c 100644 --- a/lib/views/widgets/images/multi_image_picker.dart +++ b/lib/views/widgets/images/multi_image_picker.dart @@ -3,6 +3,7 @@ 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'; @@ -324,6 +325,8 @@ class AttachmentPicker extends StatefulWidget { final bool showAsListView; final bool pickMultiple; Lookup? documentType; + Widget? child; + bool checkQuality; AttachmentPicker( {Key? key, @@ -339,6 +342,8 @@ class AttachmentPicker extends StatefulWidget { this.showAsGrid = false, this.showAsListView = false, this.pickMultiple = false, + this.checkQuality = false, + this.child, this.buttonColor}) : super(key: key); @@ -349,72 +354,78 @@ class AttachmentPicker extends StatefulWidget { class _AttachmentPickerState extends State { @override Widget build(BuildContext context) { - return 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), - 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(() {}); + 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(() {}); + }, + ); }, - ); - }, - ), - ).paddingOnly(top: 0), - ], - ); + 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 { @@ -507,7 +518,7 @@ class _AttachmentPickerState extends State { ); } - onFilePicker() async { + onFilePicker(bool checkQuality) async { ImageSource? source = await showModalBottomSheet( context: context, builder: (BuildContext context) { @@ -658,6 +669,19 @@ class _AttachmentPickerState extends State { 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); diff --git a/pubspec.yaml b/pubspec.yaml index aa34d07d..3b19daf1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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 @@ -136,6 +137,7 @@ flutter: - assets/ - assets/audio/ - assets/images/ + - assets/images/asset_image_upload/ - assets/lottie/ - assets/subtitles/ - assets/rives/ From e39526c3c6c6b9353b1e92e12d6535edfd84a108 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Thu, 16 Jul 2026 12:11:49 +0300 Subject: [PATCH 06/11] imporvements --- .../cm_module/views/components/service_request_detail_view.dart | 2 +- lib/views/widgets/equipment/asset_image_upload_page.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/modules/cm_module/views/components/service_request_detail_view.dart b/lib/modules/cm_module/views/components/service_request_detail_view.dart index 6ebd657c..33f3ea33 100644 --- a/lib/modules/cm_module/views/components/service_request_detail_view.dart +++ b/lib/modules/cm_module/views/components/service_request_detail_view.dart @@ -200,7 +200,7 @@ class _ServiceRequestDetailViewState extends State { style: AppTextStyles.heading4.copyWith(color: context.isDark ? AppColor.neutral30 : AppColor.neutral50), ), ), - const Icon(Icons.open_in_new_rounded, color: AppColor.primary10, size: 21).onPress(() { + const Icon(Icons.open_in_new_rounded, color: AppColor.primary10, size: 16).onPress(() { Navigator.of(context).pushNamed(AssetDetailPage.id, arguments: workOrder.asset!.id); }) ], diff --git a/lib/views/widgets/equipment/asset_image_upload_page.dart b/lib/views/widgets/equipment/asset_image_upload_page.dart index 65d1efc9..02c836d1 100644 --- a/lib/views/widgets/equipment/asset_image_upload_page.dart +++ b/lib/views/widgets/equipment/asset_image_upload_page.dart @@ -47,7 +47,7 @@ class _AssetImageUploadPageState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: const DefaultAppBar(title: "Upload"), + appBar: const DefaultAppBar(title: "Upload Images"), body: Column( children: [ ListView( From e9096b0ebed01ceb0f681ea7832d41dd73c5da9e Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Thu, 16 Jul 2026 12:44:02 +0300 Subject: [PATCH 07/11] improvement --- lib/controllers/providers/api/devices_provider.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/controllers/providers/api/devices_provider.dart b/lib/controllers/providers/api/devices_provider.dart index d3e841ff..66c95d7b 100644 --- a/lib/controllers/providers/api/devices_provider.dart +++ b/lib/controllers/providers/api/devices_provider.dart @@ -153,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 assetData = json.decode(response.body)["data"]; _assetById = AssetByIdModel.fromJson(assetData); return _assetById!; From 5bf52c6485124e4949af6cda28d77272af399f7e Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Thu, 16 Jul 2026 14:29:22 +0300 Subject: [PATCH 08/11] image in high quality --- lib/views/widgets/images/multi_image_picker.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/views/widgets/images/multi_image_picker.dart b/lib/views/widgets/images/multi_image_picker.dart index b50b2b3c..1f5655db 100644 --- a/lib/views/widgets/images/multi_image_picker.dart +++ b/lib/views/widgets/images/multi_image_picker.dart @@ -652,7 +652,7 @@ class _AttachmentPickerState extends State { if (source == null) return; if (widget.pickMultiple && source == ImageSource.gallery) { - final pickedFiles = await ImagePicker().pickMultiImage(imageQuality: 70, maxWidth: 800, maxHeight: 800); + final pickedFiles = await ImagePicker().pickMultiImage(); if (pickedFiles.isNotEmpty) { pickedFiles.forEach((pickedFile) { File fileImage = File(pickedFile.path); @@ -665,7 +665,7 @@ class _AttachmentPickerState extends State { return; } } - final pickedFile = await ImagePicker().pickImage(source: source, imageQuality: 70, maxWidth: 800, maxHeight: 800); + final pickedFile = await ImagePicker().pickImage(source: source); if (pickedFile != null) { File fileImage = File(pickedFile.path); From 6695912d4e946500cb8212e40282354b1b2671a1 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Sun, 19 Jul 2026 08:36:09 +0300 Subject: [PATCH 09/11] image quality to 70% --- lib/views/widgets/images/multi_image_picker.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/views/widgets/images/multi_image_picker.dart b/lib/views/widgets/images/multi_image_picker.dart index 1f5655db..e43c0ec3 100644 --- a/lib/views/widgets/images/multi_image_picker.dart +++ b/lib/views/widgets/images/multi_image_picker.dart @@ -652,7 +652,7 @@ class _AttachmentPickerState extends State { if (source == null) return; if (widget.pickMultiple && source == ImageSource.gallery) { - final pickedFiles = await ImagePicker().pickMultiImage(); + final pickedFiles = await ImagePicker().pickMultiImage(imageQuality: 70); if (pickedFiles.isNotEmpty) { pickedFiles.forEach((pickedFile) { File fileImage = File(pickedFile.path); @@ -665,7 +665,7 @@ class _AttachmentPickerState extends State { return; } } - final pickedFile = await ImagePicker().pickImage(source: source); + final pickedFile = await ImagePicker().pickImage(source: source, imageQuality: 70); if (pickedFile != null) { File fileImage = File(pickedFile.path); From 488a83f74f38e1c36581c69490e84984217ff94b Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Sun, 19 Jul 2026 09:33:46 +0300 Subject: [PATCH 10/11] FullScreen Image Viewer added. --- lib/extensions/context_extension.dart | 14 ++++++++++++- lib/extensions/widget_extensions.dart | 3 ++- lib/views/full_screen_image_viewer.dart | 20 +++++++++++++++++++ .../equipment/asset_image_upload_page.dart | 15 +++----------- 4 files changed, 38 insertions(+), 14 deletions(-) create mode 100644 lib/views/full_screen_image_viewer.dart diff --git a/lib/extensions/context_extension.dart b/lib/extensions/context_extension.dart index 41f93d7f..a33130cb 100644 --- a/lib/extensions/context_extension.dart +++ b/lib/extensions/context_extension.dart @@ -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(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), ); } diff --git a/lib/extensions/widget_extensions.dart b/lib/extensions/widget_extensions.dart index 252971f7..2e52b835 100644 --- a/lib/extensions/widget_extensions.dart +++ b/lib/extensions/widget_extensions.dart @@ -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)), diff --git a/lib/views/full_screen_image_viewer.dart b/lib/views/full_screen_image_viewer.dart new file mode 100644 index 00000000..43aee198 --- /dev/null +++ b/lib/views/full_screen_image_viewer.dart @@ -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))), + ], + ), + ); + } +} diff --git a/lib/views/widgets/equipment/asset_image_upload_page.dart b/lib/views/widgets/equipment/asset_image_upload_page.dart index 02c836d1..58e3a971 100644 --- a/lib/views/widgets/equipment/asset_image_upload_page.dart +++ b/lib/views/widgets/equipment/asset_image_upload_page.dart @@ -1,6 +1,7 @@ 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'; @@ -15,6 +16,7 @@ import 'package:test_sa/modules/cm_module/views/components/action_button/footer_ 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'; @@ -198,18 +200,7 @@ class _AssetImageUploadPageState extends State { 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(() { - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => Scaffold( - appBar: const DefaultAppBar(), - body: SafeArea(child: InteractiveViewer(child: imageWidget).center), - ), - ), - ); - })); + return ClipRRect(borderRadius: BorderRadius.circular(12), child: imageWidget.onPress(() => context.fullScreenImageViewer(imageWidget))); } return Column( mainAxisAlignment: MainAxisAlignment.center, From 04582c9803c6e315574b31bf9615b75b13c6779d Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Sun, 19 Jul 2026 09:58:53 +0300 Subject: [PATCH 11/11] improvements --- lib/helper/utils.dart | 6 +++--- lib/modules/cx_module/chat/chat_page.dart | 4 ++-- ...{loading_dialog.dart => loading_with_text_dialog.dart} | 8 +++++--- lib/views/widgets/equipment/asset_image_upload_page.dart | 2 +- lib/views/widgets/images/multi_image_picker.dart | 3 ++- 5 files changed, 13 insertions(+), 10 deletions(-) rename lib/views/widgets/dialogs/{loading_dialog.dart => loading_with_text_dialog.dart} (85%) diff --git a/lib/helper/utils.dart b/lib/helper/utils.dart index 0d706f4e..53496f1f 100644 --- a/lib/helper/utils.dart +++ b/lib/helper/utils.dart @@ -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; }); diff --git a/lib/modules/cx_module/chat/chat_page.dart b/lib/modules/cx_module/chat/chat_page.dart index 7f92f9ca..301cb1dc 100644 --- a/lib/modules/cx_module/chat/chat_page.dart +++ b/lib/modules/cx_module/chat/chat_page.dart @@ -391,7 +391,7 @@ class _ChatPageState extends State { 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); } @@ -641,7 +641,7 @@ class _ChatPageState extends State { 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); diff --git a/lib/views/widgets/dialogs/loading_dialog.dart b/lib/views/widgets/dialogs/loading_with_text_dialog.dart similarity index 85% rename from lib/views/widgets/dialogs/loading_dialog.dart rename to lib/views/widgets/dialogs/loading_with_text_dialog.dart index a8a6880e..b07b3eb6 100644 --- a/lib/views/widgets/dialogs/loading_dialog.dart +++ b/lib/views/widgets/dialogs/loading_with_text_dialog.dart @@ -3,8 +3,10 @@ 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 UploadingDialog extends StatelessWidget { - const UploadingDialog({Key? key}) : super(key: key); +class LoadingWithTextDialog extends StatelessWidget { + final String? message; + + const LoadingWithTextDialog({Key? key, this.message}) : super(key: key); @override Widget build(BuildContext context) { @@ -27,7 +29,7 @@ class UploadingDialog extends StatelessWidget { const CircularProgressIndicator(strokeWidth: 3), 16.height, Text( - "Uploading...", + message ?? "Uploading...", style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500, color: AppColor.textColor(context), height: 35 / 24, letterSpacing: -0.96), ) ], diff --git a/lib/views/widgets/equipment/asset_image_upload_page.dart b/lib/views/widgets/equipment/asset_image_upload_page.dart index 58e3a971..7cf3aaa1 100644 --- a/lib/views/widgets/equipment/asset_image_upload_page.dart +++ b/lib/views/widgets/equipment/asset_image_upload_page.dart @@ -137,7 +137,7 @@ class _AssetImageUploadPageState extends State { ), ); if (result) { - Utils.showUploadingDialog(context); + Utils.showLoadingWithTextDialog(context); result = await Provider.of(context, listen: false).uploadAssetImages(widget.assetId, trainingImageData); Utils.hideLoading(context); if (result) { diff --git a/lib/views/widgets/images/multi_image_picker.dart b/lib/views/widgets/images/multi_image_picker.dart index e43c0ec3..f31f184e 100644 --- a/lib/views/widgets/images/multi_image_picker.dart +++ b/lib/views/widgets/images/multi_image_picker.dart @@ -9,6 +9,7 @@ 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'; @@ -665,7 +666,7 @@ class _AttachmentPickerState extends State { return; } } - final pickedFile = await ImagePicker().pickImage(source: source, imageQuality: 70); + final pickedFile = await ImagePicker().pickImage(source: source, imageQuality: 50); if (pickedFile != null) { File fileImage = File(pickedFile.path);