ReadKeep/URLShare/SimpleAPI.swift
Ilyas Hallak df8a7b64b2 feat: Add Kingfisher caching, card layouts, dynamic tag layout, and undo delete
- Integrate Kingfisher for image caching with CachedAsyncImage component
- Add CacheSettingsView for managing image cache size and clearing cache
- Implement three card layout styles: compact, magazine (default), natural
- Add AppearanceSettingsView with visual layout previews and theme settings
- Create Clean Architecture for card layout with domain models and use cases
- Implement FlowLayout for dynamic label width calculation
- Add skeleton loading animation for initial bookmark loads
- Replace delete confirmation dialogs with immediate delete + 3-second undo
- Support multiple simultaneous undo operations with individual progress bars
- Add grayed-out visual feedback for pending deletions
- Centralize notification names in dedicated NotificationNames file
- Remove pagination logic from label management (replaced with FlowLayout)
- Update AsyncImage usage across BookmarkCardView, BookmarkDetailView, ImageViewerView
- Improve UI consistency and spacing throughout the app
2025-09-04 10:43:27 +02:00

116 lines
5.3 KiB
Swift

import Foundation
class SimpleAPI {
private static let logger = Logger.network
// MARK: - API Methods
static func addBookmark(title: String, url: String, labels: [String]? = nil, showStatus: @escaping (String, Bool) -> Void) async {
logger.info("Adding bookmark: \(url)")
guard let token = KeychainHelper.shared.loadToken() else {
showStatus("No token found. Please log in via the main app.", true)
return
}
guard let endpoint = KeychainHelper.shared.loadEndpoint(), !endpoint.isEmpty else {
showStatus("No server endpoint found.", true)
return
}
let requestDto = CreateBookmarkRequestDto(url: url, title: title, labels: labels)
guard let requestData = try? JSONEncoder().encode(requestDto) else {
showStatus("Failed to encode request.", true)
return
}
guard let apiUrl = URL(string: endpoint + "/api/bookmarks") else {
showStatus("Invalid server endpoint.", true)
return
}
var request = URLRequest(url: apiUrl)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
request.httpBody = requestData
do {
let (data, response) = try await URLSession.shared.data(for: request)
guard let httpResponse = response as? HTTPURLResponse else {
logger.error("Invalid server response for bookmark creation")
showStatus("Invalid server response.", true)
return
}
logger.logNetworkRequest(method: "POST", url: "/api/bookmarks", statusCode: httpResponse.statusCode)
guard 200...299 ~= httpResponse.statusCode else {
if httpResponse.statusCode == 401 {
DispatchQueue.main.async {
NotificationCenter.default.post(name: .unauthorizedAPIResponse, object: nil)
}
}
let msg = String(data: data, encoding: .utf8) ?? "Unknown error"
logger.error("Server error \(httpResponse.statusCode): \(msg)")
showStatus("Server error: \(httpResponse.statusCode)\n\(msg)", true)
return
}
if let resp = try? JSONDecoder().decode(CreateBookmarkResponseDto.self, from: data) {
logger.info("Bookmark created successfully: \(resp.message)")
showStatus("Saved: \(resp.message)", false)
} else {
logger.info("Bookmark created successfully")
showStatus("Bookmark saved!", false)
}
} catch {
logger.logNetworkError(method: "POST", url: "/api/bookmarks", error: error)
showStatus("Network error: \(error.localizedDescription)", true)
}
}
static func getBookmarkLabels(showStatus: @escaping (String, Bool) -> Void) async -> [BookmarkLabelDto]? {
logger.info("Fetching bookmark labels")
guard let token = KeychainHelper.shared.loadToken() else {
showStatus("No token found. Please log in via the main app.", true)
return nil
}
guard let endpoint = KeychainHelper.shared.loadEndpoint(), !endpoint.isEmpty else {
showStatus("No server endpoint found.", true)
return nil
}
guard let apiUrl = URL(string: endpoint + "/api/bookmarks/labels") else {
showStatus("Invalid server endpoint.", true)
return nil
}
var request = URLRequest(url: apiUrl)
request.httpMethod = "GET"
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
do {
let (data, response) = try await URLSession.shared.data(for: request)
guard let httpResponse = response as? HTTPURLResponse else {
logger.error("Invalid server response for labels request")
showStatus("Invalid server response.", true)
return nil
}
logger.logNetworkRequest(method: "GET", url: "/api/bookmarks/labels", statusCode: httpResponse.statusCode)
guard 200...299 ~= httpResponse.statusCode else {
if httpResponse.statusCode == 401 {
DispatchQueue.main.async {
NotificationCenter.default.post(name: .unauthorizedAPIResponse, object: nil)
}
}
let msg = String(data: data, encoding: .utf8) ?? "Unknown error"
logger.error("Server error \(httpResponse.statusCode): \(msg)")
showStatus("Server error: \(httpResponse.statusCode)\n\(msg)", true)
return nil
}
let labels = try JSONDecoder().decode([BookmarkLabelDto].self, from: data)
logger.info("Successfully fetched \(labels.count) bookmark labels")
return labels
} catch {
logger.logNetworkError(method: "GET", url: "/api/bookmarks/labels", error: error)
showStatus("Network error: \(error.localizedDescription)", true)
return nil
}
}
}