- Add Netflix-style Home with recent audiobooks/podcasts, list/grid toggle shared with Library - Add searchable libraries — audiobooks filter on title/author; podcasts also index and search episodes across the whole library - Add unified context menu (download / progress remove) across Home, Library, and PodcastDetail - Rework cover badges: listened checkmark top-right, download indicator bottom-right; hide progress bar when finished - Add AirPlay picker in PlayerBar - Replace splash with four-phase animation: orbital particles → attract → flash → fade; cross-platform iOS+macOS, runtime AppIcon lookup - Fix macOS parallel downloads: align connection settings with iOS, raise timeoutIntervalForRequest to 300s - Fix progress deletion using server-side progress UUID (DELETE /api/me/progress/:id) - Bump CFBundleVersion to 8 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
91 lines
2.9 KiB
Swift
91 lines
2.9 KiB
Swift
import SwiftUI
|
|
#if os(iOS)
|
|
import AVFAudio
|
|
import UIKit
|
|
#endif
|
|
|
|
@main
|
|
struct Audiobookshelf_swiftApp: App {
|
|
@State private var appState = AppState()
|
|
#if os(iOS)
|
|
/// Bridges the UIKit-only `handleEventsForBackgroundURLSession` callback
|
|
/// (used by iOS to wake the app when a background download completes)
|
|
/// into the SwiftUI lifecycle. Without this, in-flight downloads can't
|
|
/// notify the app of completion after a swipe-away.
|
|
@UIApplicationDelegateAdaptor(BackgroundDownloadAppDelegate.self) private var appDelegate
|
|
#endif
|
|
|
|
init() {
|
|
configureImageCache()
|
|
#if os(iOS)
|
|
configureAudioSession()
|
|
#endif
|
|
}
|
|
|
|
/// Increases the shared URLCache so AsyncImage covers persist across renders
|
|
/// and app launches. Default capacity is tiny (~4 MB / 20 MB).
|
|
private func configureImageCache() {
|
|
let memoryCapacity = 50 * 1024 * 1024 // 50 MB
|
|
let diskCapacity = 500 * 1024 * 1024 // 500 MB
|
|
URLCache.shared = URLCache(
|
|
memoryCapacity: memoryCapacity,
|
|
diskCapacity: diskCapacity,
|
|
diskPath: "covers"
|
|
)
|
|
}
|
|
|
|
var body: some Scene {
|
|
#if os(macOS)
|
|
WindowGroup {
|
|
ContentView()
|
|
.environment(appState)
|
|
}
|
|
.windowResizability(.contentSize)
|
|
|
|
Settings {
|
|
SettingsView()
|
|
.environment(appState)
|
|
}
|
|
#else
|
|
WindowGroup {
|
|
ContentView()
|
|
.environment(appState)
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#if os(iOS)
|
|
private func configureAudioSession() {
|
|
// `.spokenAudio` + `.longFormAudio` policy: iOS-Hinweis dass es sich um
|
|
// ernsthafte Audio-Wiedergabe handelt (nicht Tasten-Sounds). Aktiviert
|
|
// u. a. das AirPlay-Routing im Control Center / Lockscreen und sorgt
|
|
// für richtigen Ducking-/Interruption-Behavior bei Anrufen.
|
|
// setActive(true) passiert erst in play() damit beim App-Start
|
|
// keine laufende Fremd-Wiedergabe unterbrochen wird.
|
|
try? AVAudioSession.sharedInstance().setCategory(
|
|
.playback,
|
|
mode: .spokenAudio,
|
|
policy: .longFormAudio,
|
|
options: []
|
|
)
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#if os(iOS)
|
|
/// Receives the system's background URLSession completion callback and routes
|
|
/// the handler to whichever `DownloadManager` is currently active. We can't
|
|
/// reach the SwiftUI-owned `AppState` from here directly, so the handler is
|
|
/// stashed on `DownloadManager.pendingBackgroundCompletion` and consumed when
|
|
/// `urlSessionDidFinishEvents(forBackgroundURLSession:)` fires.
|
|
final class BackgroundDownloadAppDelegate: NSObject, UIApplicationDelegate {
|
|
func application(
|
|
_ application: UIApplication,
|
|
handleEventsForBackgroundURLSession identifier: String,
|
|
completionHandler: @escaping () -> Void
|
|
) {
|
|
DownloadManager.pendingBackgroundCompletion = completionHandler
|
|
}
|
|
}
|
|
#endif
|