mirror of
https://github.com/yattee/yattee.git
synced 2026-10-31 17:07:18 +00:00
Support the iPhone Duo interior display in the player
The interior display refuses programmatic rotation (UISceneErrorDomain 101: "The current windowing mode does not allow for programmatic changes to interface orientation"), so the rotate/fullscreen button did nothing there and the failure was swallowed. - OrientationManager tracks `canRotateProgrammatically` (from the hinge state, cleared when a geometry request is refused) and is @Observable. - The fullscreen button takes the iPad path when rotation is unavailable: details toggle in portrait, floating panel toggle in landscape, icon follows the panel state; orientation lock is hidden. The refused request itself falls back to the details toggle. Accelerometer-driven rotation requests are gated the same way. - Corner layout gate is size-only on iOS (≥900×600 including safe areas, so the status bar toggling with the layout can't oscillate it); the layout-mode setting is exposed on foldables via a hinge presence check. - Corner layout on iPhone always hides the status bar (the interior's landscape bar is a ~57pt column) and pads by the live top inset only, instead of the transient 82pt inset seen while unfolding. - The player hosting controller nudges its safe area after size transitions so fold/unfold doesn't leave SwiftUI with stale insets.
This commit is contained in:
@@ -25,6 +25,11 @@ struct HingeLayoutObserver: ViewModifier {
|
||||
content.onHingeChange { _, context in
|
||||
// `hinge` is nil on devices without a hinge and when the view
|
||||
// leaves a hierarchy that provides hinge updates.
|
||||
if let status = context.hinge?.status {
|
||||
// Interior display (hinge open): the system refuses
|
||||
// programmatic rotation, so the player must not offer it.
|
||||
OrientationManager.shared.canRotateProgrammatically = status == .closed
|
||||
}
|
||||
let tabletop = context.hinge?.status == .partiallyOpen
|
||||
guard tabletop != isTabletop else { return }
|
||||
withAnimation(.easeInOut(duration: 0.3)) {
|
||||
@@ -37,12 +42,41 @@ struct HingeLayoutObserver: ViewModifier {
|
||||
}
|
||||
}
|
||||
|
||||
/// Reports whether the device has a hinge at all (foldable iPhone), for UI
|
||||
/// that only applies to the interior display, such as the corner layout option.
|
||||
private struct HingePresenceObserver: ViewModifier {
|
||||
@Binding var hasHinge: Bool
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
if #available(iOS 27.1, *) {
|
||||
content.onHingeChange { _, context in
|
||||
// nil also when leaving a hinge-providing hierarchy; keep the last value then
|
||||
guard context.hinge != nil else { return }
|
||||
hasHinge = true
|
||||
}
|
||||
} else {
|
||||
content
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension View {
|
||||
/// Tracks the device hinge and writes whether the tabletop player layout
|
||||
/// should be active. No-op before iOS 27.1 and on devices without a hinge.
|
||||
func observeHingeLayout(isTabletop: Binding<Bool>) -> some View {
|
||||
modifier(HingeLayoutObserver(isTabletop: isTabletop))
|
||||
}
|
||||
|
||||
/// Writes whether the device has a hinge. No-op before iOS 27.1.
|
||||
func observeHinge(hasHinge: Binding<Bool>) -> some View {
|
||||
modifier(HingePresenceObserver(hasHinge: hasHinge))
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
extension View {
|
||||
func observeHinge(hasHinge: Binding<Bool>) -> some View { self }
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user