@erkamyaman/capacitor-foldable 7.0.0
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/ErkamyamanCapacitorFoldable.podspec +17 -0
- package/LICENSE +21 -0
- package/Package.swift +28 -0
- package/README.md +457 -0
- package/android/build.gradle +71 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/kotlin/io/github/erkamyaman/foldable/DisplayModes.kt +115 -0
- package/android/src/main/kotlin/io/github/erkamyaman/foldable/Foldable.kt +189 -0
- package/android/src/main/kotlin/io/github/erkamyaman/foldable/FoldablePlugin.kt +333 -0
- package/android/src/main/res/.gitkeep +0 -0
- package/dist/docs.json +584 -0
- package/dist/esm/css.d.ts +8 -0
- package/dist/esm/css.js +29 -0
- package/dist/esm/css.js.map +1 -0
- package/dist/esm/definitions.d.ts +267 -0
- package/dist/esm/definitions.js +2 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +5 -0
- package/dist/esm/index.js +9 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/polyfill.d.ts +19 -0
- package/dist/esm/polyfill.js +76 -0
- package/dist/esm/polyfill.js.map +1 -0
- package/dist/esm/segments.d.ts +8 -0
- package/dist/esm/segments.js +26 -0
- package/dist/esm/segments.js.map +1 -0
- package/dist/esm/size-class.d.ts +2 -0
- package/dist/esm/size-class.js +33 -0
- package/dist/esm/size-class.js.map +1 -0
- package/dist/esm/web.d.ts +21 -0
- package/dist/esm/web.js +46 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +222 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +225 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Sources/FoldablePlugin/Foldable.swift +156 -0
- package/ios/Sources/FoldablePlugin/FoldablePlugin.swift +194 -0
- package/ios/Sources/FoldablePlugin/ReservedRegionFoldProvider.swift +69 -0
- package/ios/Sources/FoldablePlugin/UIKitFoldSource.swift +53 -0
- package/ios/Tests/FoldablePluginTests/FoldableTests.swift +104 -0
- package/ios/Tests/FoldablePluginTests/ReservedRegionFoldProviderTests.swift +143 -0
- package/package.json +96 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import UIKit
|
|
2
|
+
import XCTest
|
|
3
|
+
@testable import FoldablePlugin
|
|
4
|
+
|
|
5
|
+
private final class FakeFoldSource: FoldSource {
|
|
6
|
+
var hasFold = true
|
|
7
|
+
var hingeStatus: HingeStatus?
|
|
8
|
+
var hingeAngle: Double?
|
|
9
|
+
var regions: [ReservedRegion] = []
|
|
10
|
+
|
|
11
|
+
func reservedRegions(in view: UIView) -> [ReservedRegion] {
|
|
12
|
+
return regions
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
func observe(_ view: UIView, onChange: @escaping () -> Void) {}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
class ReservedRegionFoldProviderTests: XCTestCase {
|
|
19
|
+
private let view = UIView(frame: CGRect(x: 0, y: 0, width: 626, height: 890))
|
|
20
|
+
|
|
21
|
+
private func foldState(_ source: FakeFoldSource) -> [String: Any] {
|
|
22
|
+
return Foldable(provider: ReservedRegionFoldProvider(source: source)).getFoldState(in: view)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
func testActiveVerticalDivisionIsHalfOpenedBook() {
|
|
26
|
+
let source = FakeFoldSource()
|
|
27
|
+
source.hingeStatus = .partiallyOpen
|
|
28
|
+
source.regions = [ReservedRegion(kind: .division, frame: CGRect(x: 313, y: 0, width: 0, height: 890), isActive: true)]
|
|
29
|
+
|
|
30
|
+
let result = foldState(source)
|
|
31
|
+
|
|
32
|
+
XCTAssertEqual(result["state"] as? String, "half-opened")
|
|
33
|
+
XCTAssertEqual(result["isSeparating"] as? Bool, true)
|
|
34
|
+
XCTAssertEqual(result["posture"] as? String, "book")
|
|
35
|
+
XCTAssertEqual(result["hingeOrientation"] as? String, "vertical")
|
|
36
|
+
XCTAssertEqual(result["hingeBounds"] as? [String: Int], ["x": 313, "y": 0, "width": 0, "height": 890])
|
|
37
|
+
XCTAssertEqual(result["activeDisplay"] as? String, "inner")
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
func testActiveHorizontalDivisionIsTabletop() {
|
|
41
|
+
let source = FakeFoldSource()
|
|
42
|
+
source.regions = [ReservedRegion(kind: .division, frame: CGRect(x: 0, y: 313, width: 890, height: 0), isActive: true)]
|
|
43
|
+
|
|
44
|
+
let result = foldState(source)
|
|
45
|
+
|
|
46
|
+
XCTAssertEqual(result["posture"] as? String, "tabletop")
|
|
47
|
+
XCTAssertEqual(result["hingeOrientation"] as? String, "horizontal")
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
func testInactiveDivisionIsFlatWithHinge() {
|
|
51
|
+
let source = FakeFoldSource()
|
|
52
|
+
source.hingeStatus = .fullyOpen
|
|
53
|
+
source.regions = [ReservedRegion(kind: .division, frame: CGRect(x: 313, y: 0, width: 0, height: 890), isActive: false)]
|
|
54
|
+
|
|
55
|
+
let result = foldState(source)
|
|
56
|
+
|
|
57
|
+
XCTAssertEqual(result["state"] as? String, "flat")
|
|
58
|
+
XCTAssertEqual(result["isSeparating"] as? Bool, false)
|
|
59
|
+
XCTAssertEqual(result["posture"] as? String, "flat")
|
|
60
|
+
XCTAssertEqual(result["hingeOrientation"] as? String, "vertical")
|
|
61
|
+
XCTAssertNotNil(result["hingeBounds"])
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
func testActiveDivisionWinsOverInactiveOne() {
|
|
65
|
+
let source = FakeFoldSource()
|
|
66
|
+
source.regions = [
|
|
67
|
+
ReservedRegion(kind: .division, frame: CGRect(x: 313, y: 0, width: 0, height: 890), isActive: false),
|
|
68
|
+
ReservedRegion(kind: .division, frame: CGRect(x: 0, y: 445, width: 626, height: 0), isActive: true)
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
XCTAssertEqual(foldState(source)["posture"] as? String, "tabletop")
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
func testClosedHingeIsOuterDisplayWithoutFold() {
|
|
75
|
+
let source = FakeFoldSource()
|
|
76
|
+
source.hingeStatus = .closed
|
|
77
|
+
source.regions = [ReservedRegion(kind: .division, frame: CGRect(x: 233, y: 0, width: 0, height: 678), isActive: false)]
|
|
78
|
+
|
|
79
|
+
let result = foldState(source)
|
|
80
|
+
|
|
81
|
+
XCTAssertEqual(result["state"] as? String, "flat")
|
|
82
|
+
XCTAssertNil(result["hingeBounds"])
|
|
83
|
+
XCTAssertEqual(result["activeDisplay"] as? String, "outer")
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
func testActiveOcclusionsBecomeCameraBounds() {
|
|
87
|
+
let source = FakeFoldSource()
|
|
88
|
+
source.regions = [
|
|
89
|
+
ReservedRegion(kind: .occlusion, frame: CGRect(x: 280, y: 10, width: 60, height: 30), isActive: true),
|
|
90
|
+
ReservedRegion(kind: .occlusion, frame: CGRect(x: 500, y: 10, width: 40, height: 40), isActive: false)
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
let result = foldState(source)
|
|
94
|
+
|
|
95
|
+
XCTAssertEqual(result["cameraBounds"] as? [[String: Int]], [["x": 280, "y": 10, "width": 60, "height": 30]])
|
|
96
|
+
XCTAssertNil(result["activeDisplay"])
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
func testDeviceWithoutFoldReportsStub() {
|
|
100
|
+
let source = FakeFoldSource()
|
|
101
|
+
source.hasFold = false
|
|
102
|
+
source.regions = [ReservedRegion(kind: .division, frame: CGRect(x: 313, y: 0, width: 0, height: 890), isActive: true)]
|
|
103
|
+
|
|
104
|
+
let implementation = Foldable(provider: ReservedRegionFoldProvider(source: source))
|
|
105
|
+
|
|
106
|
+
XCTAssertFalse(implementation.isDeviceFoldable())
|
|
107
|
+
XCTAssertFalse(implementation.supportsTabletop())
|
|
108
|
+
XCTAssertEqual(implementation.getFoldState(in: view)["state"] as? String, "flat")
|
|
109
|
+
XCTAssertNil(implementation.getFoldState(in: view)["hingeBounds"])
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
func testFullyOpenHingeIsFlatEvenIfTheDivisionIsStillActive() {
|
|
113
|
+
let source = FakeFoldSource()
|
|
114
|
+
source.hingeStatus = .fullyOpen
|
|
115
|
+
source.regions = [ReservedRegion(kind: .division, frame: CGRect(x: 456, y: 0, width: 40, height: 669), isActive: true)]
|
|
116
|
+
|
|
117
|
+
let result = foldState(source)
|
|
118
|
+
|
|
119
|
+
XCTAssertEqual(result["state"] as? String, "flat")
|
|
120
|
+
XCTAssertEqual(result["isSeparating"] as? Bool, false)
|
|
121
|
+
XCTAssertEqual(result["posture"] as? String, "flat")
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
func testPartiallyOpenHingeIsHalfOpenedEvenIfTheDivisionIsNotActiveYet() {
|
|
125
|
+
let source = FakeFoldSource()
|
|
126
|
+
source.hingeStatus = .partiallyOpen
|
|
127
|
+
source.regions = [ReservedRegion(kind: .division, frame: CGRect(x: 456, y: 0, width: 0, height: 669), isActive: false)]
|
|
128
|
+
|
|
129
|
+
let result = foldState(source)
|
|
130
|
+
|
|
131
|
+
XCTAssertEqual(result["state"] as? String, "half-opened")
|
|
132
|
+
XCTAssertEqual(result["posture"] as? String, "book")
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
func testHingeAngleComesFromSource() {
|
|
136
|
+
let source = FakeFoldSource()
|
|
137
|
+
source.hingeAngle = 95
|
|
138
|
+
|
|
139
|
+
let implementation = Foldable(provider: ReservedRegionFoldProvider(source: source))
|
|
140
|
+
|
|
141
|
+
XCTAssertEqual(implementation.getHingeAngle()["angle"] as? Double, 95)
|
|
142
|
+
}
|
|
143
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@erkamyaman/capacitor-foldable",
|
|
3
|
+
"version": "7.0.0",
|
|
4
|
+
"description": "Fold-state and dual-display APIs for foldable devices in Capacitor apps",
|
|
5
|
+
"main": "dist/plugin.cjs.js",
|
|
6
|
+
"module": "dist/esm/index.js",
|
|
7
|
+
"types": "dist/esm/index.d.ts",
|
|
8
|
+
"unpkg": "dist/plugin.js",
|
|
9
|
+
"files": [
|
|
10
|
+
"ErkamyamanCapacitorFoldable.podspec",
|
|
11
|
+
"LICENSE",
|
|
12
|
+
"Package.swift",
|
|
13
|
+
"android/build.gradle",
|
|
14
|
+
"android/src/main/",
|
|
15
|
+
"dist/",
|
|
16
|
+
"ios/Sources",
|
|
17
|
+
"ios/Tests"
|
|
18
|
+
],
|
|
19
|
+
"author": "Erkam Yaman <erkamyaman35@gmail.com>",
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/erkamyaman/capacitor-foldable.git"
|
|
24
|
+
},
|
|
25
|
+
"bugs": {
|
|
26
|
+
"url": "https://github.com/erkamyaman/capacitor-foldable/issues"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"capacitor",
|
|
30
|
+
"plugin",
|
|
31
|
+
"native",
|
|
32
|
+
"foldable",
|
|
33
|
+
"fold",
|
|
34
|
+
"foldables",
|
|
35
|
+
"hinge",
|
|
36
|
+
"dual-screen",
|
|
37
|
+
"posture",
|
|
38
|
+
"large-screen",
|
|
39
|
+
"android",
|
|
40
|
+
"ios",
|
|
41
|
+
"pixel-fold",
|
|
42
|
+
"iphone-duo"
|
|
43
|
+
],
|
|
44
|
+
"scripts": {
|
|
45
|
+
"verify": "npm run verify:ios && npm run verify:android && npm run verify:web",
|
|
46
|
+
"verify:ios": "xcodebuild -scheme ErkamyamanCapacitorFoldable -destination generic/platform=iOS",
|
|
47
|
+
"verify:android": "cd android && ./gradlew clean build test && cd ..",
|
|
48
|
+
"verify:web": "npm run build && npm test",
|
|
49
|
+
"test": "node --test \"test/*.test.mjs\"",
|
|
50
|
+
"lint": "npm run eslint && npm run prettier -- --check && npm run swiftlint -- lint",
|
|
51
|
+
"fmt": "npm run eslint -- --fix && npm run prettier -- --write && npm run swiftlint -- --fix --format",
|
|
52
|
+
"eslint": "eslint . --ext ts",
|
|
53
|
+
"prettier": "prettier \"**/*.{css,html,ts,js,java}\" --plugin=prettier-plugin-java",
|
|
54
|
+
"swiftlint": "node-swiftlint",
|
|
55
|
+
"docgen": "docgen --api FoldablePlugin --output-readme README.md --output-json dist/docs.json",
|
|
56
|
+
"build": "npm run clean && npm run docgen && tsc && rollup -c rollup.config.mjs",
|
|
57
|
+
"clean": "rimraf ./dist",
|
|
58
|
+
"watch": "tsc --watch",
|
|
59
|
+
"prepublishOnly": "npm run build"
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"@capacitor/android": "^8.0.0",
|
|
63
|
+
"@capacitor/core": "^8.0.0",
|
|
64
|
+
"@capacitor/docgen": "^0.3.1",
|
|
65
|
+
"@capacitor/ios": "^8.0.0",
|
|
66
|
+
"@ionic/eslint-config": "^0.4.0",
|
|
67
|
+
"@ionic/prettier-config": "^4.0.0",
|
|
68
|
+
"@ionic/swiftlint-config": "^2.0.0",
|
|
69
|
+
"eslint": "^8.57.1",
|
|
70
|
+
"prettier": "^3.6.2",
|
|
71
|
+
"prettier-plugin-java": "^2.7.7",
|
|
72
|
+
"rimraf": "^6.1.0",
|
|
73
|
+
"rollup": "^4.53.2",
|
|
74
|
+
"swiftlint": "^2.0.0",
|
|
75
|
+
"typescript": "^5.9.3"
|
|
76
|
+
},
|
|
77
|
+
"peerDependencies": {
|
|
78
|
+
"@capacitor/core": "^7.0.0"
|
|
79
|
+
},
|
|
80
|
+
"prettier": "@ionic/prettier-config",
|
|
81
|
+
"swiftlint": "@ionic/swiftlint-config",
|
|
82
|
+
"eslintConfig": {
|
|
83
|
+
"extends": "@ionic/eslint-config/recommended"
|
|
84
|
+
},
|
|
85
|
+
"capacitor": {
|
|
86
|
+
"ios": {
|
|
87
|
+
"src": "ios"
|
|
88
|
+
},
|
|
89
|
+
"android": {
|
|
90
|
+
"src": "android"
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
"publishConfig": {
|
|
94
|
+
"access": "public"
|
|
95
|
+
}
|
|
96
|
+
}
|