Update from NavidromePlayer.zip (2026-04-04 07:45)

This commit is contained in:
Dallas Groot 2026-04-04 07:45:37 -07:00
parent 274b5dfe02
commit a8d669824e
2 changed files with 36 additions and 127 deletions

View file

@ -96,58 +96,6 @@ class AppDelegate: NSObject, UIApplicationDelegate {
}
}
// MARK: - Scene Delegate
// Takes ownership of window creation so we can use NavidromeHostingController.
// When AppDelegate.application(_:configurationForConnecting:options:) returns a
// UISceneConfiguration with delegateClass = NavidromeSceneDelegate.self, SwiftUI's
// default window-bridging delegate is replaced our delegate creates the window.
// WindowGroup in App.body becomes a scene declaration that's never bridged to a
// UIWindow, which is harmless.
final class NavidromeSceneDelegate: NSObject, UIWindowSceneDelegate {
var window: UIWindow?
private var cancellable: AnyCancellable?
func scene(_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = scene as? UIWindowScene else { return }
let rootContent = AnyView(
RootView()
.environmentObject(ServerManager.shared)
.environmentObject(AudioPlayer.shared)
.environmentObject(OfflineManager.shared)
.tint(Color(red: 1.0, green: 0.176, blue: 0.333))
)
let hostingController = NavidromeHostingController(rootView: rootContent)
let window = UIWindow(windowScene: windowScene)
window.rootViewController = hostingController
window.makeKeyAndVisible()
self.window = window
// Kick the status bar whenever the album color extractor publishes a new style
cancellable = AlbumColorExtractor.shared.$preferredStatusBarStyle
.receive(on: DispatchQueue.main)
.sink { [weak hostingController] _ in
hostingController?.setNeedsStatusBarAppearanceUpdate()
}
}
}
// MARK: - Custom Hosting Controller
// Overrides preferredStatusBarStyle independently of the SwiftUI colorScheme.
// NowPlayingView keeps .preferredColorScheme(.dark) so all SwiftUI views stay
// visually dark. This override controls ONLY the status bar icon colour.
final class NavidromeHostingController: UIHostingController<AnyView> {
override var preferredStatusBarStyle: UIStatusBarStyle {
AlbumColorExtractor.shared.preferredStatusBarStyle
}
}
@main
struct NavidromePlayerApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

View file

@ -1,94 +1,55 @@
import UIKit
import SwiftUI
// MARK: - Status Bar Style Manager
//
// Singleton that controls UIKit-level status bar appearance independently of
// SwiftUI's .preferredColorScheme. Using .preferredColorScheme(.light/.dark) for
// status bar control was wrong it cascades through the entire view hierarchy and
// flips all system colors. This class owns ONLY the status bar style via the
// NavidromeHostingController override of preferredStatusBarStyle.
//
// NowPlayingView calls update(albumColor:isVisible:) on appear/dismiss/song change.
final class StatusBarStyleManager {
static let shared = StatusBarStyleManager()
private(set) var currentStyle: UIStatusBarStyle = .default
private weak var hostingController: NavidromeHostingController?
private init() {}
fileprivate func register(_ vc: NavidromeHostingController) {
hostingController = vc
}
/// Call whenever NowPlaying visibility or album art changes.
func update(albumColor: Color, isVisible: Bool) {
let style: UIStatusBarStyle
if !isVisible {
style = .default // follow system preference everywhere else
} else {
// NowPlaying always has a heavy black overlay so white icons are almost
// always correct. Only flip to dark icons for extremely bright/white art
// where the overlay might not fully neutralize a near-white background.
var brightness: CGFloat = 0
UIColor(albumColor).getHue(nil, saturation: nil, brightness: &brightness, alpha: nil)
style = brightness > 0.85 ? .darkContent : .lightContent
}
guard style != currentStyle else { return }
currentStyle = style
DispatchQueue.main.async { [weak self] in
self?.hostingController?.setNeedsStatusBarAppearanceUpdate()
}
}
}
// MARK: - Hosting Controller
/// UIHostingController subclass that delegates preferredStatusBarStyle to
/// StatusBarStyleManager, giving us UIKit-level status bar control without
/// touching SwiftUI's colorScheme environment.
final class NavidromeHostingController: UIHostingController<AnyView> {
override var preferredStatusBarStyle: UIStatusBarStyle {
StatusBarStyleManager.shared.currentStyle
}
override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation { .fade }
// Consume don't forward to children so there's one source of truth
override var childForStatusBarStyle: UIViewController? { nil }
}
import Combine
// MARK: - Scene Delegate
// Takes ownership of window creation so we can use NavidromeHostingController.
// When AppDelegate.application(_:configurationForConnecting:options:) returns a
// UISceneConfiguration with delegateClass = NavidromeSceneDelegate.self, SwiftUI's
// default window-bridging delegate is replaced our delegate creates the window.
// WindowGroup in App.body becomes a scene declaration that's never bridged to a
// UIWindow, which is harmless.
/// Provides NavidromeHostingController as the window's root view controller.
/// Registered via AppDelegate.application(_:configurationForConnecting:options:)
/// which bypasses SwiftUI's automatic window creation for this scene.
final class NavidromeSceneDelegate: UIResponder, UIWindowSceneDelegate {
final class NavidromeSceneDelegate: NSObject, UIWindowSceneDelegate {
var window: UIWindow?
func scene(
_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions
) {
private var cancellable: AnyCancellable?
func scene(_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = scene as? UIWindowScene else { return }
// Recreate the same environment the WindowGroup body provides.
// Singletons are used directly since they outlive any SwiftUI lifecycle.
let content = AnyView(
let rootContent = AnyView(
RootView()
.environmentObject(ServerManager.shared)
.environmentObject(AudioPlayer.shared)
.environmentObject(OfflineManager.shared)
.tint(Color(red: 1.0, green: 0.176, blue: 0.333))
)
let hostingController = NavidromeHostingController(rootView: content)
StatusBarStyleManager.shared.register(hostingController)
let hostingController = NavidromeHostingController(rootView: rootContent)
let window = UIWindow(windowScene: windowScene)
window.rootViewController = hostingController
window.makeKeyAndVisible()
self.window = window
// Re-trigger status bar appearance whenever album brightness changes
cancellable = AlbumColorExtractor.shared.$preferredStatusBarStyle
.receive(on: DispatchQueue.main)
.sink { [weak hostingController] _ in
hostingController?.setNeedsStatusBarAppearanceUpdate()
}
}
}
// MARK: - Custom Hosting Controller
// Overrides preferredStatusBarStyle independently of the SwiftUI colorScheme.
// NowPlayingView keeps .preferredColorScheme(.dark) so all SwiftUI views stay
// visually dark. This override controls ONLY the status bar icon colour.
final class NavidromeHostingController: UIHostingController<AnyView> {
override var preferredStatusBarStyle: UIStatusBarStyle {
AlbumColorExtractor.shared.preferredStatusBarStyle
}
}