Skip to main content

fiddle with album overlays, allow reviewing again

ID
1d997c8
date
2023-06-09 23:30:38+00:00
author
Alex Chan <alex@alexwlchan.net>
parent
75a3b5f
message
fiddle with album overlays, allow reviewing again
changed files
6 files, 146 additions, 24 deletions

Changed files

BlinkReviewer/BlinkReviewer.xcodeproj/project.pbxproj (31876) → BlinkReviewer/BlinkReviewer.xcodeproj/project.pbxproj (32342)

diff --git a/BlinkReviewer/BlinkReviewer.xcodeproj/project.pbxproj b/BlinkReviewer/BlinkReviewer.xcodeproj/project.pbxproj
index 81d626c..86ca30c 100644
--- a/BlinkReviewer/BlinkReviewer.xcodeproj/project.pbxproj
+++ b/BlinkReviewer/BlinkReviewer.xcodeproj/project.pbxproj
@@ -14,6 +14,7 @@
 		945F17B62A33D7AA004FC479 /* ReviewStateBorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 945F17B52A33D7AA004FC479 /* ReviewStateBorder.swift */; };
 		945F17B82A33DAC7004FC479 /* ReviewStateSaturation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 945F17B72A33DAC7004FC479 /* ReviewStateSaturation.swift */; };
 		94A0835E2A33E49E00238964 /* FocusedImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94A0835D2A33E49E00238964 /* FocusedImage.swift */; };
+		94A083612A33E98000238964 /* AlbumInfoOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94A083602A33E98000238964 /* AlbumInfoOverlay.swift */; };
 		94C5FFF22A33ADD4004ADDF5 /* PHFetchResultCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94C5FFF12A33ADD4004ADDF5 /* PHFetchResultCollection.swift */; };
 		94C5FFF62A33B698004ADDF5 /* PHAssetHStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94C5FFF52A33B698004ADDF5 /* PHAssetHStack.swift */; };
 		94D2C8B92A320E6F00BEE15B /* ReviewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94D2C8B82A320E6F00BEE15B /* ReviewState.swift */; };
@@ -60,6 +61,7 @@
 		945F17B52A33D7AA004FC479 /* ReviewStateBorder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReviewStateBorder.swift; sourceTree = "<group>"; };
 		945F17B72A33DAC7004FC479 /* ReviewStateSaturation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReviewStateSaturation.swift; sourceTree = "<group>"; };
 		94A0835D2A33E49E00238964 /* FocusedImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FocusedImage.swift; sourceTree = "<group>"; };
+		94A083602A33E98000238964 /* AlbumInfoOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumInfoOverlay.swift; sourceTree = "<group>"; };
 		94C5FFF12A33ADD4004ADDF5 /* PHFetchResultCollection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PHFetchResultCollection.swift; sourceTree = "<group>"; };
 		94C5FFF52A33B698004ADDF5 /* PHAssetHStack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PHAssetHStack.swift; sourceTree = "<group>"; };
 		94D2C8B82A320E6F00BEE15B /* ReviewState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReviewState.swift; sourceTree = "<group>"; };
@@ -136,6 +138,7 @@
 			isa = PBXGroup;
 			children = (
 				94A0835D2A33E49E00238964 /* FocusedImage.swift */,
+				94A083602A33E98000238964 /* AlbumInfoOverlay.swift */,
 			);
 			path = FocusedImage;
 			sourceTree = "<group>";
@@ -374,6 +377,7 @@
 				945F17B42A33D726004FC479 /* ReviewStateIcon.swift in Sources */,
 				945F17B62A33D7AA004FC479 /* ReviewStateBorder.swift in Sources */,
 				94D7511E2A31B243005859E7 /* FullSizeImage.swift in Sources */,
+				94A083612A33E98000238964 /* AlbumInfoOverlay.swift in Sources */,
 				94C5FFF22A33ADD4004ADDF5 /* PHFetchResultCollection.swift in Sources */,
 				94D750F02A31A796005859E7 /* BlinkReviewerApp.swift in Sources */,
 				940331732A336B5100200C5D /* DeferredRendering.swift in Sources */,

BlinkReviewer/BlinkReviewer/Photos/AssetHelpers.swift (2805) → BlinkReviewer/BlinkReviewer/Photos/AssetHelpers.swift (2360)

diff --git a/BlinkReviewer/BlinkReviewer/Photos/AssetHelpers.swift b/BlinkReviewer/BlinkReviewer/Photos/AssetHelpers.swift
index 3e29e8d..dec461e 100644
--- a/BlinkReviewer/BlinkReviewer/Photos/AssetHelpers.swift
+++ b/BlinkReviewer/BlinkReviewer/Photos/AssetHelpers.swift
@@ -8,21 +8,6 @@
 import Foundation
 import Photos
 
-/// Returns a list of all the images in the Photos Library.
-func getAllPhotos() -> [PHAsset] {
-    var photos: [PHAsset] = []
-    
-    let options = PHFetchOptions()
-    options.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: true)]
-    
-    PHAsset.fetchAssets(with: PHAssetMediaType.image, options: options)
-        .enumerateObjects({ (asset, _, _) in
-            photos.append(asset)
-        })
-    
-    return photos
-}
-
 extension PHAsset {
     /// Returns a list of all the albums that contain this asset.
     func albums() -> [PHAssetCollection] {

BlinkReviewer/BlinkReviewer/Photos/PhotosLibrary.swift (3802) → BlinkReviewer/BlinkReviewer/Photos/PhotosLibrary.swift (5792)

diff --git a/BlinkReviewer/BlinkReviewer/Photos/PhotosLibrary.swift b/BlinkReviewer/BlinkReviewer/Photos/PhotosLibrary.swift
index 7034eec..c431463 100644
--- a/BlinkReviewer/BlinkReviewer/Photos/PhotosLibrary.swift
+++ b/BlinkReviewer/BlinkReviewer/Photos/PhotosLibrary.swift
@@ -41,21 +41,51 @@ class PhotosLibrary: NSObject, ObservableObject, PHPhotoLibraryChangeObserver {
     
     private func updateStatus(_ changeInstance: PHChange) {
         DispatchQueue.main.async {
+            let start = DispatchTime.now()
+            var elapsed = start
+
+            func printElapsed(_ label: String) -> Void {
+              let now = DispatchTime.now()
+
+              let totalInterval = Double(now.uptimeNanoseconds - start.uptimeNanoseconds) / 1_000_000_000
+              let elapsedInterval = Double(now.uptimeNanoseconds - elapsed.uptimeNanoseconds) / 1_000_000_000
+
+              elapsed = DispatchTime.now()
+
+              print("Time to \(label):\n  \(elapsedInterval) seconds (\(totalInterval) total)")
+            }
+            
             let options = PHFetchOptions()
             options.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
             options.fetchLimit = 500
             
 //            print()
             
-            if let changeDetails = changeInstance.changeDetails(for: self.assets2) {
-                self.assets2 = changeDetails.fetchResultAfterChanges
+            if let assetsChangeDetails = changeInstance.changeDetails(for: self.assets2) {
+                self.assets2 = assetsChangeDetails.fetchResultAfterChanges
             } else {
                 self.assets2 = PHAsset.fetchAssets(with: PHAssetMediaType.image, options: options)
             }
-
-            self.approvedAssets = PHAsset.fetchAssets(in: self.approved, options: nil)
-            self.rejectedAssets = PHAsset.fetchAssets(in: self.rejected, options: nil)
-            self.needsActionAssets = PHAsset.fetchAssets(in: self.needsAction, options: nil)
+            
+            if let approvedChangeDetails = changeInstance.changeDetails(for: self.approvedAssets) {
+                self.approvedAssets = approvedChangeDetails.fetchResultAfterChanges
+            } else {
+                self.approvedAssets = PHAsset.fetchAssets(in: self.approved, options: nil)
+            }
+            
+            if let rejectedChangeDetails = changeInstance.changeDetails(for: self.rejectedAssets) {
+                self.rejectedAssets = rejectedChangeDetails.fetchResultAfterChanges
+            } else {
+                self.rejectedAssets = PHAsset.fetchAssets(in: self.rejected, options: nil)
+            }
+            
+            if let needsActionChangeDetails = changeInstance.changeDetails(for: self.needsActionAssets) {
+                self.needsActionAssets = needsActionChangeDetails.fetchResultAfterChanges
+            } else {
+                self.needsActionAssets = PHAsset.fetchAssets(in: self.needsAction, options: nil)
+            }
+            
+            printElapsed("get all photos data (update)")
             
             self.isPhotoLibraryAuthorized = PHPhotoLibrary.authorizationStatus() == .authorized
         }
@@ -64,6 +94,20 @@ class PhotosLibrary: NSObject, ObservableObject, PHPhotoLibraryChangeObserver {
 
     private func updateStatus(isChange: Bool) {
         DispatchQueue.main.async {
+            let start = DispatchTime.now()
+            var elapsed = start
+
+            func printElapsed(_ label: String) -> Void {
+              let now = DispatchTime.now()
+
+              let totalInterval = Double(now.uptimeNanoseconds - start.uptimeNanoseconds) / 1_000_000_000
+              let elapsedInterval = Double(now.uptimeNanoseconds - elapsed.uptimeNanoseconds) / 1_000_000_000
+
+              elapsed = DispatchTime.now()
+
+              print("Time to \(label):\n  \(elapsedInterval) seconds (\(totalInterval) total)")
+            }
+            
             let options = PHFetchOptions()
             options.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
             options.fetchLimit = 500
@@ -74,6 +118,8 @@ class PhotosLibrary: NSObject, ObservableObject, PHPhotoLibraryChangeObserver {
             self.rejectedAssets = PHAsset.fetchAssets(in: self.rejected, options: nil)
             self.needsActionAssets = PHAsset.fetchAssets(in: self.needsAction, options: nil)
             
+            printElapsed("get all photos data (new)")
+            
             self.isPhotoLibraryAuthorized = PHPhotoLibrary.authorizationStatus() == .authorized
         }
     }

BlinkReviewer/BlinkReviewer/Views/FocusedImage/AlbumInfoOverlay.swift (0) → BlinkReviewer/BlinkReviewer/Views/FocusedImage/AlbumInfoOverlay.swift (1833)

diff --git a/BlinkReviewer/BlinkReviewer/Views/FocusedImage/AlbumInfoOverlay.swift b/BlinkReviewer/BlinkReviewer/Views/FocusedImage/AlbumInfoOverlay.swift
new file mode 100644
index 0000000..885f4f2
--- /dev/null
+++ b/BlinkReviewer/BlinkReviewer/Views/FocusedImage/AlbumInfoOverlay.swift
@@ -0,0 +1,48 @@
+import SwiftUI
+import Photos
+
+/// Show the names of the albums that a given asset is in.
+///
+/// Each album is shown as a separate "pill" in the list, for example:
+///
+///     [Cats] [Cross-stitch] [Stuff I did in 2023]
+///
+struct AlbumInfoOverlay: ViewModifier {
+    @State var asset: PHAsset?
+    
+    // TODO: This doesn't update properly :-/
+    func body(content: Content) -> some View {
+        if let thisAsset = asset {
+            content.overlay(alignment: Alignment(horizontal: .center, vertical: .top)) {
+                HStack {
+                    ForEach(thisAsset.albums(), id: \.localIdentifier) { album in
+                        if let title = album.localizedTitle {
+                            // Don't show the names of the meta-albums used to manage
+                            // review state.
+                            if (title != "Approved" && title != "Rejected" && title != "Needs Action") {
+                                
+                                // The icon was chosen to match the one used for albums
+                                // in the sidebar in Photos.
+                                Text("\(Image(systemName: "rectangle.stack")) \(title)")
+                                    .fontWeight(.bold)
+                                    .font(.title2)
+                                    .padding(5)
+                                    .background(.white.opacity(0.9))
+                                    .cornerRadius(7.0)
+                                    .shadow(radius: 2.0)
+                            }
+                        }
+                    }
+                }.padding()
+            }
+        } else {
+            content
+        }
+    }
+}
+
+extension View {
+    func albumInfo(for asset: PHAsset?) -> some View {
+        modifier(AlbumInfoOverlay(asset: asset))
+    }
+}

BlinkReviewer/BlinkReviewer/Views/PHAssetImage.swift (4581) → BlinkReviewer/BlinkReviewer/Views/PHAssetImage.swift (4583)

diff --git a/BlinkReviewer/BlinkReviewer/Views/PHAssetImage.swift b/BlinkReviewer/BlinkReviewer/Views/PHAssetImage.swift
index 8163d28..d0cc05a 100644
--- a/BlinkReviewer/BlinkReviewer/Views/PHAssetImage.swift
+++ b/BlinkReviewer/BlinkReviewer/Views/PHAssetImage.swift
@@ -103,7 +103,7 @@ class PHAssetImage: NSObject, ObservableObject {
                         }
                         
                         if let imageResult = result {
-                            print("got image!")
+//                            print("got image!")
                             self.image = imageResult
                             
                             if !self.isDegraded {

BlinkReviewer/BlinkReviewer/Views/PhotoReviewer.swift (9584) → BlinkReviewer/BlinkReviewer/Views/PhotoReviewer.swift (11564)

diff --git a/BlinkReviewer/BlinkReviewer/Views/PhotoReviewer.swift b/BlinkReviewer/BlinkReviewer/Views/PhotoReviewer.swift
index 8a0d609..832a67a 100644
--- a/BlinkReviewer/BlinkReviewer/Views/PhotoReviewer.swift
+++ b/BlinkReviewer/BlinkReviewer/Views/PhotoReviewer.swift
@@ -45,13 +45,12 @@ struct PhotoReviewer: View {
                     .frame(height: 90)
                     
                     FocusedImage(assetImage: focusedAssetImage)
+                        .albumInfo(for: focusedAssetImage.asset)
                     
                     Spacer()
                 }
             }
             .onAppear {
-                
-                
                 NSEvent.addLocalMonitorForEvents(matching: .keyDown) { event in
                     handleKeyEventNew(event)
                     return event
@@ -137,6 +136,46 @@ struct PhotoReviewer: View {
                     focusedAssetIndex -= 1
                 }
             
+            case let e where e.characters == "1" || e.characters == "2" || e.characters == "3":
+                print("time to review!")
+                let state = photosLibrary.state(for: focusedAsset)
+            
+                let approved = getAlbum(withName: "Approved")
+                let rejected = getAlbum(withName: "Rejected")
+                let needsAction = getAlbum(withName: "Needs Action")
+            
+                try! PHPhotoLibrary.shared().performChangesAndWait {
+                    // Strictly speaking, the first condition is a combination of two:
+                    //
+                    //   1. The action is `toggle-approved` and the photo is approved,
+                    //      in which case toggling means un-approving it.
+                    //   2. The action is anything else and the photo is approved, in
+                    //      which case setting the new status means removing approved.
+                    //
+                    // Similar logic applies for all three conditions.
+                    if state == .Approved {
+                        focusedAsset.remove(fromAlbum: approved)
+                    } else if e.characters == "1" {
+                        focusedAsset.add(toAlbum: approved)
+                    }
+
+                    if state == .Rejected {
+                        focusedAsset.remove(fromAlbum: rejected)
+                    } else if e.characters == "2" {
+                        focusedAsset.add(toAlbum: rejected)
+                    }
+
+                    if state == .NeedsAction {
+                        focusedAsset.remove(fromAlbum: needsAction)
+                    } else if e.characters == "3" {
+                        focusedAsset.add(toAlbum: needsAction)
+                    }
+                }
+            
+                if selectedAssetIndex < photosLibrary.assets2.count - 1 {
+                    focusedAssetIndex += 1
+                }
+            
             default:
                 logger.info("Received unhandled keyboard event: \(event, privacy: .public)")
                 break