Add view of all images for a cap

This commit is contained in:
Christoph Hagen 2023-02-19 00:38:52 +01:00
parent fb90a4847e
commit 9cf1c236e0
8 changed files with 134 additions and 10 deletions

View File

@ -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 = "<group>"; };
88DBE72D285495B100D1573B /* FancyTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FancyTextField.swift; sourceTree = "<group>"; };
E20D1049285612AF0019BD91 /* ImageGrid.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageGrid.swift; sourceTree = "<group>"; };
E20D104B28563DB10019BD91 /* ImageCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCache.swift; sourceTree = "<group>"; };
@ -187,6 +189,7 @@
E2EA00E0283F658E00F7B269 /* SettingsView.swift */,
E2EA00CB283EB43E00F7B269 /* SortCaseRowView.swift */,
E2EA00C6283EAA0100F7B269 /* SortSelectionView.swift */,
88C1511B29A11ADF0080EF4F /* CapImagesView.swift */,
);
path = Views;
sourceTree = "<group>";
@ -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" */ = {

View File

@ -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"
}
}
],

View File

@ -55,6 +55,12 @@ struct ContentView: View {
@State
private var selectedCapId: Int?
@State
var showImageOverviewForCap = false
@State
private var selectedCapToShowImages: Cap?
var filteredCaps: [Cap] {
let text = searchString
.trimmingCharacters(in: .whitespacesAndNewlines)
@ -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 {

View File

@ -24,13 +24,21 @@ 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.
- Parameter id: The unique id of the cap

View File

@ -6,3 +6,8 @@ struct CapImage: Codable, Equatable, Hashable {
let version: Int
}
extension CapImage: Identifiable {
var id: Int { version }
}

View File

@ -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 {
@ -359,6 +364,10 @@ final class Database: ObservableObject {
return false
}
func cap(for id: Int) -> Cap? {
caps[id]
}
// MARK: Adding new data
func save(newCap name: String) -> Cap {

View File

@ -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<Cap?>, database: Database, isPresented: Binding<Bool>) {
self.database = database
self._cap = cap
self._isPresented = isPresented
}
let database: Database
var images: [CapImage] {
guard let cap else {
return []
}
return (0..<cap.imageCount).map {
cap.image(version: $0)
}
}
var title: String {
guard let cap else {
return "Images"
}
return "Cap \(cap.id)"
}
var body: some View {
GeometryReader { geo in
VStack {
HStack {
Text(title).font(.title2).bold()
Spacer()
Button(action: { isPresented = false }) {
Image(systemSymbol: .xmarkCircleFill)
.foregroundColor(.gray)
.font(.system(size: 26))
}
}
let gridItem = GridItem(.flexible(), spacing: 10, alignment: .leading)
ScrollView(.vertical) {
LazyVGrid(columns: [gridItem, gridItem, gridItem, gridItem]) {
ForEach(images) { item in
CachedCapImage(
item,
check: { database.images.cachedImage(item) },
fetch: { await database.images.image(item) },
content: { $0.resizable() },
placeholder: { ProgressView() })
.frame(width: imageSize,
height: imageSize)
.clipShape(Circle())
}
}
}
}
.padding(.horizontal)
}
}
}
struct CapImagesView_Previews: PreviewProvider {
static var previews: some View {
CapImagesView(cap: .constant(.init(id: 123, name: "Some")), database: .mock, isPresented: .constant(true))
}
}