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>
This commit is contained in:
@@ -3,20 +3,21 @@ import SwiftUI
|
||||
struct ContentView: View {
|
||||
@Environment(AppState.self) private var app
|
||||
@Environment(\.scenePhase) private var scenePhase
|
||||
#if os(iOS)
|
||||
@State private var splashVisible = true
|
||||
#endif
|
||||
|
||||
/// 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 os(iOS)
|
||||
if splashVisible {
|
||||
SplashView()
|
||||
.zIndex(10)
|
||||
.transition(.opacity.animation(.easeOut(duration: 0.55)))
|
||||
if !splashFinished {
|
||||
SplashScreenView(isReady: isReadyToDismiss) {
|
||||
splashFinished = true
|
||||
}
|
||||
.zIndex(10)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
.task { await boot() }
|
||||
.onChange(of: scenePhase) { _, newPhase in
|
||||
@@ -42,17 +43,11 @@ struct ContentView: View {
|
||||
}
|
||||
|
||||
private func boot() async {
|
||||
#if os(iOS)
|
||||
// Run bootstrap and minimum splash time in parallel;
|
||||
// dismiss splash only after BOTH complete.
|
||||
await withTaskGroup(of: Void.self) { group in
|
||||
group.addTask { await app.bootstrap() }
|
||||
group.addTask { try? await Task.sleep(for: .seconds(1.2)) }
|
||||
await group.waitForAll()
|
||||
}
|
||||
withAnimation { splashVisible = false }
|
||||
#else
|
||||
await app.bootstrap()
|
||||
#endif
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user