@@ -179,7 +179,7 @@ extension AttachmentCreationCoordinator: AttachmentCreationDelegate {
179179 MageLogger . misc. debug ( " Present the gallery " )
180180 var configuration = PHPickerConfiguration ( photoLibrary: PHPhotoLibrary . shared ( ) )
181181 configuration. filter = . any( of: [ . images, . videos] )
182- configuration. selectionLimit = 1 ;
182+ configuration. selectionLimit = 10 ;
183183
184184 // This is to compensate for iOS not setting all the colors on the PHPicker
185185 // it only sets the tint color not anything else, so let's make the button actually viewable
@@ -202,8 +202,7 @@ extension AttachmentCreationCoordinator: PHPickerViewControllerDelegate {
202202 guard let documentsDirectory = FileManager . default. urls ( for: . documentDirectory, in: . userDomainMask) . first else {
203203 return
204204 }
205- let dateFormatter = DateFormatter ( )
206- dateFormatter. dateFormat = " yyyyMMdd_HHmmss "
205+ let uniqueId = UUID ( ) . uuidString
207206 let attachmentsDirectory = documentsDirectory. appendingPathComponent ( " attachments " )
208207 let requestOptions = PHImageRequestOptions ( )
209208 requestOptions. isSynchronous = true
@@ -213,7 +212,7 @@ extension AttachmentCreationCoordinator: PHPickerViewControllerDelegate {
213212 guard let data else {
214213 return
215214 }
216- let scaledImagePath = attachmentsDirectory. appendingPathComponent ( " MAGE_ \( dateFormatter . string ( from : Date ( ) ) ) .jpeg " )
215+ let scaledImagePath = attachmentsDirectory. appendingPathComponent ( " MAGE_ \( uniqueId ) .jpeg " )
217216 do {
218217 try FileManager . default. createDirectory ( at: attachmentsDirectory, withIntermediateDirectories: true , attributes: [ . protectionKey : FileProtectionType . complete] )
219218 }
@@ -246,10 +245,9 @@ extension AttachmentCreationCoordinator: PHPickerViewControllerDelegate {
246245 guard let documentsDirectory = FileManager . default. urls ( for: . documentDirectory, in: . userDomainMask) . first else {
247246 return
248247 }
249- let dateFormatter = DateFormatter ( )
250- dateFormatter. dateFormat = " yyyyMMdd_HHmmss "
248+ let uniqueId = UUID ( ) . uuidString
251249 let attachmentsDirectory = documentsDirectory. appendingPathComponent ( " attachments " )
252- let videoExportPath = attachmentsDirectory. appendingPathComponent ( " MAGE_ \( dateFormatter . string ( from : Date ( ) ) ) .mp4 " )
250+ let videoExportPath = attachmentsDirectory. appendingPathComponent ( " MAGE_ \( uniqueId ) .mp4 " )
253251 let assetRequestOptions = PHVideoRequestOptions ( )
254252 assetRequestOptions. deliveryMode = . highQualityFormat
255253 assetRequestOptions. isNetworkAccessAllowed = true
@@ -282,7 +280,7 @@ extension AttachmentCreationCoordinator: PHPickerViewControllerDelegate {
282280 self . addAttachmentForSaving ( location: videoExportPath, contentType: " video/mp4 " )
283281 }
284282 }
285-
283+
286284 private func requestAVAssetAsync( forVideo: PHAsset , options: PHVideoRequestOptions ? ) async throws -> AVAsset ? {
287285 try await withCheckedThrowingContinuation { continuation in
288286 PHImageManager . default ( ) . requestAVAsset ( forVideo: forVideo, options: options) { avAsset, audioMix, info in
@@ -294,7 +292,7 @@ extension AttachmentCreationCoordinator: PHPickerViewControllerDelegate {
294292 }
295293 }
296294 }
297-
295+
298296 func galleryPermissionDenied( ) {
299297 // The user selected certain photos to share with MAGE and this wasn't one of them
300298 // prompt the user to pick more photos to share
@@ -316,47 +314,31 @@ extension AttachmentCreationCoordinator: PHPickerViewControllerDelegate {
316314 }
317315
318316 func picker( _ picker: PHPickerViewController , didFinishPicking results: [ PHPickerResult ] ) {
319- MageLogger . misc. debug ( " picked a photo \( results) " )
320- // This is to compensate for iOS not setting all the colors on the PHPicker so now we have to set it back
321- UINavigationBar . appearance ( ) . tintColor = self . scheme? . colorScheme. onPrimaryColor
322-
317+ MageLogger . misc. debug ( " picked photos \( results) " )
318+ UINavigationBar . appearance ( ) . tintColor = self . scheme? . colorScheme. onPrimaryColor // This is to compensate for iOS not setting all the colors on the PHPicker so now we have to set it back
319+
323320 guard !results. isEmpty else {
324321 picker. dismiss ( animated: true , completion: nil )
325322 return
326323 }
327324
328- let dateFormatter = DateFormatter ( ) ;
329- dateFormatter. dateFormat = " yyyyMMdd_HHmmss " ;
330-
331325 for result in results {
332326 let itemProvider = result. itemProvider
327+ guard let assetIdentifier = result. assetIdentifier else {
328+ continue // Skip invalid asset (edge case: potentially deleted during upload)
329+ }
333330
334- // find the first type that we can handle
335- for typeIdentifier in itemProvider. registeredTypeIdentifiers {
336- guard let utType = UTType ( typeIdentifier) else {
337- continue
338- }
339- // Matches both com.apple.live-photo-bundle and com.apple.private.live-photo-bundle
340- if utType. conforms ( to: . image) || typeIdentifier. contains ( " live-photo-bundle " ) {
341- if let assetIdentifier = result. assetIdentifier {
342- let options = PHFetchOptions ( )
343- options. predicate = NSPredicate ( format: " mediaType = %d " , PHAssetMediaType . image. rawValue)
344- let fetchResult = PHAsset . fetchAssets ( withLocalIdentifiers: [ assetIdentifier] , options: nil )
345- handlePhoto ( selectedAsset: fetchResult. firstObject, utType: utType)
346- picker. dismiss ( animated: true , completion: nil )
347- return
348- }
349- }
350- // otherwise it should be a movie
351- if utType. conforms ( to: . movie) , let assetIdentifier = result. assetIdentifier {
352- let fetchResult = PHAsset . fetchAssets ( withLocalIdentifiers: [ assetIdentifier] , options: nil )
353- handleVideo ( selectedAsset: fetchResult. firstObject, utType: utType)
354- picker. dismiss ( animated: true , completion: nil )
355- return
356- }
331+ if itemProvider. hasItemConformingToTypeIdentifier ( UTType . movie. identifier) {
332+ let fetchResult = PHAsset . fetchAssets ( withLocalIdentifiers: [ assetIdentifier] , options: nil )
333+ handleVideo ( selectedAsset: fetchResult. firstObject, utType: . movie)
334+ } else if itemProvider. hasItemConformingToTypeIdentifier ( UTType . image. identifier) {
335+ let fetchResult = PHAsset . fetchAssets ( withLocalIdentifiers: [ assetIdentifier] , options: nil )
336+ handlePhoto ( selectedAsset: fetchResult. firstObject, utType: . image)
337+ } else {
338+ MageLogger . misc. debug ( " Could not handle asset types: \( itemProvider. registeredTypeIdentifiers) " )
357339 }
358- MDCSnackbarManager . default. show ( MDCSnackbarMessage ( text: " Could not handle asset types: \( itemProvider. registeredTypeIdentifiers) " ) )
359340 }
341+ picker. dismiss ( animated: true , completion: nil )
360342 }
361343}
362344
@@ -395,16 +377,18 @@ extension AttachmentCreationCoordinator: UIImagePickerControllerDelegate {
395377 func handleCameraImage( picker: UIImagePickerController , info: [ UIImagePickerController . InfoKey : Any ] ) {
396378 locationManager? . stopUpdatingHeading ( ) ;
397379 locationManager? . stopUpdatingLocation ( ) ;
380+ let uniqueId = UUID ( ) . uuidString
381+ let date = Date ( )
398382 let dateFormatter = DateFormatter ( ) ;
399383 dateFormatter. dateFormat = " yyyyMMdd_HHmmss " ;
400384
401385 if let chosenImage = info [ . originalImage] as? UIImage ,
402386 let documentsDirectory = FileManager . default. urls ( for: . documentDirectory, in: . userDomainMask) . first {
403387 DispatchQueue . global ( qos: . userInitiated) . async { [ self ] in
404388 let attachmentsDirectory = documentsDirectory. appendingPathComponent ( " attachments " ) ;
405- let fileToWriteTo = attachmentsDirectory. appendingPathComponent ( " MAGE_ \( dateFormatter . string ( from : Date ( ) ) ) .jpeg " ) ;
406- let originalFileToWriteTo = attachmentsDirectory. appendingPathComponent ( " MAGE_ \( dateFormatter. string ( from: Date ( ) ) ) _original.jpeg " ) ;
407-
389+ let fileToWriteTo = attachmentsDirectory. appendingPathComponent ( " MAGE_ \( uniqueId ) .jpeg " ) ;
390+ let originalFileToWriteTo = attachmentsDirectory. appendingPathComponent ( " MAGE_ \( uniqueId ) \( dateFormatter. string ( from: date ) ) _original.jpeg " ) ;
391+
408392 do {
409393 try FileManager . default. createDirectory ( at: fileToWriteTo. deletingLastPathComponent ( ) , withIntermediateDirectories: true , attributes: [ . protectionKey : FileProtectionType . complete] ) ;
410394 guard let imageData = chosenImage. qualityScaled ( ) else { return } ;
@@ -428,13 +412,13 @@ extension AttachmentCreationCoordinator: UIImagePickerControllerDelegate {
428412 guard let originalWithGPS = writeMetadataIntoImageData ( imagedata: originalImageData, metadata: NSDictionary ( dictionary: metadata) ) else { return } ;
429413 do {
430414 try originalWithGPS. write ( to: originalFileToWriteTo, options: . completeFileProtection)
431-
415+
432416 try ? PHPhotoLibrary . shared ( ) . performChangesAndWait {
433417 PHAssetChangeRequest . creationRequestForAssetFromImage ( atFileURL: originalFileToWriteTo)
434418 }
435419
436420 try FileManager . default. removeItem ( at: originalFileToWriteTo) ;
437-
421+
438422 } catch {
439423 MageLogger . misc. error ( " Unable to write image to file \( originalFileToWriteTo) : \( error) " )
440424 }
@@ -532,19 +516,18 @@ extension AttachmentCreationCoordinator: UIImagePickerControllerDelegate {
532516 }
533517 return nil ;
534518 }
535-
519+
536520 func handleMovie( picker: UIImagePickerController , info: [ UIImagePickerController . InfoKey : Any ] ) {
537521 MageLogger . misc. debug ( " handling movie \( info) " )
538- let dateFormatter = DateFormatter ( ) ;
539- dateFormatter. dateFormat = " yyyyMMdd_HHmmss " ;
522+ let uniqueId = UUID ( ) . uuidString
540523 guard let videoUrl = info [ . mediaURL] as? URL else { return }
541524 guard let documentsDirectory = FileManager . default. urls ( for: . documentDirectory, in: . userDomainMask) . first else { return }
542525
543526 if ( picker. sourceType == . camera && UIVideoAtPathIsCompatibleWithSavedPhotosAlbum ( videoUrl. path) ) {
544527 UISaveVideoAtPathToSavedPhotosAlbum ( videoUrl. path, nil , nil , nil ) ;
545528 }
546529 let attachmentsDirectory = documentsDirectory. appendingPathComponent ( " attachments " ) ;
547- let fileToWriteTo = attachmentsDirectory. appendingPathComponent ( " MAGE_ \( dateFormatter . string ( from : Date ( ) ) ) .mp4 " ) ;
530+ let fileToWriteTo = attachmentsDirectory. appendingPathComponent ( " MAGE_ \( uniqueId ) .mp4 " ) ;
548531
549532 let videoQuality : String = videoUploadQuality ( ) ;
550533 MageLogger . misc. debug ( " video quality \( videoQuality) " )
@@ -563,22 +546,22 @@ extension AttachmentCreationCoordinator: UIImagePickerControllerDelegate {
563546 exportSession. exportAsynchronously {
564547 let foo = exportSession. status
565548 switch ( exportSession. status) {
566- case . completed:
567- print ( " Export complete " )
568- self . addAttachmentForSaving ( location: fileToWriteTo, contentType: " video/mp4 " )
569- case . failed:
570- print ( " Export Failed: \( String ( describing: exportSession. error? . localizedDescription) ) " )
571- case . cancelled:
572- print ( " Export cancelled " ) ;
573- case . unknown:
574- print ( " Unknown " )
575- case . waiting:
576- print ( " Waiting " )
577- case . exporting:
578- print ( " Exporting " )
579- @unknown default :
580- print ( " Unknown " )
581- }
549+ case . completed:
550+ print ( " Export complete " )
551+ self . addAttachmentForSaving ( location: fileToWriteTo, contentType: " video/mp4 " )
552+ case . failed:
553+ print ( " Export Failed: \( String ( describing: exportSession. error? . localizedDescription) ) " )
554+ case . cancelled:
555+ print ( " Export cancelled " ) ;
556+ case . unknown:
557+ print ( " Unknown " )
558+ case . waiting:
559+ print ( " Waiting " )
560+ case . exporting:
561+ print ( " Exporting " )
562+ @unknown default :
563+ print ( " Unknown " )
564+ }
582565 }
583566 } catch {
584567 MageLogger . misc. error ( " Error creating directory path \( fileToWriteTo. deletingLastPathComponent ( ) ) : \( error) " )
@@ -603,12 +586,8 @@ extension AttachmentCreationCoordinator: UIDocumentPickerDelegate {
603586 func documentPicker( _ controller: UIDocumentPickerViewController , didPickDocumentsAt urls: [ URL ] ) {
604587 for url in urls {
605588 let securityScoped = url. startAccessingSecurityScopedResource ( )
606-
607- let dateFormatter = DateFormatter ( ) ;
608- dateFormatter. dateFormat = " yyyyMMdd_HHmmss " ;
609-
589+ let uniqueId = UUID ( ) . uuidString
610590 let uttype = try ? url. resourceValues ( forKeys: [ . contentTypeKey] ) . contentType
611-
612591 let fileType = uttype? . preferredFilenameExtension ?? url. pathExtension
613592 let mimeType = uttype? . preferredMIMEType ?? UTType . data. identifier
614593
@@ -619,11 +598,11 @@ extension AttachmentCreationCoordinator: UIDocumentPickerDelegate {
619598 let attachmentsDirectory = documentsDirectory. appendingPathComponent ( " attachments " )
620599 let urlWithoutExtension = url. deletingPathExtension ( )
621600 let filename = urlWithoutExtension. lastPathComponent
622- let fileToWriteTo = attachmentsDirectory. appendingPathComponent ( " MAGE_ \( filename) _ \( dateFormatter . string ( from : Date ( ) ) ) . \( fileType) " ) ;
601+ let fileToWriteTo = attachmentsDirectory. appendingPathComponent ( " MAGE_ \( filename) _ \( uniqueId ) . \( fileType) " ) ;
623602
624603 do {
625604 try FileManager . default. createDirectory ( at: fileToWriteTo. deletingLastPathComponent ( ) , withIntermediateDirectories: true , attributes: [ . protectionKey : FileProtectionType . complete] ) ;
626-
605+
627606
628607 do {
629608 let attachmentData = try Data ( contentsOf: url)
@@ -653,7 +632,7 @@ extension AttachmentCreationCoordinator: AudioRecordingDelegate {
653632 func recordingAvailable( recording: Recording ) {
654633 MageLogger . misc. debug ( " Recording available " )
655634 addAttachmentForSaving ( location: URL ( fileURLWithPath: recording. filePath!) , contentType: recording. mediaType!)
656-
635+
657636 self . audioRecorderViewController? . dismiss ( animated: true , completion: nil ) ;
658637 }
659638}
0 commit comments