diff --git a/iosApp/Configuration/Version.xcconfig b/iosApp/Configuration/Version.xcconfig index 798f5377..920f55cf 100644 --- a/iosApp/Configuration/Version.xcconfig +++ b/iosApp/Configuration/Version.xcconfig @@ -1,3 +1,3 @@ -CURRENT_PROJECT_VERSION=30 -MARKETING_VERSION=0.1.0 +CURRENT_PROJECT_VERSION=31 +MARKETING_VERSION=0.1.3 diff --git a/iosApp/iosApp/ContentView.swift b/iosApp/iosApp/ContentView.swift index 589c9e76..8b736eb9 100644 --- a/iosApp/iosApp/ContentView.swift +++ b/iosApp/iosApp/ContentView.swift @@ -2,6 +2,94 @@ import UIKit import SwiftUI import ComposeApp +final class RootComposeViewController: UIViewController { + private let contentController: UIViewController + + init(contentController: UIViewController) { + self.contentController = contentController + super.init(nibName: nil, bundle: nil) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + + view.backgroundColor = .black + contentController.view.backgroundColor = .black + + addChild(contentController) + view.addSubview(contentController.view) + contentController.view.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + contentController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), + contentController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), + contentController.view.topAnchor.constraint(equalTo: view.topAnchor), + contentController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor), + ]) + contentController.didMove(toParent: self) + } + + override var childForHomeIndicatorAutoHidden: UIViewController? { + immersiveController(in: contentController) ?? contentController + } + + override var childForScreenEdgesDeferringSystemGestures: UIViewController? { + immersiveController(in: contentController) ?? contentController + } + + override var childForStatusBarHidden: UIViewController? { + immersiveController(in: contentController) ?? contentController + } + + override var prefersHomeIndicatorAutoHidden: Bool { + immersiveController(in: contentController)?.prefersHomeIndicatorAutoHidden ?? false + } + + override var preferredScreenEdgesDeferringSystemGestures: UIRectEdge { + immersiveController(in: contentController)?.preferredScreenEdgesDeferringSystemGestures ?? [] + } + + override var prefersStatusBarHidden: Bool { + immersiveController(in: contentController)?.prefersStatusBarHidden ?? false + } + + override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation { + .fade + } + + func refreshImmersiveSystemUI() { + setNeedsUpdateOfHomeIndicatorAutoHidden() + setNeedsUpdateOfScreenEdgesDeferringSystemGestures() + setNeedsStatusBarAppearanceUpdate() + } + + private func immersiveController(in controller: UIViewController?) -> UIViewController? { + guard let controller else { return nil } + + if controller.prefersHomeIndicatorAutoHidden || + !controller.preferredScreenEdgesDeferringSystemGestures.isEmpty || + controller.prefersStatusBarHidden { + return controller + } + + if let presented = immersiveController(in: controller.presentedViewController) { + return presented + } + + for child in controller.children.reversed() { + if let immersiveChild = immersiveController(in: child) { + return immersiveChild + } + } + + return nil + } +} + struct ComposeView: UIViewControllerRepresentable { func makeUIViewController(context: Context) -> UIViewController { // Register MPV player bridge before Compose initializes @@ -9,7 +97,7 @@ struct ComposeView: UIViewControllerRepresentable { let controller = MainViewControllerKt.MainViewController() controller.view.backgroundColor = UIColor(red: 0.008, green: 0.016, blue: 0.016, alpha: 1.0) - return controller + return RootComposeViewController(contentController: controller) } func updateUIViewController(_ uiViewController: UIViewController, context: Context) {} diff --git a/iosApp/iosApp/Player/MPVPlayerBridge.swift b/iosApp/iosApp/Player/MPVPlayerBridge.swift index 906a4983..ae08f457 100644 --- a/iosApp/iosApp/Player/MPVPlayerBridge.swift +++ b/iosApp/iosApp/Player/MPVPlayerBridge.swift @@ -167,6 +167,22 @@ final class MPVPlayerViewController: UIViewController { } private var _currentErrorMessage: String? + override var prefersHomeIndicatorAutoHidden: Bool { + true + } + + override var preferredScreenEdgesDeferringSystemGestures: UIRectEdge { + [.bottom, .left, .right] + } + + override var prefersStatusBarHidden: Bool { + true + } + + override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation { + .fade + } + // MARK: - Lifecycle override func viewDidLoad() { @@ -181,6 +197,12 @@ final class MPVPlayerViewController: UIViewController { setupMpv() setupNotifications() + refreshImmersiveSystemUI() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + refreshImmersiveSystemUI() } override func viewDidLayoutSubviews() { @@ -188,6 +210,16 @@ final class MPVPlayerViewController: UIViewController { metalLayer.frame = view.bounds } + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + refreshImmersiveSystemUI() + } + + override func viewSafeAreaInsetsDidChange() { + super.viewSafeAreaInsetsDidChange() + refreshImmersiveSystemUI() + } + // MARK: - MPV Setup private func setupMpv() { @@ -633,6 +665,23 @@ final class MPVPlayerViewController: UIViewController { .joined(separator: ",") checkError(mpv_set_property_string(mpv, "http-header-fields", serialized)) } + + private func refreshImmersiveSystemUI() { + setNeedsUpdateOfHomeIndicatorAutoHidden() + setNeedsUpdateOfScreenEdgesDeferringSystemGestures() + setNeedsStatusBarAppearanceUpdate() + + var currentParent = parent + while let controller = currentParent { + controller.setNeedsUpdateOfHomeIndicatorAutoHidden() + controller.setNeedsUpdateOfScreenEdgesDeferringSystemGestures() + controller.setNeedsStatusBarAppearanceUpdate() + if let rootController = controller as? RootComposeViewController { + rootController.refreshImmersiveSystemUI() + } + currentParent = controller.parent + } + } } // MARK: - Bridge Creator (implements Kotlin protocol)