@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.
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-screen-orientation",
3
- "version": "8.2.0",
3
+ "version": "8.3.1",
4
4
  "description": "Screen orientation plugin with support for bypassing orientation lock",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",