Files
ABS-Client/ABS Client/Audiobookshelf swift/Views/FullHistoryView.swift
Scarriffle 134482bb4a Enhance download notifications and settings; add support for live activities
- Updated notification body to use proper German quotation marks for download failure alerts.
- Refactored audio interruption handling in PlayerEngine to utilize MainActor.assumeIsolated for better performance.
- Introduced nonisolated properties for support and downloads directories in ProgressSyncManager.
- Improved FullHistoryView and MainView with additional toolbar items and alerts for download failures.
- Added new toggles in SettingsView for parallel downloads and mobile data usage.
- Created new color assets for the DownloadWidgetExtension and updated widget appearance.
- Implemented Info.plist for DownloadWidgetExtension to support widget functionality.
- Enabled live activities support in iOS Info.plist.
2026-06-08 19:32:54 +02:00

107 lines
4.5 KiB
Swift

import SwiftUI
struct FullHistoryView: View {
@Environment(AppState.self) private var app
@Environment(\.dismiss) private var dismiss
var body: some View {
NavigationStack {
List {
ForEach(app.history.entries) { entry in
let isCurrent = entry.itemId == app.currentItem?.id &&
entry.episodeId == app.currentItem?.episodeId
Button {
dismiss()
Task { await app.playFromHistory(entry) }
} label: {
HStack(spacing: 10) {
VStack(alignment: .leading, spacing: 3) {
Text(entry.itemTitle)
.font(.subheadline.bold())
HStack(spacing: 4) {
if let ch = entry.chapterTitle {
Text(ch).font(.caption).foregroundStyle(.secondary)
Text("·").font(.caption).foregroundStyle(.secondary)
}
Text(formatTime(entry.position))
.font(.caption.monospacedDigit())
.foregroundStyle(.secondary)
}
Text(relativeTime(entry.timestamp))
.font(.caption2)
.foregroundStyle(.tertiary)
}
Spacer()
if !isCurrent {
Text(String(localized: "history.other_item"))
.font(.caption2)
.foregroundStyle(.tertiary)
}
}
.contentShape(Rectangle())
}
.buttonStyle(.plain)
}
if !app.history.entries.isEmpty {
Section {
Button(role: .destructive) {
app.history.clear()
} label: {
Text(String(localized: "history.clear"))
.frame(maxWidth: .infinity, alignment: .center)
}
}
}
}
.listStyle(.plain)
.navigationTitle(String(localized: "history.title"))
#if os(iOS)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button(String(localized: "settings.done")) { dismiss() }
}
}
#else
.toolbar {
ToolbarItem(placement: .confirmationAction) {
Button(String(localized: "settings.done")) { dismiss() }
.keyboardShortcut(.cancelAction)
}
}
#endif
.overlay {
if app.history.entries.isEmpty {
ContentUnavailableView(
String(localized: "history.empty"),
systemImage: "clock.arrow.circlepath",
description: Text(String(localized: "history.empty_desc"))
)
}
}
}
#if os(iOS)
.presentationDetents([.medium, .large])
.presentationDragIndicator(.visible)
#else
.frame(minWidth: 480, minHeight: 560)
#endif
}
private func formatTime(_ seconds: Double) -> String {
guard seconds.isFinite, seconds >= 0 else { return "0:00" }
let total = Int(seconds)
let h = total / 3600, m = (total % 3600) / 60, s = total % 60
return h > 0 ? String(format: "%d:%02d:%02d", h, m, s) : String(format: "%d:%02d", m, s)
}
private func relativeTime(_ date: Date) -> String {
let diff = Int(-date.timeIntervalSinceNow)
if diff < 60 { return String(localized: "history.just_now") }
if diff < 3600 { return String(format: String(localized: "history.minutes_ago"), diff / 60) }
if diff < 86400 { return String(format: String(localized: "history.hours_ago"), diff / 3600) }
return String(format: String(localized: "history.days_ago"), diff / 86400)
}
}