Add image deletion and main selection

This commit is contained in:
Christoph Hagen 2023-03-12 12:14:38 +01:00
parent 9ed0be8cc1
commit 441197144f
3 changed files with 148 additions and 9 deletions

View File

@ -597,6 +597,76 @@ final class Database: ObservableObject {
} }
} }
func setMainImage(_ version: Int, for capId: Int) async -> Cap? {
guard hasServerAuthentication else {
log("No authorization to set main image")
return nil
}
guard var cap = cap(for: capId) else {
log("No cap \(capId) to set main image")
return nil
}
guard version < cap.imageCount else {
log("Invalid main image \(version) for \(capId) with only \(cap.imageCount) images")
return nil
}
cap.mainImage = version
let finalCap = cap
DispatchQueue.main.async {
self.caps[capId] = finalCap
log("Set main image \(version) for \(capId)")
}
return finalCap
}
func delete(image: CapImage) async -> Bool {
guard hasServerAuthentication else {
log("No authorization to set main image")
return false
}
guard var cap = cap(for: image.cap) else {
log("No cap \(image.cap) to set main image")
return false
}
guard image.version < cap.imageCount else {
log("Invalid main image \(image.version) for \(cap.id) with only \(cap.imageCount) images")
return false
}
let url = serverUrl
.appendingPathComponent("delete/\(cap.id)/\(image.version)")
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.addValue(serverAuthenticationKey, forHTTPHeaderField: "key")
do {
let (data, response) = try await URLSession.shared.data(for: request)
guard let httpResponse = response as? HTTPURLResponse else {
log("Unexpected response deleting image \(image.version) of cap \(cap.id): \(response)")
return false
}
guard httpResponse.statusCode == 200 else {
log("Failed to delete image \(image.version) of cap \(cap.id): Response \(httpResponse.statusCode)")
return false
}
let newCap: Cap
do {
newCap = try JSONDecoder().decode(Cap.self, from: data)
} catch {
log("Invalid response data deleting image \(image.version) of cap \(cap.id): \(data)")
return false
}
// Delete cached images
images.removeCachedImages(for: cap.id)
// Update cap
caps[newCap.id] = newCap
log("Deleted image \(image.version) of cap \(cap.id)")
return true
} catch {
log("Failed to delete image \(image.version) of cap \(cap.id): \(error)")
return false
}
}
// MARK: Classification // MARK: Classification
/// The compiled recognition model on disk /// The compiled recognition model on disk

View File

@ -106,6 +106,30 @@ final class ImageCache {
return saveImage(image, at: downloadedImageUrl) return saveImage(image, at: downloadedImageUrl)
} }
@discardableResult
func removeCachedImages(for cap: Int) -> Bool {
let prefix = String(format: "%04d-", cap)
let files: [URL]
do {
files = try fm.contentsOfDirectory(atPath: folder.path)
.filter { $0.hasPrefix(prefix) }
.map { folder.appendingPathComponent($0) }
} catch {
log("Failed to get image cache file list: \(error)")
return false
}
var isSuccessful = true
for url in files {
do {
try fm.removeItem(at: url)
} catch {
isSuccessful = false
log("Failed to remove image \(url.lastPathComponent) from cache: \(error)")
}
}
return isSuccessful
}
private func loadRemoteImage(_ image: CapImage) async -> URL? { private func loadRemoteImage(_ image: CapImage) async -> URL? {
let remoteURL = remoteImageUrl(image) let remoteURL = remoteImageUrl(image)
return await loadRemoteImage(at: remoteURL) return await loadRemoteImage(at: remoteURL)

View File

@ -10,6 +10,10 @@ struct CapImagesView: View {
@Binding @Binding
var isPresented: Bool var isPresented: Bool
@State
private var selectedCap: CapImage?
init(cap: Binding<Cap?>, database: Database, isPresented: Binding<Bool>) { init(cap: Binding<Cap?>, database: Database, isPresented: Binding<Bool>) {
self.database = database self.database = database
self._cap = cap self._cap = cap
@ -50,6 +54,7 @@ struct CapImagesView: View {
ScrollView(.vertical) { ScrollView(.vertical) {
LazyVGrid(columns: [gridItem, gridItem, gridItem, gridItem]) { LazyVGrid(columns: [gridItem, gridItem, gridItem, gridItem]) {
ForEach(images) { item in ForEach(images) { item in
ZStack(alignment: .topLeading) {
CachedCapImage( CachedCapImage(
item, item,
check: { database.images.cachedImage(item) }, check: { database.images.cachedImage(item) },
@ -59,12 +64,52 @@ struct CapImagesView: View {
.frame(width: imageSize, .frame(width: imageSize,
height: imageSize) height: imageSize)
.clipShape(Circle()) .clipShape(Circle())
.onLongPressGesture { selectedCap = item }
if item.version == cap?.mainImage {
Image(systemSymbol: .checkmarkCircleFill)
.foregroundColor(.green)
.background(Color.white)
.clipShape(Circle())
}
}
} }
} }
} }
} }
.padding(.horizontal) .padding(.horizontal)
} }
.actionSheet(item: $selectedCap) { item in
ActionSheet(title: Text("Image \(item.version)"), buttons: [
.default(Text("Set as main image")) { setMainImage(item) },
.destructive(Text("Delete image")) { delete(image: item) },
.cancel()
])
}
}
private func delete(image: CapImage) {
Task {
guard let cap = await database.setMainImage(image.version, for: image.cap) else {
return
}
DispatchQueue.main.async {
self.cap = cap
}
}
selectedCap = nil
}
private func setMainImage(_ image: CapImage) {
Task {
guard let cap = await database.setMainImage(image.version, for: image.cap) else {
return
}
DispatchQueue.main.async {
self.cap = cap
}
}
self.selectedCap = nil
} }
} }