Adds the Flutter inventory app source, cleaned up for a shared repo: generic package name, no hardcoded ERP endpoints (moved to gitignored local config), no dead auth code, no debug logging of session data.
156 lines
4.3 KiB
Dart
156 lines
4.3 KiB
Dart
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<File?> 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<int?> 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<bool> 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<void> 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);
|
|
}
|
|
}
|
|
}
|