import 'dart:convert'; import 'package:flutter/foundation.dart'; import 'dart:io'; import 'package:http/http.dart' as http; import 'package:device_info_plus/device_info_plus.dart'; import 'package:crypto/crypto.dart'; import '../data/models/shot.dart'; import '../data/models/target_type.dart'; import 'wallet_identity_service.dart'; class AiExportService { // Utilise 10.0.2.2 pour l'émulateur Android, sinon localhost. // Pour un appareil physique, il faudra utiliser l'IP locale du PC (ex: 192.168.1.X). static String get _defaultApiUrl { if (Platform.isAndroid) { return 'http://10.0.2.2:3000/api/upload'; } return 'http://localhost:3000/api/upload'; } /// Extrait les informations de l'appareil Future> _getDeviceInfo() async { final DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin(); Map deviceData = {'model': 'Unknown', 'os': 'Unknown'}; try { if (Platform.isAndroid) { final androidInfo = await deviceInfoPlugin.androidInfo; deviceData['model'] = '${androidInfo.brand} ${androidInfo.model}'; deviceData['os'] = 'Android ${androidInfo.version.release}'; } else if (Platform.isIOS) { final iosInfo = await deviceInfoPlugin.iosInfo; deviceData['model'] = iosInfo.name; deviceData['os'] = '${iosInfo.systemName} ${iosInfo.systemVersion}'; } else if (Platform.isWindows) { final windowsInfo = await deviceInfoPlugin.windowsInfo; deviceData['model'] = 'Windows PC'; deviceData['os'] = 'Windows ${windowsInfo.majorVersion}.${windowsInfo.minorVersion}'; } } catch (e) { debugPrint('Erreur lors de la récupération des infos appareil: $e'); } return deviceData; } /// Exporte l'image et les données de plotting vers le serveur Future exportData({ required String imagePath, required String sessionId, required TargetType targetType, required double targetCenterX, required double targetCenterY, required double targetRadius, required List shots, int distanceMeters = 25, String weaponName = 'Unknown', String? apiUrl, }) async { try { final url = Uri.parse(apiUrl ?? _defaultApiUrl); final request = http.MultipartRequest('POST', url); // 1. Prepare image final file = File(imagePath); if (!await file.exists()) { throw Exception('Le fichier image n\'existe pas'); } // Read image metadata (approximate dimensions since decoding image can be heavy) // On the frontend we usually have aspectRatio, here we use generic values if not available. final deviceData = await _getDeviceInfo(); // We approximate the target corners from center and radius // radius is relative (0 to 1). We need image width/height to get pixels. // But we can just pass relative corners as well, or a normalized bounding box. // Let's create normalized corners (0 to 1). final corners = [ {"norm_x": targetCenterX - targetRadius, "norm_y": targetCenterY - targetRadius}, {"norm_x": targetCenterX + targetRadius, "norm_y": targetCenterY - targetRadius}, {"norm_x": targetCenterX + targetRadius, "norm_y": targetCenterY + targetRadius}, {"norm_x": targetCenterX - targetRadius, "norm_y": targetCenterY + targetRadius}, ]; // Format the impacts final formattedImpacts = shots.asMap().entries.map((entry) { final index = entry.key; final shot = entry.value; return { "id": index + 1, "label": "bullet_hole", "score": shot.score, "coords": { "norm_x": shot.x, "norm_y": shot.y } }; }).toList(); // Get and hash the wallet identity final walletService = WalletIdentityService(); final phrase = await walletService.getIdentityPhrase(); final phraseBytes = utf8.encode(phrase); final walletHash = sha256.convert(phraseBytes).toString(); // Build JSON payload final plottingJson = { "session_id": sessionId, "wallet_hash": walletHash, "timestamp": DateTime.now().toIso8601String(), "device_info": deviceData, "target_metadata": { "type": targetType.name, "distance_meters": distanceMeters, "weapon": weaponName, // The backend could extract exact width/height from the image. }, "plotting": { "target_corners": corners, "impacts": formattedImpacts } }; // Add fields to request request.fields['plotting'] = jsonEncode(plottingJson); // Add file request.files.add( await http.MultipartFile.fromPath('photo', imagePath), ); // Send request final response = await request.send(); if (response.statusCode == 200) { final responseData = await response.stream.bytesToString(); debugPrint('Export réussi: $responseData'); return true; } else { final errorData = await response.stream.bytesToString(); debugPrint('Erreur d\'export: ${response.statusCode} - $errorData'); return false; } } catch (e) { debugPrint('Exception lors de l\'export: $e'); return false; } } }