import 'dart:convert'; import 'dart:io'; import 'package:dio/dio.dart' as d; import 'package:flutter/material.dart'; import 'package:get_storage/get_storage.dart'; import 'package:image_picker/image_picker.dart'; import 'package:get/get.dart'; import 'package:inventory_app/models/product.dart'; import 'package:mime/mime.dart'; class ProductImageUpdater { final d.Dio dio = d.Dio(); final String baseUrl; final box = GetStorage(); ProductImageUpdater({required this.baseUrl}); final ImagePicker _picker = ImagePicker(); /// Step 1: Pick or take a photo Future pickPhoto({bool fromCamera = true}) async { final XFile? photo = await _picker.pickImage( source: fromCamera ? ImageSource.camera : ImageSource.gallery, ); return photo != null ? File(photo.path) : null; } Future uploadMetaFile(File file) async { final sessionId = box.read('sessionId'); if (sessionId == null) { Get.snackbar("Erreur", "Session ID introuvable",backgroundColor: Colors.red); return null; } final fileName = file.path.split('/').last; final fileSize = await file.length(); if (fileSize > 5 * 1024 * 1024) { Get.snackbar("Erreur", "Fichier trop volumineux. Max: 5MB",backgroundColor: Colors.red); return null; } final mimeType = lookupMimeType(file.path) ?? 'application/octet-stream'; try { final response = await dio.post( '$baseUrl/ws/files/upload', data: file.openRead(), // ✅ Send binary stream options: d.Options( headers: { 'Cookie': 'JSESSIONID=$sessionId', 'Content-Type': 'application/octet-stream', 'X-File-Name': fileName, 'X-File-Size': fileSize.toString(), 'X-File-Type': mimeType, 'X-File-Offset': '0', }, responseType: d.ResponseType.json, ), ); print('📦 StatusCode: ${response.statusCode}'); print('📦 Response: ${response.data}'); if (response.statusCode == 200 && response != null) { final int id = response.data['id']; print('✅ File uploaded, MetaFile ID: $id'); return id; } else { print('❌ Upload failed: ${response.data}'); } } on d.DioException catch (e) { print('❌ DioException: ${e.message}'); if (e.response != null) { print('📄 Response data: ${e.response?.data}'); } } catch (e) { print('❌ Unexpected error: $e'); } return null; } /// Step 3: Update the product with the image ID Future updateProductImage({ required Product product, required int imageId, }) async { final sessionId = box.read('sessionId'); if (sessionId == null) { Get.snackbar("Erreur", "Session ID introuvable",backgroundColor: Colors.red); return false; } final payload = { "data": { "id": product.id, "version": product.version, "picture": {"id": imageId}, }, }; print('✅ payload $payload'); try { final response = await dio.post( '$baseUrl/ws/rest/com.axelor.apps.base.db.Product/${product.id}', data: jsonEncode(payload), options: d.Options( headers: { 'Cookie': 'JSESSIONID=$sessionId', 'Content-Type': 'application/json', }, ), ); if (response.statusCode == 200 && response.data['status'] == 0) { print('✅ Product image updated'); return true; } else { print('❌ Update failed: ${response.data}'); } } catch (e) { print('❌ d.Dio error: $e'); } return false; } /// Step 4: Combine all actions in one flow Future updateProductPictureFlow( BuildContext context, Product product, ) async { File? file = await pickPhoto(); if (file == null) { Get.snackbar("Annulé", "Aucune image sélectionnée",backgroundColor: Colors.red); return; } final imageId = await uploadMetaFile(file); print('✅ imageId $imageId'); if (imageId == null) { Get.snackbar("Erreur", "Upload échoué",backgroundColor: Colors.red); return; } final success = await updateProductImage( product: product, imageId: imageId, ); if (success) { Get.snackbar("Succès", "Image du produit mise à jour",backgroundColor: Colors.green); } } }