Add image deletion and main selection
This commit is contained in:
parent
9ed0be8cc1
commit
441197144f
@ -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
|
||||
|
||||
/// The compiled recognition model on disk
|
||||
|
@ -106,6 +106,30 @@ final class ImageCache {
|
||||
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? {
|
||||
let remoteURL = remoteImageUrl(image)
|
||||
return await loadRemoteImage(at: remoteURL)
|
||||
|
@ -10,6 +10,10 @@ struct CapImagesView: View {
|
||||
@Binding
|
||||
var isPresented: Bool
|
||||
|
||||
@State
|
||||
private var selectedCap: CapImage?
|
||||
|
||||
|
||||
init(cap: Binding<Cap?>, database: Database, isPresented: Binding<Bool>) {
|
||||
self.database = database
|
||||
self._cap = cap
|
||||
@ -50,6 +54,7 @@ struct CapImagesView: View {
|
||||
ScrollView(.vertical) {
|
||||
LazyVGrid(columns: [gridItem, gridItem, gridItem, gridItem]) {
|
||||
ForEach(images) { item in
|
||||
ZStack(alignment: .topLeading) {
|
||||
CachedCapImage(
|
||||
item,
|
||||
check: { database.images.cachedImage(item) },
|
||||
@ -59,12 +64,52 @@ struct CapImagesView: View {
|
||||
.frame(width: imageSize,
|
||||
height: imageSize)
|
||||
.clipShape(Circle())
|
||||
.onLongPressGesture { selectedCap = item }
|
||||
if item.version == cap?.mainImage {
|
||||
Image(systemSymbol: .checkmarkCircleFill)
|
||||
.foregroundColor(.green)
|
||||
.background(Color.white)
|
||||
.clipShape(Circle())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.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
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user