diff --git a/lib/models/device/predict_device_model.dart b/lib/models/device/predict_device_model.dart index 4084e800..fe3a6206 100644 --- a/lib/models/device/predict_device_model.dart +++ b/lib/models/device/predict_device_model.dart @@ -81,6 +81,8 @@ class PredictedAssetMetadata { String? assetNumber; String? departmentId; String? departmentName; + String? modelName; + String? manufacturerName; PredictedAssetMetadata({ this.siteId, @@ -90,6 +92,8 @@ class PredictedAssetMetadata { this.assetNumber, this.departmentId, this.departmentName, + this.modelName, + this.manufacturerName, }); PredictedAssetMetadata.fromJson(Map json) { @@ -100,6 +104,8 @@ class PredictedAssetMetadata { assetNumber = json['asset_number']; departmentId = json['department_id']; departmentName = json['department_name']; + modelName = json['model_name']; + manufacturerName = json['manufacturer_name']; } Map toJson() { @@ -111,6 +117,8 @@ class PredictedAssetMetadata { 'asset_number': assetNumber, 'department_id': departmentId, 'department_name': departmentName, + 'model_name': modelName, + 'manufacturer_name': manufacturerName, }; } } \ No newline at end of file diff --git a/lib/modules/asset_inventory_module/pages/asset_inventory_form_view.dart b/lib/modules/asset_inventory_module/pages/asset_inventory_form_view.dart index 29c3d4d5..5957fc07 100644 --- a/lib/modules/asset_inventory_module/pages/asset_inventory_form_view.dart +++ b/lib/modules/asset_inventory_module/pages/asset_inventory_form_view.dart @@ -12,11 +12,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/generic_attachment_model.dart'; +import 'package:test_sa/models/device/predict_device_model.dart'; import 'package:test_sa/modules/asset_inventory_module/models/asset_inventory_model.dart'; import 'package:test_sa/modules/asset_inventory_module/pages/search_asset_view.dart'; import 'package:test_sa/modules/asset_inventory_module/provider/asset_inventory_provider.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/modules/copilot/copilot_helper.dart'; import 'package:test_sa/new_views/app_style/app_color.dart'; import 'package:test_sa/new_views/common_widgets/app_lazy_loading.dart'; import 'package:test_sa/new_views/common_widgets/app_text_form_field.dart'; @@ -331,9 +333,35 @@ class _AssetInventoryFormViewState extends State { ], ), ), + floatingActionButtonLocation: FloatingActionButtonLocation.miniEndFloat, + floatingActionButton: Padding( + padding: const EdgeInsets.only(bottom: 70), + child: FloatingActionButton( + heroTag: "asset_inventory_form_scan_fab", + backgroundColor: AppColor.primary10, + onPressed: () => _predictAsset(), + child: "asset_scan".toSvgAsset(height: 36, color: AppColor.addItemButtonColor(context)), + ), + ), ); } + Future _predictAsset() async { + final PredictedAsset? predicted = await CopilotHelper.pickImageFromCamera(context, pickMode: true); + if (predicted != null) _applyPredictedAsset(predicted); + } + + void _applyPredictedAsset(PredictedAsset predicted) { + final String? assetNumber = predicted.metadata?.assetNumber ?? predicted.assetId; + if (assetNumber == null || assetNumber.isEmpty) { + "Predicted asset has no asset number".showToast; + return; + } + resetData(); + setState(() {}); + searchAsset(assetNo: assetNumber); + } + Future searchAsset({required String assetNo}) async { AssetInventoryProvider provider = Provider.of(context, listen: false); Map payload = { diff --git a/lib/modules/copilot/copilot_helper.dart b/lib/modules/copilot/copilot_helper.dart new file mode 100644 index 00000000..9301465c --- /dev/null +++ b/lib/modules/copilot/copilot_helper.dart @@ -0,0 +1,77 @@ +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:permission_handler/permission_handler.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/models/device/predict_device_model.dart'; +import 'package:test_sa/new_views/common_widgets/app_lazy_loading.dart'; +import 'package:test_sa/views/widgets/equipment/predict_device_list.dart'; + +class CopilotHelper { + static Future pickImageFromCamera(BuildContext context, {bool pickMode = false}) async { + final status = await Permission.camera.status; + + if (status.isGranted) { + return openCamera(context, pickMode: pickMode); + } + + final permission = await Permission.camera.request(); + + if (permission.isGranted) { + return openCamera(context, pickMode: pickMode); + } + + if (permission.isPermanentlyDenied) { + await openAppSettings(); + return null; + } + + debugPrint('Camera permission denied'); + return null; + } + + static Future openCamera(BuildContext context, {bool pickMode = false}) async { + final XFile? pickedFile = await ImagePicker().pickImage( + source: ImageSource.camera, + imageQuality: 70, + ); + + if (pickedFile == null) { + return null; + } + + final File imageFile = File(pickedFile.path); + + return uploadImage(imageFile, context, pickMode: pickMode); + } + + static Future uploadImage(File imageFile, BuildContext context, {bool pickMode = false}) async { + try { + showDialog(context: context, barrierDismissible: false, builder: (context) => const AppLazyLoading()); + AssetProvider assetProvider = Provider.of(context, listen: false); + + PredictDeviceResponse? response = await assetProvider.predictDevice(imageFile); + Navigator.pop(context); + if (response != null && response.success == true) { + final List predictedAssetList = response.predictedAsset ?? []; + if (predictedAssetList.isNotEmpty) { + return Navigator.push( + context, + MaterialPageRoute(builder: (context) => PredictDeviceView(predictedAsset: predictedAssetList, pickMode: pickMode)), + ); + } else { + context.showInfoDialog("No Predicted asset found"); + } + } else { + context.showInfoDialog("Failed to recognize the asset. Please try again."); + } + } catch (e) { + debugPrint('Upload error: $e'); + } + return null; + } +} diff --git a/lib/views/widgets/equipment/my_assets_page.dart b/lib/views/widgets/equipment/my_assets_page.dart index 80ea7384..1c633495 100644 --- a/lib/views/widgets/equipment/my_assets_page.dart +++ b/lib/views/widgets/equipment/my_assets_page.dart @@ -3,8 +3,6 @@ import 'dart:io'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; -import 'package:image_picker/image_picker.dart'; -import 'package:permission_handler/permission_handler.dart'; import 'package:provider/provider.dart'; import 'package:test_sa/controllers/providers/api/devices_provider.dart'; import 'package:test_sa/controllers/providers/api/user_provider.dart'; @@ -13,16 +11,14 @@ 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/models/device/predict_device_model.dart'; +import 'package:test_sa/modules/copilot/copilot_helper.dart'; import 'package:test_sa/modules/tm_module/device_transfer/device_filter_screen.dart'; import 'package:test_sa/modules/tm_module/device_transfer/device_search_screen.dart'; import 'package:test_sa/modules/export_module/pages/export_form_view.dart'; import 'package:test_sa/modules/export_module/models/export_module_type.dart'; -import 'package:test_sa/new_views/common_widgets/app_lazy_loading.dart'; import 'package:test_sa/views/widgets/equipment/asset_detail_page.dart'; import 'package:test_sa/views/widgets/equipment/asset_item_gridview.dart'; import 'package:test_sa/views/widgets/equipment/asset_item_listview.dart'; -import 'package:test_sa/views/widgets/equipment/predict_device_list.dart'; import 'package:test_sa/views/widgets/loaders/lazy_loading.dart'; import 'package:test_sa/views/widgets/loaders/loading_manager.dart'; import 'package:test_sa/views/widgets/loaders/no_data_found.dart'; @@ -650,59 +646,6 @@ class _MyAssetsPageState extends State with SingleTickerProviderSt } Future _pickImageFromCamera() async { - final status = await Permission.camera.status; - - if (status.isGranted) { - await _openCamera(); - return; - } - - final permission = await Permission.camera.request(); - - if (permission.isGranted) { - await _openCamera(); - return; - } - - if (permission.isPermanentlyDenied) { - await openAppSettings(); - return; - } - - debugPrint('Camera permission denied'); - } - - Future _openCamera() async { - final XFile? pickedFile = await ImagePicker().pickImage( - source: ImageSource.camera, - imageQuality: 70, - ); - - if (pickedFile == null) { - return; - } - - final File imageFile = File(pickedFile.path); - - await _uploadImage(imageFile); - } - - Future _uploadImage(File imageFile) async { - try { - showDialog(context: context, barrierDismissible: false, builder: (context) => const AppLazyLoading()); - PredictDeviceResponse? response = await _devicesProvider.predictDevice(imageFile); - Navigator.pop(context); - if (response != null && response.success == true) { - final List predictedAssetList = response.predictedAsset ?? []; - if (predictedAssetList.isNotEmpty) { - Navigator.push(context, MaterialPageRoute(builder: (context) => PredictDeviceView(predictedAsset: predictedAssetList))); - } else { - context.showInfoDialog("No Predicted asset found"); - // Fluttertoast.showToast(msg: 'No Predicted asset found', toastLength: Toast.LENGTH_LONG); - } - } - } catch (e) { - debugPrint('Upload error: $e'); - } + await CopilotHelper.pickImageFromCamera(context); } } diff --git a/lib/views/widgets/equipment/predict_device_list.dart b/lib/views/widgets/equipment/predict_device_list.dart index 7d2fdc6a..0a2bcf21 100644 --- a/lib/views/widgets/equipment/predict_device_list.dart +++ b/lib/views/widgets/equipment/predict_device_list.dart @@ -1,22 +1,15 @@ 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/int_extensions.dart'; -import 'package:test_sa/extensions/widget_extensions.dart'; import 'package:test_sa/models/device/predict_device_model.dart'; -import 'package:test_sa/new_views/app_style/app_color.dart'; import 'package:test_sa/new_views/common_widgets/default_app_bar.dart'; import 'package:test_sa/views/widgets/equipment/asset_detail_page.dart'; -import 'package:test_sa/views/widgets/equipment/predict_device_item_grid_view.dart'; import 'package:test_sa/views/widgets/equipment/predict_device_item_view.dart'; -import 'package:test_sa/views/widgets/loaders/no_data_found.dart'; - class PredictDeviceView extends StatefulWidget { static const String id = "/predict_device_view"; List predictedAsset; + bool pickMode; - PredictDeviceView({Key? key, required this.predictedAsset}) : super(key: key); + PredictDeviceView({Key? key, required this.predictedAsset, this.pickMode = false}) : super(key: key); @override _PredictDeviceViewState createState() => _PredictDeviceViewState(); @@ -86,6 +79,10 @@ class _PredictDeviceViewState extends State with SingleTicker child: PredictDeviceItemView( device: widget.predictedAsset[itemIndex], onPressed: (device) { + if (widget.pickMode) { + Navigator.of(context).pop(device); + return; + } int? assetId = int.tryParse(device.assetId ?? ''); Navigator.of(context).pushNamed(AssetDetailPage.id, arguments: assetId); },