Features: - Dual-AVPlayer Smart DJ crossfade with LUFS normalization - Mitsuha-style FFT visualizer (real-time + offline pre-computed) - Companion API integration (Smart DJ, tag editing, vis frames) - Offline-first SyncEngine with delta sync and album detail pre-caching - Audio pre-fetcher for gapless queue playback - Optimistic action queue (star/unstar with background retry) - ShazamKit recognition with MusicKit preview playback - Radio streaming with HLS/PLS/M3U support and buffer seek - Watch app with Crown Sequencer and Ultra speaker support - Batch metadata editing with album_artist fix for split albums - Cache-first UI pattern across all views - NWPathMonitor offline detection with reactive song greying
79 lines
2.7 KiB
Swift
79 lines
2.7 KiB
Swift
import SwiftUI
|
|
|
|
// MARK: - App Delegate (Background Upload Session)
|
|
|
|
class AppDelegate: NSObject, UIApplicationDelegate {
|
|
func application(
|
|
_ application: UIApplication,
|
|
handleEventsForBackgroundURLSession identifier: String,
|
|
completionHandler: @escaping () -> Void
|
|
) {
|
|
if identifier == "com.navidromeplayer.batchupload" {
|
|
ZipImportManager.shared.setBackgroundCompletionHandler(completionHandler)
|
|
DebugLogger.shared.log("Background session woke app: \(identifier)", category: "Upload")
|
|
}
|
|
}
|
|
}
|
|
|
|
@main
|
|
struct NavidromePlayerApp: App {
|
|
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
|
|
|
|
@StateObject private var serverManager = ServerManager.shared
|
|
@StateObject private var audioPlayer = AudioPlayer.shared
|
|
@StateObject private var offlineManager = OfflineManager.shared
|
|
|
|
// Initialize WatchConnectivity so sync works immediately
|
|
private let watchConnectivity = WatchConnectivityManager.shared
|
|
|
|
init() {
|
|
// Dismiss keyboard when scrolling any scroll view
|
|
UIScrollView.appearance().keyboardDismissMode = .interactive
|
|
}
|
|
|
|
var body: some Scene {
|
|
WindowGroup {
|
|
RootView()
|
|
.environmentObject(serverManager)
|
|
.environmentObject(audioPlayer)
|
|
.environmentObject(offlineManager)
|
|
.tint(Color(red: 1.0, green: 0.176, blue: 0.333)) // iOS 8 Music pink
|
|
}
|
|
}
|
|
}
|
|
|
|
struct RootView: View {
|
|
@EnvironmentObject var serverManager: ServerManager
|
|
|
|
var body: some View {
|
|
Group {
|
|
if serverManager.servers.isEmpty {
|
|
// No servers configured — show login
|
|
LoginView()
|
|
} else {
|
|
// Servers exist — show main UI immediately (connects in background)
|
|
MainTabView()
|
|
}
|
|
}
|
|
.dismissKeyboardOnTap()
|
|
.task {
|
|
ImageCache.shared.trimDiskCache()
|
|
AudioPreFetcher.shared.cleanOldPrefetches(keeping: AudioPlayer.shared.queue)
|
|
|
|
if serverManager.activeServer != nil {
|
|
await serverManager.connectToActive()
|
|
|
|
// Background sync — fills LibraryCache so UI never waits for network
|
|
SyncEngine.shared.syncIfNeeded()
|
|
|
|
// Flush any pending optimistic actions (star/unstar that failed offline)
|
|
OptimisticActionQueue.shared.flush()
|
|
}
|
|
|
|
// Connect Companion push client if enabled
|
|
if CompanionSettings.shared.isEnabled {
|
|
CompanionPushClient.shared.connect()
|
|
}
|
|
}
|
|
}
|
|
}
|