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 5957fc07..14f35072 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 @@ -347,7 +347,7 @@ class _AssetInventoryFormViewState extends State { } Future _predictAsset() async { - final PredictedAsset? predicted = await CopilotHelper.pickImageFromCamera(context, pickMode: true); + final PredictedAsset? predicted = await CopilotHelper.pickImageForInventorySession(context); if (predicted != null) _applyPredictedAsset(predicted); } diff --git a/lib/modules/copilot/copilot_helper.dart b/lib/modules/copilot/copilot_helper.dart index 9301465c..a510e2b7 100644 --- a/lib/modules/copilot/copilot_helper.dart +++ b/lib/modules/copilot/copilot_helper.dart @@ -1,28 +1,58 @@ import 'dart:io'; - -import 'package:flutter/foundation.dart'; +import 'package:file_picker/file_picker.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/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/predict_device_model.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/views/widgets/equipment/predict_device_list.dart'; +enum _ImagePickSource { camera, gallery, files } + class CopilotHelper { - static Future pickImageFromCamera(BuildContext context, {bool pickMode = false}) async { + static Future pickImageFromCamera(BuildContext context, {bool pickMode = false}) { + return _pickImage(context, pickMode: pickMode, allowGallery: false); + } + + static Future pickImageForInventorySession(BuildContext context, {bool pickMode = true}) { + return _pickImage(context, pickMode: pickMode, allowGallery: true); + } + + static Future _pickImage(BuildContext context, {bool pickMode = false, bool allowGallery = false}) async { + _ImagePickSource source = _ImagePickSource.camera; + + if (allowGallery) { + final _ImagePickSource? selected = await _showImageSourceSheet(context); + if (selected == null) return null; + if (!context.mounted) return null; + source = selected; + } + + if (source == _ImagePickSource.files) { + return _pickFromFiles(context, pickMode: pickMode); + } + + if (source == _ImagePickSource.gallery) { + return openCamera(context, pickMode: pickMode, source: ImageSource.gallery); + } + final status = await Permission.camera.status; if (status.isGranted) { - return openCamera(context, pickMode: pickMode); + return openCamera(context, pickMode: pickMode, source: ImageSource.camera); } final permission = await Permission.camera.request(); if (permission.isGranted) { - return openCamera(context, pickMode: pickMode); + return openCamera(context, pickMode: pickMode, source: ImageSource.camera); } if (permission.isPermanentlyDenied) { @@ -34,9 +64,93 @@ class CopilotHelper { return null; } - static Future openCamera(BuildContext context, {bool pickMode = false}) async { + static Future _pickFromFiles(BuildContext context, {bool pickMode = false}) async { + final FilePickerResult? result = await FilePicker.platform.pickFiles( + type: FileType.image, + allowMultiple: false, + ); + + final String? path = result?.files.single.path; + if (path == null) return null; + + final File imageFile = File(path); + debugPrint('CopilotHelper: picked image extension = ${imageFile.path.split('.').last}'); + + if (!context.mounted) return null; + return uploadImage(imageFile, context, pickMode: pickMode); + } + + static Future<_ImagePickSource?> _showImageSourceSheet(BuildContext context) { + return showModalBottomSheet<_ImagePickSource>( + context: context, + builder: (BuildContext context) { + Widget sourceCard({required String icon, required String label, required VoidCallback onTap}) { + return Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: AppColor.background(context), + borderRadius: BorderRadius.circular(16), + border: Border.all(color: AppColor.borderColor(context), width: 2), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + icon.toSvgAsset(width: 24, height: 24, color: AppColor.icon2Color(context)), + label.bodyText2(context).custom(color: AppColor.labelTextStyleColor(context)), + ], + ), + ).onPress(onTap); + } + + List listItems = [ + sourceCard( + icon: 'camera_icon', + label: '${context.translation.open}\n${context.translation.camera}', + onTap: () => Navigator.of(context).pop(_ImagePickSource.camera), + ), + sourceCard( + icon: 'gallery_icon', + label: '${context.translation.open}\n${context.translation.gallery}', + onTap: () => Navigator.of(context).pop(_ImagePickSource.gallery), + ), + sourceCard( + icon: 'file_icon', + label: '${context.translation.open}\n${context.translation.files}', + onTap: () => Navigator.of(context).pop(_ImagePickSource.files), + ), + ]; + + return SafeArea( + top: false, + child: Container( + width: double.infinity, + color: AppColor.bottomSheetColor(context), + padding: const EdgeInsets.all(16.0), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + 'Scan Asset'.addTranslation.heading4(context), + GridView.builder( + padding: const EdgeInsets.only(top: 16, bottom: 0), + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, childAspectRatio: 1, crossAxisSpacing: 16, mainAxisSpacing: 16), + itemCount: listItems.length, + itemBuilder: (context, index) => listItems[index], + ), + ], + ), + ), + ); + }, + ); + } + + static Future openCamera(BuildContext context, {bool pickMode = false, ImageSource source = ImageSource.camera}) async { final XFile? pickedFile = await ImagePicker().pickImage( - source: ImageSource.camera, + source: source, imageQuality: 70, ); @@ -45,7 +159,9 @@ class CopilotHelper { } final File imageFile = File(pickedFile.path); + debugPrint('CopilotHelper: picked image extension = ${imageFile.path.split('.').last}'); + if (!context.mounted) return null; return uploadImage(imageFile, context, pickMode: pickMode); }