Files
ABS-Client/ABS Client/Audiobookshelf swift/Audiobookshelf_swiftApp.swift
Scarriffle a17a61ad9a Home screen, library-wide search, splash with orbital particles, macOS download fix
- 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>
2026-06-23 08:54:43 +02:00

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