Add chapters, history, bookmarks, live download progress, and i18n
- Chapter navigation with auto-scroll to current chapter and end-of-chapter sleep timer - Opt-in listening history (local-only) with XML export and per-item quick menu - Bookmarks with server sync via Audiobookshelf API - Live MB counter during downloads via URLSessionDownloadTask delegate - In-progress downloads shown in "Heruntergeladen" with dimmed cover + ring overlay - Cover image cache (50 MB memory / 500 MB disk URLCache) - German/English localization (de.lproj, en.lproj) - Loading spinner now triggers immediately on view switch instead of waiting for the network Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -106,6 +106,10 @@ final class ABSClient {
|
||||
)
|
||||
item.mediaType = mediaType
|
||||
item.description = meta?.description
|
||||
item.chapters = (raw.media?.chapters ?? []).compactMap { c in
|
||||
guard let id = c.id, let start = c.start, let end = c.end, let title = c.title else { return nil }
|
||||
return Chapter(id: id, start: start, end: end, title: title)
|
||||
}
|
||||
return item
|
||||
}
|
||||
|
||||
@@ -225,4 +229,43 @@ final class ABSClient {
|
||||
}
|
||||
|
||||
var bearerHeader: [String: String] { ["Authorization": "Bearer \(auth.token)"] }
|
||||
|
||||
// MARK: - Bookmarks
|
||||
|
||||
func fetchBookmarks(itemId: String, episodeId: String? = nil) async throws -> [ServerBookmark] {
|
||||
let req = try makeRequest(path: progressPath(itemId: itemId, episodeId: episodeId))
|
||||
let (data, response) = try await session.data(for: req)
|
||||
guard let http = response as? HTTPURLResponse else { return [] }
|
||||
if http.statusCode == 404 { return [] }
|
||||
guard (200..<300).contains(http.statusCode) else { throw ABSClientError.httpStatus(http.statusCode) }
|
||||
let dto = try JSONDecoder().decode(ProgressResponseDTO.self, from: data)
|
||||
return (dto.bookmarks ?? []).compactMap { b in
|
||||
guard let title = b.title, let time = b.time else { return nil }
|
||||
return ServerBookmark(title: title, time: time, createdAt: b.createdAt)
|
||||
}
|
||||
}
|
||||
|
||||
func createBookmark(itemId: String, time: Double, title: String) async throws {
|
||||
let body = try JSONSerialization.data(withJSONObject: ["title": title, "time": time])
|
||||
let req = try makeRequest(path: "/api/me/item/\(itemId)/bookmark", method: "POST", body: body)
|
||||
let (_, response) = try await session.data(for: req)
|
||||
guard let http = response as? HTTPURLResponse, (200..<300).contains(http.statusCode) else {
|
||||
throw ABSClientError.httpStatus((response as? HTTPURLResponse)?.statusCode ?? 0)
|
||||
}
|
||||
}
|
||||
|
||||
func deleteBookmark(itemId: String, time: Double) async throws {
|
||||
let timeInt = Int(time)
|
||||
let req = try makeRequest(path: "/api/me/item/\(itemId)/bookmark/\(timeInt)", method: "DELETE")
|
||||
let (_, response) = try await session.data(for: req)
|
||||
guard let http = response as? HTTPURLResponse, (200..<300).contains(http.statusCode) else {
|
||||
throw ABSClientError.httpStatus((response as? HTTPURLResponse)?.statusCode ?? 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ServerBookmark {
|
||||
let title: String
|
||||
let time: Double
|
||||
let createdAt: Double?
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user