diff --git a/ios/OralTrainer.xcodeproj/project.xcworkspace/xcuserdata/liushuming.xcuserdatad/UserInterfaceState.xcuserstate b/ios/OralTrainer.xcodeproj/project.xcworkspace/xcuserdata/liushuming.xcuserdatad/UserInterfaceState.xcuserstate index 0ce9aee..3b3d8e5 100644 Binary files a/ios/OralTrainer.xcodeproj/project.xcworkspace/xcuserdata/liushuming.xcuserdatad/UserInterfaceState.xcuserstate and b/ios/OralTrainer.xcodeproj/project.xcworkspace/xcuserdata/liushuming.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/ios/OralTrainer/AppDelegate.swift b/ios/OralTrainer/AppDelegate.swift index 5efd84d..ec7ccc7 100644 --- a/ios/OralTrainer/AppDelegate.swift +++ b/ios/OralTrainer/AppDelegate.swift @@ -9,4 +9,38 @@ final class AppDelegate: NSObject, UIApplicationDelegate { ) -> UIInterfaceOrientationMask { AppDelegate.orientationLock } + + static func lockOrientation(_ mask: UIInterfaceOrientationMask) { + orientationLock = mask + guard let scene = UIApplication.shared.connectedScenes + .compactMap({ $0 as? UIWindowScene }) + .first(where: { $0.activationState == .foregroundActive }) + ?? UIApplication.shared.connectedScenes.compactMap({ $0 as? UIWindowScene }).first + else { + return + } + + func update(_ controller: UIViewController?) { + guard let controller else { return } + controller.setNeedsUpdateOfSupportedInterfaceOrientations() + update(controller.presentedViewController) + controller.children.forEach { update($0) } + } + + let requested: UIInterfaceOrientationMask + if mask.contains(.landscapeRight) { + requested = .landscapeRight + } else if mask.contains(.landscapeLeft) { + requested = .landscapeLeft + } else { + requested = mask + } + + scene.windows.forEach { update($0.rootViewController) } + scene.requestGeometryUpdate(.iOS(interfaceOrientations: requested)) { _ in } + DispatchQueue.main.async { + scene.windows.forEach { update($0.rootViewController) } + scene.requestGeometryUpdate(.iOS(interfaceOrientations: requested)) { _ in } + } + } } diff --git a/ios/OralTrainer/Resources/Info.plist b/ios/OralTrainer/Resources/Info.plist index e67e3d5..1cb0a2f 100644 --- a/ios/OralTrainer/Resources/Info.plist +++ b/ios/OralTrainer/Resources/Info.plist @@ -33,6 +33,8 @@ 需要麦克风权限才能进行录音评测 UILaunchScreen + UIRequiresFullScreen + UISupportedInterfaceOrientations UIInterfaceOrientationPortrait diff --git a/ios/OralTrainer/Views/FullscreenPlayerView.swift b/ios/OralTrainer/Views/FullscreenPlayerView.swift index 6ed296e..4ee8ff3 100644 --- a/ios/OralTrainer/Views/FullscreenPlayerView.swift +++ b/ios/OralTrainer/Views/FullscreenPlayerView.swift @@ -9,6 +9,29 @@ struct FullscreenPlayerView: View { @State private var explanationSentence: SentenceBoundary? var body: some View { + GeometryReader { proxy in + let fakeLandscape = proxy.size.height > proxy.size.width + playerChrome + .frame( + width: fakeLandscape ? proxy.size.height : proxy.size.width, + height: fakeLandscape ? proxy.size.width : proxy.size.height + ) + .rotationEffect(.degrees(fakeLandscape ? -90 : 0)) + .frame(width: proxy.size.width, height: proxy.size.height) + } + .background(Color.black) + .ignoresSafeArea() + .statusBarHidden(true) + .sheet(item: $explanationSentence) { sentence in + SentenceExplanationSheet( + mediaId: controller.currentItem?.id ?? "", + courseTitle: controller.currentItem?.title ?? "口语训练课程", + sentence: sentence + ) + } + } + + private var playerChrome: some View { ZStack { Color.black.ignoresSafeArea() PlayerSurfaceView(controller: controller) { isSwipeUp in @@ -95,13 +118,6 @@ struct FullscreenPlayerView: View { } .padding(16) } - .sheet(item: $explanationSentence) { sentence in - SentenceExplanationSheet( - mediaId: controller.currentItem?.id ?? "", - courseTitle: controller.currentItem?.title ?? "口语训练课程", - sentence: sentence - ) - } } @State private var progressDragging = false diff --git a/ios/OralTrainer/Views/RootView.swift b/ios/OralTrainer/Views/RootView.swift index f45305a..ef48f4c 100644 --- a/ios/OralTrainer/Views/RootView.swift +++ b/ios/OralTrainer/Views/RootView.swift @@ -115,7 +115,9 @@ struct RootView: View { FullscreenPlayerView(controller: controller) { exitFullscreen() } - .onAppear(perform: enterFullscreen) + .onAppear { + AppDelegate.lockOrientation(.landscape) + } } } @@ -519,26 +521,14 @@ struct RootView: View { // MARK: - Fullscreen private func enterFullscreen() { - AppDelegate.orientationLock = UIDevice.current.userInterfaceIdiom == .pad ? .all : .landscape - updateSupportedInterfaceOrientations() + AppDelegate.lockOrientation(.landscape) showFullscreenPlayer = true } private func exitFullscreen() { - AppDelegate.orientationLock = UIDevice.current.userInterfaceIdiom == .pad ? .all : .portrait - updateSupportedInterfaceOrientations() + let restored: UIInterfaceOrientationMask = + UIDevice.current.userInterfaceIdiom == .pad ? .all : .portrait + AppDelegate.lockOrientation(restored) showFullscreenPlayer = false } - - private func updateSupportedInterfaceOrientations() { - let scenes = UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene } - let controllers = scenes.flatMap { $0.windows }.compactMap(\.rootViewController) - for controller in controllers { - if #available(iOS 16.0, *) { - controller.setNeedsUpdateOfSupportedInterfaceOrientations() - } else { - UIViewController.attemptRotationToDeviceOrientation() - } - } - } }