diff --git a/Caps.xcodeproj/project.pbxproj b/Caps.xcodeproj/project.pbxproj index 84eec13..63e342c 100644 --- a/Caps.xcodeproj/project.pbxproj +++ b/Caps.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 88C1511C29A11ADF0080EF4F /* CapImagesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88C1511B29A11ADF0080EF4F /* CapImagesView.swift */; }; 88DBE72E285495B100D1573B /* FancyTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88DBE72D285495B100D1573B /* FancyTextField.swift */; }; E20D104A285612AF0019BD91 /* ImageGrid.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20D1049285612AF0019BD91 /* ImageGrid.swift */; }; E20D104C28563DB10019BD91 /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20D104B28563DB10019BD91 /* ImageCache.swift */; }; @@ -50,6 +51,7 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 88C1511B29A11ADF0080EF4F /* CapImagesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CapImagesView.swift; sourceTree = ""; }; 88DBE72D285495B100D1573B /* FancyTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FancyTextField.swift; sourceTree = ""; }; E20D1049285612AF0019BD91 /* ImageGrid.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageGrid.swift; sourceTree = ""; }; E20D104B28563DB10019BD91 /* ImageCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCache.swift; sourceTree = ""; }; @@ -187,6 +189,7 @@ E2EA00E0283F658E00F7B269 /* SettingsView.swift */, E2EA00CB283EB43E00F7B269 /* SortCaseRowView.swift */, E2EA00C6283EAA0100F7B269 /* SortSelectionView.swift */, + 88C1511B29A11ADF0080EF4F /* CapImagesView.swift */, ); path = Views; sourceTree = ""; @@ -285,6 +288,7 @@ files = ( E20D10582858CEBD0019BD91 /* IconButton.swift in Sources */, E25AAC7E283D855D006E9E7F /* ContentView.swift in Sources */, + 88C1511C29A11ADF0080EF4F /* CapImagesView.swift in Sources */, E2EA00F328438E6B00F7B269 /* CapNameEntryView.swift in Sources */, E25AAC8B283D868D006E9E7F /* Classifier.swift in Sources */, E20D10562858CDFA0019BD91 /* View+Extensions.swift in Sources */, @@ -457,7 +461,7 @@ INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 15.4; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -489,7 +493,7 @@ INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 15.4; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -532,7 +536,7 @@ repositoryURL = "https://github.com/SFSafeSymbols/SFSafeSymbols"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 3.0.0; + minimumVersion = 4.0.0; }; }; E2EA00C8283EACB200F7B269 /* XCRemoteSwiftPackageReference "bottom-sheet" */ = { diff --git a/Caps.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Caps.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 3547906..9d32ecf 100644 --- a/Caps.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Caps.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -14,8 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/SFSafeSymbols/SFSafeSymbols", "state" : { - "revision" : "c8c33d947d8a1c883aa19fd24e14fd738b06e369", - "version" : "3.3.2" + "revision" : "7cca2d60925876b5953a2cf7341cd80fbeac983c", + "version" : "4.1.1" } } ], diff --git a/Caps.xcodeproj/project.xcworkspace/xcuserdata/imac.xcuserdatad/UserInterfaceState.xcuserstate b/Caps.xcodeproj/project.xcworkspace/xcuserdata/imac.xcuserdatad/UserInterfaceState.xcuserstate index acccdda..0bedba2 100644 Binary files a/Caps.xcodeproj/project.xcworkspace/xcuserdata/imac.xcuserdatad/UserInterfaceState.xcuserstate and b/Caps.xcodeproj/project.xcworkspace/xcuserdata/imac.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/Caps/ContentView.swift b/Caps/ContentView.swift index 0163ace..a69b16f 100644 --- a/Caps/ContentView.swift +++ b/Caps/ContentView.swift @@ -54,6 +54,12 @@ struct ContentView: View { @State private var selectedCapId: Int? + + @State + var showImageOverviewForCap = false + + @State + private var selectedCapToShowImages: Cap? var filteredCaps: [Cap] { let text = searchString @@ -125,7 +131,7 @@ struct ContentView: View { .onTapGesture { didTap(cap: cap) } - .swipeActions() { + .swipeActions(edge: .trailing) { Button { showRenameWindow(for: cap) } label: { @@ -133,6 +139,14 @@ struct ContentView: View { } .tint(.purple) } + .swipeActions(edge: .leading) { + Button { + showAllImages(for: cap) + } label: { + Label("Images", systemSymbol: .photoStack) + } + .tint(.purple) + } } .refreshable { refresh() @@ -260,7 +274,11 @@ struct ContentView: View { } .sheet(isPresented: $showGridView) { GridView(isPresented: $showGridView, database: database) - }.alert(isPresented: $showNewClassifierAlert) { + } + .bottomSheet(isPresented: $showImageOverviewForCap, height: 400) { + CapImagesView(cap: $selectedCapToShowImages, database: database, isPresented: $showImageOverviewForCap) + } + .alert(isPresented: $showNewClassifierAlert) { Alert(title: Text("New classifier available"), message: Text("Classifier \(database.serverClassifierVersion) is available. You have version \(database.classifierVersion). Do you want to download it now?"), primaryButton: .default(Text("Download"), action: downloadClassifier), @@ -377,6 +395,11 @@ struct ContentView: View { return } } + + private func showAllImages(for cap: Cap) { + selectedCapToShowImages = cap + showImageOverviewForCap = true + } } struct ContentView_Previews: PreviewProvider { diff --git a/Caps/Data/Cap.swift b/Caps/Data/Cap.swift index 6e0fdbe..32c88fc 100644 --- a/Caps/Data/Cap.swift +++ b/Caps/Data/Cap.swift @@ -24,12 +24,20 @@ struct Cap { /// The subpath to the main image on the server var mainImagePath: String { - String(format: "images/%04d/%04d-%02d.jpg", id, id, mainImage) + imagePath(version: mainImage) + } + + func imagePath(version: Int) -> String { + String(format: "images/%04d/%04d-%02d.jpg", id, id, version) } var image: CapImage { .init(cap: id, version: mainImage) } + + func image(version: Int) -> CapImage { + .init(cap: id, version: version) + } /** Create a new cap. diff --git a/Caps/Data/CapImage.swift b/Caps/Data/CapImage.swift index 836e495..efaa68c 100644 --- a/Caps/Data/CapImage.swift +++ b/Caps/Data/CapImage.swift @@ -6,3 +6,8 @@ struct CapImage: Codable, Equatable, Hashable { let version: Int } + +extension CapImage: Identifiable { + + var id: Int { version } +} diff --git a/Caps/Data/Database.swift b/Caps/Data/Database.swift index 04b131d..1d9bc81 100644 --- a/Caps/Data/Database.swift +++ b/Caps/Data/Database.swift @@ -241,7 +241,7 @@ final class Database: ObservableObject { @discardableResult func downloadCaps() async -> Bool { - print("Downloading cap data") + print("Downloading cap data from \(serverDbUrl)") let data: Data let response: URLResponse do { @@ -250,7 +250,11 @@ final class Database: ObservableObject { print("Failed to download classifier version: \(error)") return false } - guard (response as? HTTPURLResponse)?.statusCode == 200 else { + guard let httpResponse = response as? HTTPURLResponse else { + return false + } + guard httpResponse.statusCode == 200 else { + print("Failed to download caps: \(httpResponse.statusCode)") return false } @@ -261,6 +265,7 @@ final class Database: ObservableObject { print("Failed to decode server database: \(error)") return false } + print("Downloaded \(capData) caps") var inserts = 0 var updates = 0 for cap in capData { @@ -358,6 +363,10 @@ final class Database: ObservableObject { func hasPendingOperations(for cap: Int) -> Bool { return false } + + func cap(for id: Int) -> Cap? { + caps[id] + } // MARK: Adding new data diff --git a/Caps/Views/CapImagesView.swift b/Caps/Views/CapImagesView.swift new file mode 100644 index 0000000..a926ad5 --- /dev/null +++ b/Caps/Views/CapImagesView.swift @@ -0,0 +1,75 @@ +import SwiftUI + +struct CapImagesView: View { + + private let imageSize: CGFloat = 70 + + @Binding + var cap: Cap? + + @Binding + var isPresented: Bool + + init(cap: Binding, database: Database, isPresented: Binding) { + self.database = database + self._cap = cap + self._isPresented = isPresented + } + + let database: Database + + var images: [CapImage] { + guard let cap else { + return [] + } + return (0..