import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'dart:ui'; import 'package:croppy/croppy.dart'; import 'package:cross_file/cross_file.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'package:island/models/file.dart'; import 'package:native_exif/native_exif.dart'; import 'package:tus_client_dart/tus_client_dart.dart'; Future cropImage( BuildContext context, { required XFile image, List? allowedAspectRatios, }) async { final result = await showMaterialImageCropper( context, imageProvider: kIsWeb ? NetworkImage(image.path) : FileImage(File(image.path)), showLoadingIndicatorOnSubmit: true, allowedAspectRatios: allowedAspectRatios, ); if (result == null) return null; // Cancelled operation final croppedFile = result.uiImage; final croppedBytes = await croppedFile.toByteData( format: ImageByteFormat.png, ); if (croppedBytes == null) { return image; } croppedFile.dispose(); return XFile.fromData( croppedBytes.buffer.asUint8List(), path: image.path, mimeType: image.mimeType, ); } Completer putMediaToCloud({ required UniversalFile fileData, required String atk, required String baseUrl, String? filename, String? mimetype, Function(double progress, Duration estimate)? onProgress, }) { final completer = Completer(); // Process the image to remove GPS EXIF data if needed if (fileData.isOnDevice && fileData.type == UniversalFileType.image) { final data = fileData.data; if (data is XFile && !kIsWeb && (Platform.isIOS || Platform.isAndroid)) { // Use native_exif to selectively remove GPS data Exif.fromPath(data.path) .then((exif) { // Remove GPS-related attributes final gpsAttributes = [ 'GPSLatitude', 'GPSLatitudeRef', 'GPSLongitude', 'GPSLongitudeRef', 'GPSAltitude', 'GPSAltitudeRef', 'GPSTimeStamp', 'GPSProcessingMethod', 'GPSDateStamp', ]; // Create a map of attributes to clear final clearAttributes = {}; for (final attr in gpsAttributes) { clearAttributes[attr] = ''; } // Write empty values to remove GPS data return exif.writeAttributes(clearAttributes); }) .then((_) { // Continue with upload after GPS data is removed _processUpload( fileData, atk, baseUrl, filename, mimetype, onProgress, completer, ); }) .catchError((e) { // If there's an error, continue with the original file debugPrint('Error removing GPS EXIF data: $e'); _processUpload( fileData, atk, baseUrl, filename, mimetype, onProgress, completer, ); }); return completer; } } // If not an image or on web, continue with normal upload _processUpload( fileData, atk, baseUrl, filename, mimetype, onProgress, completer, ); return completer; } // Helper method to process the upload after any EXIF processing Completer _processUpload( UniversalFile fileData, String atk, String baseUrl, String? filename, String? mimetype, Function(double progress, Duration estimate)? onProgress, Completer completer, ) { late XFile file; String actualFilename = filename ?? 'randomly_file'; String actualMimetype = mimetype ?? ''; Uint8List? byteData; // Handle the data based on what's in the UniversalFile final data = fileData.data; if (data is XFile) { file = data; actualFilename = filename ?? data.name; actualMimetype = mimetype ?? data.mimeType ?? ''; } else if (data is List || data is Uint8List) { byteData = data is List ? Uint8List.fromList(data) : data; actualFilename = filename ?? 'uploaded_file'; actualMimetype = mimetype ?? 'application/octet-stream'; if (mimetype == null) { completer.completeError( ArgumentError('Mimetype is required when providing raw bytes.'), ); return completer; } file = XFile.fromData(byteData!, mimeType: actualMimetype); } else if (data is SnCloudFile) { // If the file is already on the cloud, just return it completer.complete(data); return completer; } else { completer.completeError( ArgumentError( 'Invalid fileData type. Expected data to be XFile, List, Uint8List, or SnCloudFile.', ), ); return completer; } final Map metadata = { 'filename': actualFilename, 'content-type': actualMimetype, }; final client = TusClient(file); client .upload( uri: Uri.parse('$baseUrl/files/tus'), headers: {'Authorization': 'Bearer $atk'}, metadata: metadata, onComplete: (lastResponse) { final resp = jsonDecode(lastResponse!.headers['x-fileinfo']!); completer.complete(SnCloudFile.fromJson(resp)); }, onProgress: (double progress, Duration estimate) { onProgress?.call(progress, estimate); }, measureUploadSpeed: true, ) .catchError(completer.completeError); return completer; }