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
|
// MARK: Classification
|
||||||
|
|
||||||
/// The compiled recognition model on disk
|
/// The compiled recognition model on disk
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user