Files
ABS-Client/ABS Client/Audiobookshelf swift/Services/BookmarkManager.swift
Scarriffle fa47cae664 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>
2026-05-25 18:43:16 +02:00

87 lines
2.6 KiB
Swift

import Foundation
import Observation
struct Bookmark: Codable, Identifiable {
let id: UUID
let createdAt: Date
let itemId: String
let episodeId: String?
let itemTitle: String
var title: String
let chapterTitle: String?
let time: Double
}
@Observable
@MainActor
final class BookmarkManager {
private(set) var bookmarks: [Bookmark] = []
private var saveFile: URL {
AppPaths.supportDirectory.appendingPathComponent("bookmarks.json")
}
init() { load() }
func add(item: LibraryItem, time: Double, title: String, chapters: [Chapter]) {
let chapter = chapters.last { $0.start <= time }
let bm = Bookmark(
id: UUID(),
createdAt: Date(),
itemId: item.id,
episodeId: item.episodeId,
itemTitle: item.title,
title: title,
chapterTitle: chapter?.title,
time: time
)
bookmarks.insert(bm, at: 0)
save()
}
func delete(_ bookmark: Bookmark) {
bookmarks.removeAll { $0.id == bookmark.id }
save()
}
func bookmarks(for item: LibraryItem) -> [Bookmark] {
bookmarks.filter { $0.itemId == item.id && $0.episodeId == item.episodeId }
.sorted { $0.time < $1.time }
}
/// Replaces bookmarks for an item with the server-loaded list (keeps bookmarks for other items).
func mergeFromServer(_ serverBookmarks: [ServerBookmark], for item: LibraryItem) {
bookmarks.removeAll { $0.itemId == item.id && $0.episodeId == item.episodeId }
let chapters = item.chapters
for sb in serverBookmarks {
let chapter = chapters.last { $0.start <= sb.time }
let bm = Bookmark(
id: UUID(),
createdAt: sb.createdAt.map { Date(timeIntervalSince1970: $0 / 1000) } ?? Date(),
itemId: item.id,
episodeId: item.episodeId,
itemTitle: item.title,
title: sb.title,
chapterTitle: chapter?.title,
time: sb.time
)
bookmarks.append(bm)
}
bookmarks.sort { $0.createdAt > $1.createdAt }
save()
}
private func save() {
try? FileManager.default.createDirectory(
at: AppPaths.supportDirectory, withIntermediateDirectories: true)
if let data = try? JSONEncoder().encode(bookmarks) {
try? data.write(to: saveFile)
}
}
private func load() {
guard let data = try? Data(contentsOf: saveFile) else { return }
bookmarks = (try? JSONDecoder().decode([Bookmark].self, from: data)) ?? []
}
}