@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.
Files changed (43) hide show
  1. package/ErkamyamanCapacitorFoldable.podspec +17 -0
  2. package/LICENSE +21 -0
  3. package/Package.swift +28 -0
  4. package/README.md +457 -0
  5. package/android/build.gradle +71 -0
  6. package/android/src/main/AndroidManifest.xml +2 -0
  7. package/android/src/main/kotlin/io/github/erkamyaman/foldable/DisplayModes.kt +115 -0
  8. package/android/src/main/kotlin/io/github/erkamyaman/foldable/Foldable.kt +189 -0
  9. package/android/src/main/kotlin/io/github/erkamyaman/foldable/FoldablePlugin.kt +333 -0
  10. package/android/src/main/res/.gitkeep +0 -0
  11. package/dist/docs.json +584 -0
  12. package/dist/esm/css.d.ts +8 -0
  13. package/dist/esm/css.js +29 -0
  14. package/dist/esm/css.js.map +1 -0
  15. package/dist/esm/definitions.d.ts +267 -0
  16. package/dist/esm/definitions.js +2 -0
  17. package/dist/esm/definitions.js.map +1 -0
  18. package/dist/esm/index.d.ts +5 -0
  19. package/dist/esm/index.js +9 -0
  20. package/dist/esm/index.js.map +1 -0
  21. package/dist/esm/polyfill.d.ts +19 -0
  22. package/dist/esm/polyfill.js +76 -0
  23. package/dist/esm/polyfill.js.map +1 -0
  24. package/dist/esm/segments.d.ts +8 -0
  25. package/dist/esm/segments.js +26 -0
  26. package/dist/esm/segments.js.map +1 -0
  27. package/dist/esm/size-class.d.ts +2 -0
  28. package/dist/esm/size-class.js +33 -0
  29. package/dist/esm/size-class.js.map +1 -0
  30. package/dist/esm/web.d.ts +21 -0
  31. package/dist/esm/web.js +46 -0
  32. package/dist/esm/web.js.map +1 -0
  33. package/dist/plugin.cjs.js +222 -0
  34. package/dist/plugin.cjs.js.map +1 -0
  35. package/dist/plugin.js +225 -0
  36. package/dist/plugin.js.map +1 -0
  37. package/ios/Sources/FoldablePlugin/Foldable.swift +156 -0
  38. package/ios/Sources/FoldablePlugin/FoldablePlugin.swift +194 -0
  39. package/ios/Sources/FoldablePlugin/ReservedRegionFoldProvider.swift +69 -0
  40. package/ios/Sources/FoldablePlugin/UIKitFoldSource.swift +53 -0
  41. package/ios/Tests/FoldablePluginTests/FoldableTests.swift +104 -0
  42. package/ios/Tests/FoldablePluginTests/ReservedRegionFoldProviderTests.swift +143 -0
  43. 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
+ }