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:
@@ -12,13 +12,14 @@ enum LibraryLayout: String, CaseIterable, Identifiable {
|
||||
struct LibraryListView: View {
|
||||
let items: [LibraryItem]
|
||||
var onRefresh: (() async -> Void)? = nil
|
||||
var dimDownloading: Bool = false
|
||||
let onSelect: (LibraryItem) -> Void
|
||||
|
||||
var body: some View {
|
||||
#if os(iOS)
|
||||
List {
|
||||
ForEach(items) { item in
|
||||
LibraryListRow(item: item)
|
||||
LibraryListRow(item: item, dimDownloading: dimDownloading)
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture { onSelect(item) }
|
||||
.listRowInsets(EdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16))
|
||||
@@ -32,7 +33,7 @@ struct LibraryListView: View {
|
||||
ScrollView {
|
||||
LazyVStack(spacing: 0) {
|
||||
ForEach(Array(items.enumerated()), id: \.element.id) { idx, item in
|
||||
LibraryListRow(item: item)
|
||||
LibraryListRow(item: item, dimDownloading: dimDownloading)
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture { onSelect(item) }
|
||||
if idx < items.count - 1 {
|
||||
@@ -49,10 +50,17 @@ struct LibraryListView: View {
|
||||
struct LibraryListRow: View {
|
||||
@Environment(AppState.self) private var app
|
||||
let item: LibraryItem
|
||||
var dimDownloading: Bool = false
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 12) {
|
||||
cover
|
||||
ZStack {
|
||||
cover
|
||||
.opacity(dimDownloading && isActivelyDownloading ? 0.55 : 1.0)
|
||||
if dimDownloading, case .downloading(let p) = app.downloads.state(for: item.downloadKey) {
|
||||
LargeDownloadOverlay(progress: p, size: 40)
|
||||
}
|
||||
}
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text(item.title)
|
||||
.font(.headline)
|
||||
@@ -77,18 +85,27 @@ struct LibraryListRow: View {
|
||||
#endif
|
||||
}
|
||||
}
|
||||
.opacity(dimDownloading && isActivelyDownloading ? 0.55 : 1.0)
|
||||
Spacer(minLength: 8)
|
||||
#if os(macOS)
|
||||
if item.durationSeconds > 0 {
|
||||
if dimDownloading, app.downloads.downloadedBytes(for: item.downloadKey) > 0 {
|
||||
Text(formatBytes(app.downloads.downloadedBytes(for: item.downloadKey)))
|
||||
.font(.caption.monospacedDigit())
|
||||
.foregroundStyle(.secondary)
|
||||
.opacity(dimDownloading && isActivelyDownloading ? 0.55 : 1.0)
|
||||
} else if item.durationSeconds > 0 {
|
||||
Text(formatDuration(item.durationSeconds))
|
||||
.font(.caption.monospacedDigit())
|
||||
.foregroundStyle(.secondary)
|
||||
.opacity(dimDownloading && isActivelyDownloading ? 0.55 : 1.0)
|
||||
}
|
||||
#endif
|
||||
downloadStatus
|
||||
#if os(macOS)
|
||||
.frame(width: 28)
|
||||
#endif
|
||||
if !(dimDownloading && isActivelyDownloading) {
|
||||
downloadStatus
|
||||
#if os(macOS)
|
||||
.frame(width: 28)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#if os(macOS)
|
||||
.padding(.horizontal, 16)
|
||||
@@ -97,6 +114,11 @@ struct LibraryListRow: View {
|
||||
.contextMenu { downloadMenuItems }
|
||||
}
|
||||
|
||||
private var isActivelyDownloading: Bool {
|
||||
if case .downloading = app.downloads.state(for: item.downloadKey) { return true }
|
||||
return false
|
||||
}
|
||||
|
||||
private var cover: some View {
|
||||
Group {
|
||||
if let url = app.client.coverURL(itemId: item.id) {
|
||||
|
||||
Reference in New Issue
Block a user