- 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>
54 lines
1.6 KiB
Swift
54 lines
1.6 KiB
Swift
import SwiftUI
|
|
|
|
struct ContentView: View {
|
|
@Environment(AppState.self) private var app
|
|
@Environment(\.scenePhase) private var scenePhase
|
|
|
|
/// Bootstrap fertig — Signal an den Splash, Phase 2 zu starten.
|
|
@State private var isReadyToDismiss = false
|
|
/// Splash hat seine Fade-Phase ausgespielt und kann aus dem View-Tree raus.
|
|
@State private var splashFinished = false
|
|
|
|
var body: some View {
|
|
ZStack {
|
|
mainContent
|
|
if !splashFinished {
|
|
SplashScreenView(isReady: isReadyToDismiss) {
|
|
splashFinished = true
|
|
}
|
|
.zIndex(10)
|
|
}
|
|
}
|
|
.task { await boot() }
|
|
.onChange(of: scenePhase) { _, newPhase in
|
|
if newPhase == .active { app.onScenePhaseActive() }
|
|
}
|
|
}
|
|
|
|
@ViewBuilder
|
|
private var mainContent: some View {
|
|
Group {
|
|
if app.auth.isLoggedIn {
|
|
MainView()
|
|
} else {
|
|
LoginView()
|
|
}
|
|
}
|
|
.environment(\.locale, Locale(identifier: app.language))
|
|
#if os(iOS)
|
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
#else
|
|
.frame(minWidth: 900, minHeight: 600)
|
|
#endif
|
|
}
|
|
|
|
private func boot() async {
|
|
await app.bootstrap()
|
|
// SplashScreenView wartet selbst auf seine Mindest-Loop-Zeit, daher
|
|
// kein künstlicher Sleep mehr hier nötig. Sobald isReadyToDismiss
|
|
// gesetzt wird, fängt der Splash mit Phase 2 an und ruft am Ende
|
|
// seinen onComplete-Callback (splashFinished = true).
|
|
isReadyToDismiss = true
|
|
}
|
|
}
|