@capgo/capacitor-screen-orientation 8.2.0 → 8.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +134 -22
- package/android/src/main/java/ee/forgr/capacitor/screenorientation/CapacitorScreenOrientationPlugin.java +23 -1
- package/dist/docs.json +260 -3
- package/dist/esm/definitions.d.ts +144 -3
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +8 -1
- package/dist/esm/web.js +10 -1
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +10 -1
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +10 -1
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CapacitorScreenOrientationPlugin/CapacitorScreenOrientationPlugin.swift +36 -8
- package/ios/Sources/CapacitorScreenOrientationPlugin/IPhoneDuoFold.swift +258 -0
- package/package.json +1 -1
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import ObjectiveC
|
|
3
|
+
import UIKit
|
|
4
|
+
|
|
5
|
+
/// iPhone Duo fold, hinge, and bar placement.
|
|
6
|
+
/// The types landed in UIKit 9127.0.85 (iOS 27.1). This calls them through the
|
|
7
|
+
/// runtime so a build with the 27.0 SDK still works on the iPhone Duo simulator.
|
|
8
|
+
final class IPhoneDuoFold: NSObject {
|
|
9
|
+
static let shared = IPhoneDuoFold()
|
|
10
|
+
|
|
11
|
+
private let available: Bool
|
|
12
|
+
private let divisionKind: AnyObject?
|
|
13
|
+
private let occlusionKind: AnyObject?
|
|
14
|
+
private var hingeStatus: Int?
|
|
15
|
+
private var hingeRadians: Double?
|
|
16
|
+
private var interaction: AnyObject?
|
|
17
|
+
private var onChange: (() -> Void)?
|
|
18
|
+
|
|
19
|
+
private override init() {
|
|
20
|
+
let viewResponds = UIView.instancesRespond(to: NSSelectorFromString("reservedRegionsOfKind:options:"))
|
|
21
|
+
let kindClass: AnyClass? = NSClassFromString("UIViewReservedRegionKind")
|
|
22
|
+
let interactionClass: AnyClass? = NSClassFromString("UIHingeInteraction")
|
|
23
|
+
available = viewResponds && kindClass != nil && interactionClass != nil
|
|
24
|
+
if let kindClass = kindClass {
|
|
25
|
+
divisionKind = IPhoneDuoFold.classObject(kindClass, "divisionRegionKind")
|
|
26
|
+
occlusionKind = IPhoneDuoFold.classObject(kindClass, "occlusionRegionKind")
|
|
27
|
+
} else {
|
|
28
|
+
divisionKind = nil
|
|
29
|
+
occlusionKind = nil
|
|
30
|
+
}
|
|
31
|
+
super.init()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
func isFoldable(in view: UIView?) -> Bool {
|
|
35
|
+
guard available else { return false }
|
|
36
|
+
if let status = hingeStatus, status != 0 { return true }
|
|
37
|
+
guard let view = view else { return false }
|
|
38
|
+
return !regions(kind: divisionKind, in: view, options: 1).isEmpty
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
func foldState(in view: UIView?) -> [String: Any] {
|
|
42
|
+
guard let view = view, isFoldable(in: view) else {
|
|
43
|
+
return ["state": "flat", "isSeparating": false, "posture": "flat"]
|
|
44
|
+
}
|
|
45
|
+
let regions = allRegions(in: view)
|
|
46
|
+
let angle = hingeDegrees
|
|
47
|
+
let closed = angle.map { $0 < 20 } ?? (hingeStatus == 1)
|
|
48
|
+
let divisions = closed ? [] : regions.filter { $0.kind == "division" }
|
|
49
|
+
let division = divisions.first { $0.active } ?? divisions.first
|
|
50
|
+
let folded: Bool
|
|
51
|
+
if let angle = angle, division != nil {
|
|
52
|
+
folded = (20...160).contains(angle)
|
|
53
|
+
} else if hingeStatus == 2 {
|
|
54
|
+
folded = division != nil
|
|
55
|
+
} else if hingeStatus == 3 {
|
|
56
|
+
folded = false
|
|
57
|
+
} else {
|
|
58
|
+
folded = division?.active == true
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
var result: [String: Any] = [
|
|
62
|
+
"state": folded ? "half-opened" : "flat",
|
|
63
|
+
"isSeparating": folded,
|
|
64
|
+
"posture": "flat"
|
|
65
|
+
]
|
|
66
|
+
if folded, let division = division {
|
|
67
|
+
result["posture"] = division.frame.height >= division.frame.width ? "book" : "tabletop"
|
|
68
|
+
result["hingeOrientation"] = division.frame.height >= division.frame.width ? "vertical" : "horizontal"
|
|
69
|
+
result["hingeBounds"] = box(division.frame)
|
|
70
|
+
result["hingeMargins"] = [
|
|
71
|
+
"top": Int(division.margins.top.rounded()),
|
|
72
|
+
"right": Int(division.margins.right.rounded()),
|
|
73
|
+
"bottom": Int(division.margins.bottom.rounded()),
|
|
74
|
+
"left": Int(division.margins.left.rounded())
|
|
75
|
+
]
|
|
76
|
+
}
|
|
77
|
+
let cameras = regions.filter { $0.kind == "occlusion" && $0.active }.map { box($0.frame) }
|
|
78
|
+
if !cameras.isEmpty {
|
|
79
|
+
result["cameraBounds"] = cameras
|
|
80
|
+
}
|
|
81
|
+
if hingeStatus != nil {
|
|
82
|
+
let onInner = regions.contains { $0.kind == "division" }
|
|
83
|
+
result["activeDisplay"] = (closed || !onInner) ? "outer" : "inner"
|
|
84
|
+
}
|
|
85
|
+
return result
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
func hingeAngle() -> [String: Any] {
|
|
89
|
+
guard let degrees = hingeDegrees else {
|
|
90
|
+
return ["angle": NSNull(), "status": NSNull()]
|
|
91
|
+
}
|
|
92
|
+
return [
|
|
93
|
+
"angle": degrees,
|
|
94
|
+
"status": statusName(hingeStatus) as Any? ?? NSNull()
|
|
95
|
+
]
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
func reservedRegions(in view: UIView?) -> [String: Any] {
|
|
99
|
+
guard let view = view, available else { return ["regions": []] }
|
|
100
|
+
let regions = allRegions(in: view).map { region -> [String: Any] in
|
|
101
|
+
let frame = box(region.frame)
|
|
102
|
+
var item: [String: Any] = [
|
|
103
|
+
"x": frame["x"] ?? 0,
|
|
104
|
+
"y": frame["y"] ?? 0,
|
|
105
|
+
"width": frame["width"] ?? 0,
|
|
106
|
+
"height": frame["height"] ?? 0,
|
|
107
|
+
"kind": region.kind,
|
|
108
|
+
"isActive": region.active
|
|
109
|
+
]
|
|
110
|
+
item["margins"] = [
|
|
111
|
+
"top": Int(region.margins.top.rounded()),
|
|
112
|
+
"right": Int(region.margins.right.rounded()),
|
|
113
|
+
"bottom": Int(region.margins.bottom.rounded()),
|
|
114
|
+
"left": Int(region.margins.left.rounded())
|
|
115
|
+
]
|
|
116
|
+
return item
|
|
117
|
+
}
|
|
118
|
+
return ["regions": regions]
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
func barPlacement(in view: UIView?) -> [String: Any] {
|
|
122
|
+
guard let view = view else {
|
|
123
|
+
return ["verticalBarEdge": NSNull(), "inset": 0]
|
|
124
|
+
}
|
|
125
|
+
view.layoutIfNeeded()
|
|
126
|
+
let insets = view.safeAreaInsets
|
|
127
|
+
let rightToLeft = view.effectiveUserInterfaceLayoutDirection == .rightToLeft
|
|
128
|
+
let edge: String?
|
|
129
|
+
if insets.right >= 70 {
|
|
130
|
+
edge = rightToLeft ? "leading" : "trailing"
|
|
131
|
+
} else if insets.left >= 70 {
|
|
132
|
+
edge = rightToLeft ? "trailing" : "leading"
|
|
133
|
+
} else {
|
|
134
|
+
edge = nil
|
|
135
|
+
}
|
|
136
|
+
let inset = edge == nil ? 0 : Int((edge == "trailing" && !rightToLeft || edge == "leading" && rightToLeft ? insets.right : insets.left).rounded())
|
|
137
|
+
return ["verticalBarEdge": edge as Any? ?? NSNull(), "inset": inset]
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
func observe(in view: UIView, onChange: @escaping () -> Void) {
|
|
141
|
+
guard available, interaction == nil, let interactionClass: AnyClass = NSClassFromString("UIHingeInteraction") else { return }
|
|
142
|
+
self.onChange = onChange
|
|
143
|
+
let block: @convention(block) (AnyObject, AnyObject) -> Void = { [weak self] _, update in
|
|
144
|
+
self?.store(update)
|
|
145
|
+
self?.onChange?()
|
|
146
|
+
}
|
|
147
|
+
let sel = NSSelectorFromString("initWithUpdateHandler:")
|
|
148
|
+
typealias InitFn = @convention(c) (AnyObject, Selector, AnyObject) -> AnyObject?
|
|
149
|
+
let allocated = interactionClass.alloc()
|
|
150
|
+
guard let initFn = class_getMethodImplementation(interactionClass, sel) else { return }
|
|
151
|
+
let interaction = unsafeBitCast(initFn, to: InitFn.self)(allocated, sel, block as AnyObject)
|
|
152
|
+
self.interaction = interaction
|
|
153
|
+
if let interaction = interaction {
|
|
154
|
+
view.perform(NSSelectorFromString("addInteraction:"), with: interaction)
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
private var hingeDegrees: Double? {
|
|
159
|
+
guard let radians = hingeRadians, hingeStatus != 0 else { return nil }
|
|
160
|
+
return radians * 180 / .pi
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private func store(_ update: AnyObject) {
|
|
164
|
+
guard let hinge = object(update, "hinge") else { return }
|
|
165
|
+
hingeStatus = integer(hinge, "status")
|
|
166
|
+
hingeRadians = double(hinge, "angle")
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
private func allRegions(in view: UIView) -> [Region] {
|
|
170
|
+
regions(kind: divisionKind, in: view, options: 1) + regions(kind: occlusionKind, in: view, options: 1)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
private func regions(kind: AnyObject?, in view: UIView, options: UInt) -> [Region] {
|
|
174
|
+
guard let kind = kind else { return [] }
|
|
175
|
+
let sel = NSSelectorFromString("reservedRegionsOfKind:options:")
|
|
176
|
+
typealias Fn = @convention(c) (AnyObject, Selector, AnyObject, UInt) -> NSArray?
|
|
177
|
+
let list = unsafeBitCast(view.method(for: sel), to: Fn.self)(view, sel, kind, options) ?? []
|
|
178
|
+
return list.compactMap { item in
|
|
179
|
+
guard let region = item as? AnyObject else { return nil }
|
|
180
|
+
let kindObject = object(region, "kind")
|
|
181
|
+
let name = kindObject?.description.contains("occlusion") == true ? "occlusion" : "division"
|
|
182
|
+
return Region(
|
|
183
|
+
kind: name,
|
|
184
|
+
frame: rect(region, "frame"),
|
|
185
|
+
margins: insets(region, "margins"),
|
|
186
|
+
active: bool(region, "isActive")
|
|
187
|
+
)
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private func statusName(_ status: Int?) -> String? {
|
|
192
|
+
switch status {
|
|
193
|
+
case 1: return "closed"
|
|
194
|
+
case 2: return "partiallyOpen"
|
|
195
|
+
case 3: return "fullyOpen"
|
|
196
|
+
default: return nil
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
private struct Region {
|
|
201
|
+
var kind: String
|
|
202
|
+
var frame: CGRect
|
|
203
|
+
var margins: UIEdgeInsets
|
|
204
|
+
var active: Bool
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
private func box(_ rect: CGRect) -> [String: Int] {
|
|
208
|
+
return [
|
|
209
|
+
"x": Int(rect.origin.x.rounded()),
|
|
210
|
+
"y": Int(rect.origin.y.rounded()),
|
|
211
|
+
"width": Int(rect.size.width.rounded()),
|
|
212
|
+
"height": Int(rect.size.height.rounded())
|
|
213
|
+
]
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
private static func classObject(_ cls: AnyClass, _ name: String) -> AnyObject? {
|
|
217
|
+
let sel = NSSelectorFromString(name)
|
|
218
|
+
typealias Fn = @convention(c) (AnyClass, Selector) -> AnyObject?
|
|
219
|
+
guard let imp = class_getMethodImplementation(object_getClass(cls), sel) else { return nil }
|
|
220
|
+
return unsafeBitCast(imp, to: Fn.self)(cls, sel)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
private func object(_ target: AnyObject, _ name: String) -> AnyObject? {
|
|
224
|
+
let sel = NSSelectorFromString(name)
|
|
225
|
+
typealias Fn = @convention(c) (AnyObject, Selector) -> AnyObject?
|
|
226
|
+
return unsafeBitCast(target.method(for: sel), to: Fn.self)(target, sel)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
private func integer(_ target: AnyObject, _ name: String) -> Int {
|
|
230
|
+
let sel = NSSelectorFromString(name)
|
|
231
|
+
typealias Fn = @convention(c) (AnyObject, Selector) -> Int
|
|
232
|
+
return unsafeBitCast(target.method(for: sel), to: Fn.self)(target, sel)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
private func double(_ target: AnyObject, _ name: String) -> Double {
|
|
236
|
+
let sel = NSSelectorFromString(name)
|
|
237
|
+
typealias Fn = @convention(c) (AnyObject, Selector) -> Double
|
|
238
|
+
return unsafeBitCast(target.method(for: sel), to: Fn.self)(target, sel)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
private func bool(_ target: AnyObject, _ name: String) -> Bool {
|
|
242
|
+
let sel = NSSelectorFromString(name)
|
|
243
|
+
typealias Fn = @convention(c) (AnyObject, Selector) -> Bool
|
|
244
|
+
return unsafeBitCast(target.method(for: sel), to: Fn.self)(target, sel)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
private func rect(_ target: AnyObject, _ name: String) -> CGRect {
|
|
248
|
+
let sel = NSSelectorFromString(name)
|
|
249
|
+
typealias Fn = @convention(c) (AnyObject, Selector) -> CGRect
|
|
250
|
+
return unsafeBitCast(target.method(for: sel), to: Fn.self)(target, sel)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
private func insets(_ target: AnyObject, _ name: String) -> UIEdgeInsets {
|
|
254
|
+
let sel = NSSelectorFromString(name)
|
|
255
|
+
typealias Fn = @convention(c) (AnyObject, Selector) -> UIEdgeInsets
|
|
256
|
+
return unsafeBitCast(target.method(for: sel), to: Fn.self)(target, sel)
|
|
257
|
+
}
|
|
258
|
+
}
|
package/package.json
CHANGED