Skip to content

Commit 37dcc46

Browse files
cwerthomnidanbenner-vegadanbennerjmckomnipaulsolt-ofsw
authored
Testflight Release 4.3.0 (#157)
* Performance Fixes for 50K Observations issue (#141) * preventing updateDiff from doing work twice * updated date formatter to use new ISO8601 formatter (create once and reuse - new formatter is faster than old formatter) * Fetch users and observation.remoteIds before batchImport. Some cleanup. --------- Co-authored-by: dannybenner <[email protected]> * added codeowneers file. * Core Data Concurrency Fixes (#147) * Fixes Core Data concurrency issues related to location and teams * Update return type to non-optional. We should never insert an optional into NSMutableArray, that's a run-time exception. * fixed and tested * Reverted all changes, commented thoroughly, modified to use **<=** * reverted indentation * Local Login View Modernization (#150) * Created view model and SwiftUI components to replace the local login view Still needs some clean-up * Added Previews to new SwiftUI view. * Refactored into reusable pieces. Adding back some missing business logic. Added previews to new SwiftUI views. * Removed files no longer needed. * Added line wrapping to the error message. * Removed commented out code Disabled autocapitalization in the Password field, it was already disabled in the username field. * File not needed so removing * Jmcdougall/bug 1451 new branch multi photo selection (#152) * Allowed for multi-selection media picker * MVP for multi photo selector * Created breaks after handling image assets to keep from creating duplicate images and videos * Fixes to handle photo and video duplicates * Form UX Field Improvement (#151) * Improved form navigation. * Cleaned out code no longer needed. * Removed commented out code. * Fixed the glitch, made it so you ".onlyFromCache()" when appropriate network selected * identical fix but for location summary * removed comment * Disabled or commented out problem tests. (#154) The affected tests will be fixed or replaced as soon as possible. * Add intro screens before login (#155) * Intro Login Views MVP * Intro Views MVP * Removed unnecessary image files * Added new strings in String Catalog * Fixed object version number issue --------- Co-authored-by: James McDougall <[email protected]> --------- Co-authored-by: Dan Benner <[email protected]> Co-authored-by: dannybenner <[email protected]> Co-authored-by: John McKeever <[email protected]> Co-authored-by: Paul Solt <[email protected]> Co-authored-by: Brent Michalski <[email protected]> Co-authored-by: James McDougall <[email protected]> Co-authored-by: James McDougall <[email protected]>
1 parent 22a1ba9 commit 37dcc46

File tree

178 files changed

+2545
-1272
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

178 files changed

+2545
-1272
lines changed

CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* @ngageoint/mage

MAGE.xcodeproj/project.pbxproj

Lines changed: 52 additions & 10 deletions
Large diffs are not rendered by default.

MAGE.xctestplan

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,21 @@
3838
},
3939
"testTargets" : [
4040
{
41-
"parallelizable" : true,
41+
"parallelizable" : false,
4242
"skippedTests" : [
43+
"AttachmentFieldViewTests\/testSetOneAttachmentThatIsSyncedAndOneThatIsNot()",
44+
"AttachmentFieldViewTests\/testSetOneAttachmentThatIsSyncedAndOneThatIsNotDifferentOrder()",
45+
"AttachmentFieldViewTests\/testSetOneAttachmentWithObservationAndTwoLater()",
46+
"AttachmentFieldViewTests\/testSetOneAttachmentWithObservationAndTwoLaterThenRemoveFirst()",
47+
"AttachmentFieldViewTests\/testSetOneAttachmentWithObservationAndTwoLaterThenRemoveSecond()",
48+
"AttachmentFieldViewTests\/testShouldCallTheAttachmentSelectionDelegateOnTap()",
4349
"AttachmentPushServiceTests",
50+
"AuthenticationCoordinatorTests",
4451
"CanCreateObservationTests\/testInitializeTheCanCreateObservationAndLongPressTheMap()",
52+
"EventChooserControllerTests",
4553
"EventChooserCoordinatorTests",
4654
"FeedItemsViewControllerNoTimestampTests",
47-
"FeedItemsViewControllerNoTimestampTests\/testOneFeedItemWithPrimaryAndSecondaryValueAndIcon()",
48-
"FeedItemsViewControllerNoTimestampTests\/testOneFeedItemWithPrimaryValue()",
55+
"FeedItemsViewControllerNoTimestampTests\/testOneFeedItemNoContent()",
4956
"FeedItemsViewControllerWithTimestampTests",
5057
"FeedItemsViewControllerWithTimestampTests\/testOneFeedItemWithPrimaryAndSecondaryValueAndIconWithoutTimestamp()",
5158
"MageServerTestsSwift\/testShouldFailWhenAnImageIsrReturned()",
@@ -68,6 +75,7 @@
6875
"parallelizable" : false,
6976
"skippedTests" : [
7077
"GeoPackageImporterTests\/testImportGeoPackageFileIntoLayer()",
78+
"GeoPackageImporterUITests\/testShouldHandleGeoPackageImportTwiceOverwrite()",
7179
"GeoPackageLayerMapTests\/testInitializeTheStaticLayerMapWithANotLoadedLayerThenLoadItButDontAddItToTheMap()",
7280
"GeoPackageTests\/testAddGeoPackageTileLayerThenReAdd()",
7381
"GeoPackageTests\/testGetFeatureKeys()"

Mage/AttachmentCreationCoordinator.swift

Lines changed: 52 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -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
}

Mage/AttachmentFieldView.swift

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import UIKit
1717
@objc func addGalleryAttachment();
1818
}
1919

20-
class AttachmentFieldView : BaseFieldView {
20+
class AttachmentFieldView : BaseFieldView, UICollectionViewDelegate {
2121
private var attachments: [AttachmentModel]?;
2222
private weak var attachmentSelectionDelegate: AttachmentSelectionDelegate?;
2323
var attachmentCreationCoordinator: AttachmentCreationCoordinator?;
@@ -42,10 +42,8 @@ class AttachmentFieldView : BaseFieldView {
4242
var cv: UICollectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: layout);
4343
cv.configureForAutoLayout();
4444
cv.register(AttachmentCell.self, forCellWithReuseIdentifier: "AttachmentCell");
45-
cv.delegate = attachmentCollectionDataStore;
45+
cv.delegate = self;
4646
cv.dataSource = attachmentCollectionDataStore;
47-
cv.accessibilityLabel = "Attachment Collection";
48-
cv.accessibilityIdentifier = "Attachment Collection";
4947
attachmentCollectionDataStore.attachmentCollection = cv;
5048
return cv;
5149
}();
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"colors" : [
3+
{
4+
"color" : {
5+
"color-space" : "srgb",
6+
"components" : {
7+
"alpha" : "1.000",
8+
"blue" : "0.957",
9+
"green" : "0.408",
10+
"red" : "0.098"
11+
}
12+
},
13+
"idiom" : "universal"
14+
},
15+
{
16+
"appearances" : [
17+
{
18+
"appearance" : "luminosity",
19+
"value" : "dark"
20+
}
21+
],
22+
"color" : {
23+
"color-space" : "srgb",
24+
"components" : {
25+
"alpha" : "1.000",
26+
"blue" : "0.957",
27+
"green" : "0.408",
28+
"red" : "0.098"
29+
}
30+
},
31+
"idiom" : "universal"
32+
}
33+
],
34+
"info" : {
35+
"author" : "xcode",
36+
"version" : 1
37+
}
38+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"colors" : [
3+
{
4+
"color" : {
5+
"color-space" : "srgb",
6+
"components" : {
7+
"alpha" : "1.000",
8+
"blue" : "0.996",
9+
"green" : "0.843",
10+
"red" : "0.180"
11+
}
12+
},
13+
"idiom" : "universal"
14+
},
15+
{
16+
"appearances" : [
17+
{
18+
"appearance" : "luminosity",
19+
"value" : "dark"
20+
}
21+
],
22+
"color" : {
23+
"color-space" : "srgb",
24+
"components" : {
25+
"alpha" : "1.000",
26+
"blue" : "0.996",
27+
"green" : "0.843",
28+
"red" : "0.180"
29+
}
30+
},
31+
"idiom" : "universal"
32+
}
33+
],
34+
"info" : {
35+
"author" : "xcode",
36+
"version" : 1
37+
}
38+
}

0 commit comments

Comments
 (0)