@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,17 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
|
|
4
|
+
|
|
5
|
+
Pod::Spec.new do |s|
|
|
6
|
+
s.name = 'ErkamyamanCapacitorFoldable'
|
|
7
|
+
s.version = package['version']
|
|
8
|
+
s.summary = package['description']
|
|
9
|
+
s.license = package['license']
|
|
10
|
+
s.homepage = package['repository']['url']
|
|
11
|
+
s.author = package['author']
|
|
12
|
+
s.source = { :git => package['repository']['url'], :tag => s.version.to_s }
|
|
13
|
+
s.source_files = 'ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}'
|
|
14
|
+
s.ios.deployment_target = '14.0'
|
|
15
|
+
s.dependency 'Capacitor'
|
|
16
|
+
s.swift_version = '5.1'
|
|
17
|
+
end
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Erkam Yaman (erkamyaman)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/Package.swift
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// swift-tools-version: 5.9
|
|
2
|
+
import PackageDescription
|
|
3
|
+
|
|
4
|
+
let package = Package(
|
|
5
|
+
name: "ErkamyamanCapacitorFoldable",
|
|
6
|
+
platforms: [.iOS(.v14)],
|
|
7
|
+
products: [
|
|
8
|
+
.library(
|
|
9
|
+
name: "ErkamyamanCapacitorFoldable",
|
|
10
|
+
targets: ["FoldablePlugin"])
|
|
11
|
+
],
|
|
12
|
+
dependencies: [
|
|
13
|
+
.package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", "7.0.0"..<"8.0.0")
|
|
14
|
+
],
|
|
15
|
+
targets: [
|
|
16
|
+
.target(
|
|
17
|
+
name: "FoldablePlugin",
|
|
18
|
+
dependencies: [
|
|
19
|
+
.product(name: "Capacitor", package: "capacitor-swift-pm"),
|
|
20
|
+
.product(name: "Cordova", package: "capacitor-swift-pm")
|
|
21
|
+
],
|
|
22
|
+
path: "ios/Sources/FoldablePlugin"),
|
|
23
|
+
.testTarget(
|
|
24
|
+
name: "FoldablePluginTests",
|
|
25
|
+
dependencies: ["FoldablePlugin"],
|
|
26
|
+
path: "ios/Tests/FoldablePluginTests")
|
|
27
|
+
]
|
|
28
|
+
)
|
package/README.md
ADDED
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
<h1 align="center">Capacitor Foldable</h1>
|
|
2
|
+
<p align="center"><strong><code>@erkamyaman/capacitor-foldable</code></strong></p>
|
|
3
|
+
<p align="center">
|
|
4
|
+
<img src="https://img.shields.io/maintenance/yes/2026?style=for-the-badge" />
|
|
5
|
+
<a href="https://www.npmjs.com/package/@erkamyaman/capacitor-foldable"><img src="https://img.shields.io/npm/v/@erkamyaman/capacitor-foldable?style=for-the-badge" /></a>
|
|
6
|
+
<a href="https://www.npmjs.com/package/@erkamyaman/capacitor-foldable"><img src="https://img.shields.io/npm/dw/@erkamyaman/capacitor-foldable?style=for-the-badge" /></a>
|
|
7
|
+
<a href="LICENSE"><img src="https://img.shields.io/npm/l/@erkamyaman/capacitor-foldable?style=for-the-badge" /></a>
|
|
8
|
+
</p>
|
|
9
|
+
<p align="center">
|
|
10
|
+
Fold state, posture, hinge angle and size classes for foldable phones such as Galaxy Z Fold and Flip and Pixel Fold, in Capacitor 7 apps.
|
|
11
|
+
</p>
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
const { state, hingeOrientation } = await Foldable.getFoldState();
|
|
15
|
+
// { state: 'half-opened', hingeOrientation: 'horizontal' }
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
> [!NOTE]
|
|
19
|
+
> **This is v7, for Capacitor 7.** On Android every function and event works. On iOS only size classes are real: `getSizeClass()` and `sizeClassChange`. The other functions run but report no fold (`getFoldState()` returns `flat`, `getHingeAngle()` returns `{ angle: null }` and `getBarPlacement()` `{ verticalBarEdge: null }`), and `hingeAngleChange` and `barPlacementChange` never fire. Capacitor 7 apps must be built with Xcode 26, which doesn't have Apple's iPhone Duo APIs. For iPhone Duo, use v8 with Capacitor 8.5 or later.
|
|
20
|
+
|
|
21
|
+
### Versions
|
|
22
|
+
|
|
23
|
+
| Capacitor | Plugin | iOS |
|
|
24
|
+
| --------- | ------ | --- |
|
|
25
|
+
| v7.x | v7.x | Size classes only, build with Xcode 26 |
|
|
26
|
+
| v8.x | v8.x | Size classes, plus iPhone Duo on Capacitor 8.5+ |
|
|
27
|
+
|
|
28
|
+
### Supported Platforms
|
|
29
|
+
|
|
30
|
+
| Platform | Status | Minimum |
|
|
31
|
+
| -------- | ------ | ------- |
|
|
32
|
+
| Android | Supported | API 23, `compileSdk` 34 |
|
|
33
|
+
| iOS | Size classes. No iPhone Duo features: use plugin v8 with Capacitor 8.5+ for those | iOS 14, built with Xcode 26 |
|
|
34
|
+
| Web | Stub, returns `flat` | |
|
|
35
|
+
|
|
36
|
+
## Do you need this plugin?
|
|
37
|
+
|
|
38
|
+
Web views already resize with the window, so a responsive layout that pads with every safe-area inset adapts to foldables without a plugin. Use this one when your app needs to know what CSS can't tell it:
|
|
39
|
+
|
|
40
|
+
- **Whether the device folds at all**, and whether it can stand half-open like a laptop.
|
|
41
|
+
- **Where the fold is**, and whether it splits the screen.
|
|
42
|
+
- **How the device is held**: flat, tabletop or book.
|
|
43
|
+
- **The hinge angle**, for effects and interactions.
|
|
44
|
+
- **Outer or inner display**, and Material window size classes.
|
|
45
|
+
- **The outer display on Android foldables**: rear display and dual screen.
|
|
46
|
+
|
|
47
|
+
## Installation
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npm install @erkamyaman/capacitor-foldable@7
|
|
51
|
+
npx cap sync
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
This is the Capacitor 7 version. On Capacitor 8, install the latest `@erkamyaman/capacitor-foldable` instead, which also supports iPhone Duo.
|
|
55
|
+
|
|
56
|
+
Build Capacitor 7 apps with Xcode 26. iOS 27 requires the scene lifecycle for apps built with Xcode 27, and Capacitor 7 doesn't adopt it, so a Capacitor 7 app built with Xcode 27 crashes at launch, with or without this plugin.
|
|
57
|
+
|
|
58
|
+
## Usage
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
import { Foldable } from '@erkamyaman/capacitor-foldable';
|
|
62
|
+
|
|
63
|
+
const { posture } = await Foldable.getFoldState(); // 'flat' | 'tabletop' | 'book'
|
|
64
|
+
|
|
65
|
+
Foldable.addListener('foldStateChange', ({ posture }) => {
|
|
66
|
+
document.documentElement.dataset.posture = posture;
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
More in [Examples](docs/examples.md).
|
|
71
|
+
|
|
72
|
+
## Web standard APIs
|
|
73
|
+
|
|
74
|
+
Chrome ships the [Device Posture API](https://developer.mozilla.org/docs/Web/API/Device_Posture_API) and the [Viewport Segments API](https://developer.mozilla.org/docs/Web/API/Viewport_Segments_API), but Android's WebView turns both off and Safari doesn't support them, so neither works inside a Capacitor app. `installFoldablePolyfill()` fills them in from the native fold state, and mirrors the CSS features as classes and variables on `<html>`:
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
import { installFoldablePolyfill } from '@erkamyaman/capacitor-foldable';
|
|
78
|
+
|
|
79
|
+
await installFoldablePolyfill();
|
|
80
|
+
|
|
81
|
+
navigator.devicePosture.type; // 'continuous' | 'folded'
|
|
82
|
+
window.viewport.segments; // two DOMRects when the fold splits the web view
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
| Standard CSS | With the polyfill |
|
|
86
|
+
| --- | --- |
|
|
87
|
+
| `@media (device-posture: folded)` | `.device-posture-folded` |
|
|
88
|
+
| `@media (horizontal-viewport-segments: 2)` | `.horizontal-viewport-segments-2` |
|
|
89
|
+
| `@media (vertical-viewport-segments: 2)` | `.vertical-viewport-segments-2` |
|
|
90
|
+
| `env(viewport-segment-width 0 0)` | `var(--viewport-segment-width-0-0)` |
|
|
91
|
+
| Native bars moved to the side (iPhone Duo) | `.vertical-bars-leading`, `.vertical-bars-trailing` |
|
|
92
|
+
|
|
93
|
+
## Guides
|
|
94
|
+
|
|
95
|
+
- [Examples](docs/examples.md)
|
|
96
|
+
- [Layout tips](docs/layout-tips.md)
|
|
97
|
+
- [Testing on Android](docs/testing-android.md)
|
|
98
|
+
- [iPhone Duo](docs/iphone-duo.md)
|
|
99
|
+
|
|
100
|
+
## API
|
|
101
|
+
|
|
102
|
+
<docgen-index>
|
|
103
|
+
|
|
104
|
+
* [`isDeviceFoldable()`](#isdevicefoldable)
|
|
105
|
+
* [`getFoldState()`](#getfoldstate)
|
|
106
|
+
* [`getHingeAngle()`](#gethingeangle)
|
|
107
|
+
* [`getSizeClass()`](#getsizeclass)
|
|
108
|
+
* [`getDisplayModes()`](#getdisplaymodes)
|
|
109
|
+
* [`getBarPlacement()`](#getbarplacement)
|
|
110
|
+
* [`startRearDisplay()`](#startreardisplay)
|
|
111
|
+
* [`stopRearDisplay()`](#stopreardisplay)
|
|
112
|
+
* [`startDualScreen(...)`](#startdualscreen)
|
|
113
|
+
* [`stopDualScreen()`](#stopdualscreen)
|
|
114
|
+
* [`addListener('foldStateChange', ...)`](#addlistenerfoldstatechange-)
|
|
115
|
+
* [`addListener('hingeAngleChange', ...)`](#addlistenerhingeanglechange-)
|
|
116
|
+
* [`addListener('sizeClassChange', ...)`](#addlistenersizeclasschange-)
|
|
117
|
+
* [`addListener('displayModeChange', ...)`](#addlistenerdisplaymodechange-)
|
|
118
|
+
* [`addListener('barPlacementChange', ...)`](#addlistenerbarplacementchange-)
|
|
119
|
+
* [Interfaces](#interfaces)
|
|
120
|
+
* [Type Aliases](#type-aliases)
|
|
121
|
+
|
|
122
|
+
</docgen-index>
|
|
123
|
+
|
|
124
|
+
<docgen-api>
|
|
125
|
+
<!--Update the source file JSDoc comments and rerun docgen to update the docs below-->
|
|
126
|
+
|
|
127
|
+
### isDeviceFoldable()
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
isDeviceFoldable() => Promise<{ foldable: boolean; supportsTabletop: boolean; }>
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Whether the device has a fold at all, and whether it can be propped half
|
|
134
|
+
open like a laptop. Both `false` on web, and on iOS except on iPhone Duo
|
|
135
|
+
(iOS 27.1 or later).
|
|
136
|
+
|
|
137
|
+
**Returns:** <code>Promise<{ foldable: boolean; supportsTabletop: boolean; }></code>
|
|
138
|
+
|
|
139
|
+
**Since:** 7.0.0
|
|
140
|
+
|
|
141
|
+
--------------------
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
### getFoldState()
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
getFoldState() => Promise<FoldState>
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Read the current fold state. Resolves to
|
|
151
|
+
`{ state: 'flat', isSeparating: false, posture: 'flat' }` when there is no
|
|
152
|
+
fold information.
|
|
153
|
+
|
|
154
|
+
**Returns:** <code>Promise<<a href="#foldstate">FoldState</a>></code>
|
|
155
|
+
|
|
156
|
+
**Since:** 7.0.0
|
|
157
|
+
|
|
158
|
+
--------------------
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
### getHingeAngle()
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
getHingeAngle() => Promise<{ angle: number | null; }>
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Read the angle between the two halves of the device, in degrees: `0` when
|
|
168
|
+
closed, `180` when flat. Resolves to `{ angle: null }` on devices without a
|
|
169
|
+
hinge angle sensor, on web, and on iOS before 27.1.
|
|
170
|
+
|
|
171
|
+
**Returns:** <code>Promise<{ angle: number | null; }></code>
|
|
172
|
+
|
|
173
|
+
**Since:** 7.0.0
|
|
174
|
+
|
|
175
|
+
--------------------
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
### getSizeClass()
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
getSizeClass() => Promise<SizeClass>
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Read the window's size classes: Apple's compact and regular, the signal its
|
|
185
|
+
iPhone Duo guidelines recommend for telling the outer display from the inner
|
|
186
|
+
one, and Material's width and height classes. On iOS `horizontal` and
|
|
187
|
+
`vertical` are UIKit's size classes; everything else comes from the window
|
|
188
|
+
size.
|
|
189
|
+
|
|
190
|
+
**Returns:** <code>Promise<<a href="#sizeclass">SizeClass</a>></code>
|
|
191
|
+
|
|
192
|
+
**Since:** 7.0.0
|
|
193
|
+
|
|
194
|
+
--------------------
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
### getDisplayModes()
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
getDisplayModes() => Promise<DisplayModes>
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Read which of the foldable display modes the device offers right now. Both
|
|
204
|
+
are `'unsupported'` on iOS and web.
|
|
205
|
+
|
|
206
|
+
**Returns:** <code>Promise<<a href="#displaymodes">DisplayModes</a>></code>
|
|
207
|
+
|
|
208
|
+
**Since:** 7.0.0
|
|
209
|
+
|
|
210
|
+
--------------------
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
### getBarPlacement()
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
getBarPlacement() => Promise<BarPlacement>
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Read where native bars go. Native tab bars and toolbars move to the side of
|
|
220
|
+
the display on iPhone Duo, but HTML ones stay put, so use this to move your
|
|
221
|
+
own tab bar too.
|
|
222
|
+
|
|
223
|
+
**Returns:** <code>Promise<<a href="#barplacement">BarPlacement</a>></code>
|
|
224
|
+
|
|
225
|
+
**Since:** 7.0.0
|
|
226
|
+
|
|
227
|
+
--------------------
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
### startRearDisplay()
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
startRearDisplay() => Promise<void>
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Move the app to the outer display. Android asks the user to confirm first,
|
|
237
|
+
and the promise resolves once the app has moved. Rejects when rear display
|
|
238
|
+
mode is not `'available'`. Only on Android.
|
|
239
|
+
|
|
240
|
+
**Since:** 7.0.0
|
|
241
|
+
|
|
242
|
+
--------------------
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
### stopRearDisplay()
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
stopRearDisplay() => Promise<void>
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Move the app back to the inner display.
|
|
252
|
+
|
|
253
|
+
**Since:** 7.0.0
|
|
254
|
+
|
|
255
|
+
--------------------
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
### startDualScreen(...)
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
startDualScreen(options: { url: string; }) => Promise<void>
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Show a page on the outer display while the app stays on the inner one. A
|
|
265
|
+
relative `url` resolves against the app's own URL, so `'cover.html'` loads a
|
|
266
|
+
page bundled with the app. The page runs in its own web view without access
|
|
267
|
+
to Capacitor plugins. Calling this again while dual-screen mode is active
|
|
268
|
+
replaces the page. Only on Android.
|
|
269
|
+
|
|
270
|
+
| Param | Type |
|
|
271
|
+
| ------------- | ----------------------------- |
|
|
272
|
+
| **`options`** | <code>{ url: string; }</code> |
|
|
273
|
+
|
|
274
|
+
**Since:** 7.0.0
|
|
275
|
+
|
|
276
|
+
--------------------
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
### stopDualScreen()
|
|
280
|
+
|
|
281
|
+
```typescript
|
|
282
|
+
stopDualScreen() => Promise<void>
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Close the page on the outer display.
|
|
286
|
+
|
|
287
|
+
**Since:** 7.0.0
|
|
288
|
+
|
|
289
|
+
--------------------
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
### addListener('foldStateChange', ...)
|
|
293
|
+
|
|
294
|
+
```typescript
|
|
295
|
+
addListener(eventName: 'foldStateChange', listenerFunc: (state: FoldState) => void) => Promise<PluginListenerHandle>
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
Listen for fold state changes. On a foldable this also fires when the
|
|
299
|
+
device rotates, because `hingeOrientation` and `hingeBounds` rotate with
|
|
300
|
+
the window.
|
|
301
|
+
|
|
302
|
+
| Param | Type |
|
|
303
|
+
| ------------------ | ------------------------------------------------------------------- |
|
|
304
|
+
| **`eventName`** | <code>'foldStateChange'</code> |
|
|
305
|
+
| **`listenerFunc`** | <code>(state: <a href="#foldstate">FoldState</a>) => void</code> |
|
|
306
|
+
|
|
307
|
+
**Returns:** <code>Promise<<a href="#pluginlistenerhandle">PluginListenerHandle</a>></code>
|
|
308
|
+
|
|
309
|
+
**Since:** 7.0.0
|
|
310
|
+
|
|
311
|
+
--------------------
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
### addListener('hingeAngleChange', ...)
|
|
315
|
+
|
|
316
|
+
```typescript
|
|
317
|
+
addListener(eventName: 'hingeAngleChange', listenerFunc: (event: { angle: number; }) => void) => Promise<PluginListenerHandle>
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
Listen for hinge angle changes. On Android the hinge sensor only runs while
|
|
321
|
+
at least one of these listeners is registered. On iOS it fires on iPhone
|
|
322
|
+
Duo (iOS 27.1 or later). Never fires on web.
|
|
323
|
+
|
|
324
|
+
| Param | Type |
|
|
325
|
+
| ------------------ | --------------------------------------------------- |
|
|
326
|
+
| **`eventName`** | <code>'hingeAngleChange'</code> |
|
|
327
|
+
| **`listenerFunc`** | <code>(event: { angle: number; }) => void</code> |
|
|
328
|
+
|
|
329
|
+
**Returns:** <code>Promise<<a href="#pluginlistenerhandle">PluginListenerHandle</a>></code>
|
|
330
|
+
|
|
331
|
+
**Since:** 7.0.0
|
|
332
|
+
|
|
333
|
+
--------------------
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
### addListener('sizeClassChange', ...)
|
|
337
|
+
|
|
338
|
+
```typescript
|
|
339
|
+
addListener(eventName: 'sizeClassChange', listenerFunc: (sizeClass: SizeClass) => void) => Promise<PluginListenerHandle>
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
Listen for size class changes, such as unfolding the device, rotating it or
|
|
343
|
+
resizing the window. On iOS this fires when UIKit's size classes change
|
|
344
|
+
(iOS 17 or later) or the device rotates, so a resize that keeps the same
|
|
345
|
+
size classes may not update `widthClass` and `heightClass` until then.
|
|
346
|
+
|
|
347
|
+
| Param | Type |
|
|
348
|
+
| ------------------ | ----------------------------------------------------------------------- |
|
|
349
|
+
| **`eventName`** | <code>'sizeClassChange'</code> |
|
|
350
|
+
| **`listenerFunc`** | <code>(sizeClass: <a href="#sizeclass">SizeClass</a>) => void</code> |
|
|
351
|
+
|
|
352
|
+
**Returns:** <code>Promise<<a href="#pluginlistenerhandle">PluginListenerHandle</a>></code>
|
|
353
|
+
|
|
354
|
+
**Since:** 7.0.0
|
|
355
|
+
|
|
356
|
+
--------------------
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
### addListener('displayModeChange', ...)
|
|
360
|
+
|
|
361
|
+
```typescript
|
|
362
|
+
addListener(eventName: 'displayModeChange', listenerFunc: (modes: DisplayModes) => void) => Promise<PluginListenerHandle>
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
Listen for changes to the display modes, including a mode ending because
|
|
366
|
+
the user folded or unfolded the device. Never fires on iOS and web.
|
|
367
|
+
|
|
368
|
+
| Param | Type |
|
|
369
|
+
| ------------------ | ------------------------------------------------------------------------- |
|
|
370
|
+
| **`eventName`** | <code>'displayModeChange'</code> |
|
|
371
|
+
| **`listenerFunc`** | <code>(modes: <a href="#displaymodes">DisplayModes</a>) => void</code> |
|
|
372
|
+
|
|
373
|
+
**Returns:** <code>Promise<<a href="#pluginlistenerhandle">PluginListenerHandle</a>></code>
|
|
374
|
+
|
|
375
|
+
**Since:** 7.0.0
|
|
376
|
+
|
|
377
|
+
--------------------
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
### addListener('barPlacementChange', ...)
|
|
381
|
+
|
|
382
|
+
```typescript
|
|
383
|
+
addListener(eventName: 'barPlacementChange', listenerFunc: (placement: BarPlacement) => void) => Promise<PluginListenerHandle>
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
Listen for bar placement changes, such as opening or rotating iPhone Duo.
|
|
387
|
+
Only fires on iOS 27.1 or later.
|
|
388
|
+
|
|
389
|
+
| Param | Type |
|
|
390
|
+
| ------------------ | ----------------------------------------------------------------------------- |
|
|
391
|
+
| **`eventName`** | <code>'barPlacementChange'</code> |
|
|
392
|
+
| **`listenerFunc`** | <code>(placement: <a href="#barplacement">BarPlacement</a>) => void</code> |
|
|
393
|
+
|
|
394
|
+
**Returns:** <code>Promise<<a href="#pluginlistenerhandle">PluginListenerHandle</a>></code>
|
|
395
|
+
|
|
396
|
+
**Since:** 7.0.0
|
|
397
|
+
|
|
398
|
+
--------------------
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
### Interfaces
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
#### FoldState
|
|
405
|
+
|
|
406
|
+
| Prop | Type | Description | Since |
|
|
407
|
+
| ---------------------- | ----------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- |
|
|
408
|
+
| **`state`** | <code>'flat' \| 'half-opened' \| 'closed'</code> | Posture of the fold. `'closed'` is never reported today: a device shut on its cover display reports `'flat'`. | 7.0.0 |
|
|
409
|
+
| **`isSeparating`** | <code>boolean</code> | Whether the fold splits the web view into two areas: `true` when half-opened, or when the hinge has a physical gap. | 7.0.0 |
|
|
410
|
+
| **`posture`** | <code>'flat' \| 'tabletop' \| 'book'</code> | How the device is held: `'tabletop'` when half-opened with a horizontal hinge, like a laptop, `'book'` when half-opened with a vertical hinge, and `'flat'` otherwise. | 7.0.0 |
|
|
411
|
+
| **`hingeOrientation`** | <code>'horizontal' \| 'vertical'</code> | Direction of the hinge relative to the window, so it flips when the device rotates. Omitted when there is no fold. | 7.0.0 |
|
|
412
|
+
| **`hingeBounds`** | <code>{ x: number; y: number; width: number; height: number; }</code> | Position of the fold in CSS pixels, relative to the web view. Zero wide (or zero tall) on a seamless fold. Omitted when there is no fold. | 7.0.0 |
|
|
413
|
+
| **`occludedBounds`** | <code>{ x: number; y: number; width: number; height: number; }</code> | Area of the web view the hinge covers, in CSS pixels. Only present on devices with a physical gap. | 7.0.0 |
|
|
414
|
+
| **`activeDisplay`** | <code>'inner' \| 'outer'</code> | Which display is showing the app, on a device with an inner and an outer display such as iPhone Duo. Reported on iPhone Duo (iOS 27.1 or later) and omitted elsewhere. | 7.0.0 |
|
|
415
|
+
| **`cameraBounds`** | <code>{ x: number; y: number; width: number; height: number; }[]</code> | Areas of the web view covered by a front-facing camera, in CSS pixels. On Android these are the display cutouts. On iPhone Duo they are the outer camera and the under-display inner camera while it is in use. Omitted when there are none. | 7.0.0 |
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
#### SizeClass
|
|
419
|
+
|
|
420
|
+
| Prop | Type | Description | Since |
|
|
421
|
+
| ----------------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- |
|
|
422
|
+
| **`horizontal`** | <code>'compact' \| 'regular'</code> | Width size class of the window: `'compact'` on a phone and on the outer display of a foldable, `'regular'` on the inner display, tablets and wide windows. On Android and web `'regular'` starts at 600 CSS pixels. | 7.0.0 |
|
|
423
|
+
| **`vertical`** | <code>'compact' \| 'regular'</code> | Height size class of the window: `'compact'` on a phone in landscape. On Android and web `'regular'` starts at 480 CSS pixels. | 7.0.0 |
|
|
424
|
+
| **`widthClass`** | <code>'compact' \| 'medium' \| 'expanded' \| 'large' \| 'extraLarge'</code> | Material window width class, from the window width in CSS pixels: `'compact'` below 600, `'medium'` below 840, `'expanded'` below 1200, `'large'` below 1600 and `'extraLarge'` from 1600. | 7.0.0 |
|
|
425
|
+
| **`heightClass`** | <code>'compact' \| 'medium' \| 'expanded'</code> | Material window height class, from the window height in CSS pixels: `'compact'` below 480, `'medium'` below 900 and `'expanded'` from 900. | 7.0.0 |
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
#### DisplayModes
|
|
429
|
+
|
|
430
|
+
| Prop | Type | Description | Since |
|
|
431
|
+
| ----------------- | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ----- |
|
|
432
|
+
| **`rearDisplay`** | <code><a href="#displaymodestatus">DisplayModeStatus</a></code> | Rear display mode moves the app to the outer display, so people can frame a photo with the rear cameras. Only on Android foldables that offer it. | 7.0.0 |
|
|
433
|
+
| **`dualScreen`** | <code><a href="#displaymodestatus">DisplayModeStatus</a></code> | Dual-screen mode shows a second page on the outer display while the app stays on the inner one. Only on Android foldables that offer it. | 7.0.0 |
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
#### BarPlacement
|
|
437
|
+
|
|
438
|
+
| Prop | Type | Description | Since |
|
|
439
|
+
| --------------------- | -------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- |
|
|
440
|
+
| **`verticalBarEdge`** | <code>'leading' \| 'trailing' \| null</code> | The edge iPhone Duo moves tab bars and toolbars to when it lays them out vertically: `'leading'` or `'trailing'` in the reading direction, so `'leading'` is the left edge in left-to-right languages. `null` when bars stay horizontal, and always on Android, web and iOS before 27.1. | 7.0.0 |
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
#### PluginListenerHandle
|
|
444
|
+
|
|
445
|
+
| Prop | Type |
|
|
446
|
+
| ------------ | ----------------------------------------- |
|
|
447
|
+
| **`remove`** | <code>() => Promise<void></code> |
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
### Type Aliases
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
#### DisplayModeStatus
|
|
454
|
+
|
|
455
|
+
<code>'unsupported' | 'unavailable' | 'available' | 'active'</code>
|
|
456
|
+
|
|
457
|
+
</docgen-api>
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
ext {
|
|
2
|
+
junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.13.2'
|
|
3
|
+
androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.1'
|
|
4
|
+
androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.3.0'
|
|
5
|
+
androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.7.0'
|
|
6
|
+
androidxCoreKTXVersion = project.hasProperty('androidxCoreKTXVersion') ? rootProject.ext.androidxCoreKTXVersion : '1.15.0'
|
|
7
|
+
androidxWindowVersion = project.hasProperty('androidxWindowVersion') ? rootProject.ext.androidxWindowVersion : '1.5.1'
|
|
8
|
+
androidxLifecycleVersion = project.hasProperty('androidxLifecycleVersion') ? rootProject.ext.androidxLifecycleVersion : '2.8.7'
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
buildscript {
|
|
12
|
+
ext.kotlin_version = project.hasProperty("kotlin_version") ? rootProject.ext.kotlin_version : '2.1.21'
|
|
13
|
+
repositories {
|
|
14
|
+
google()
|
|
15
|
+
mavenCentral()
|
|
16
|
+
}
|
|
17
|
+
dependencies {
|
|
18
|
+
classpath 'com.android.tools.build:gradle:8.13.0'
|
|
19
|
+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
apply plugin: 'com.android.library'
|
|
24
|
+
apply plugin: 'kotlin-android'
|
|
25
|
+
|
|
26
|
+
android {
|
|
27
|
+
namespace = "io.github.erkamyaman.foldable"
|
|
28
|
+
compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 36
|
|
29
|
+
defaultConfig {
|
|
30
|
+
minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 24
|
|
31
|
+
targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 36
|
|
32
|
+
versionCode 1
|
|
33
|
+
versionName "1.0"
|
|
34
|
+
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
|
35
|
+
}
|
|
36
|
+
buildTypes {
|
|
37
|
+
release {
|
|
38
|
+
minifyEnabled false
|
|
39
|
+
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
lintOptions {
|
|
43
|
+
abortOnError = false
|
|
44
|
+
}
|
|
45
|
+
compileOptions {
|
|
46
|
+
sourceCompatibility JavaVersion.VERSION_21
|
|
47
|
+
targetCompatibility JavaVersion.VERSION_21
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
kotlin {
|
|
52
|
+
jvmToolchain(21)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
repositories {
|
|
56
|
+
google()
|
|
57
|
+
mavenCentral()
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
dependencies {
|
|
62
|
+
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
|
63
|
+
implementation project(':capacitor-android')
|
|
64
|
+
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
|
|
65
|
+
implementation "androidx.core:core-ktx:$androidxCoreKTXVersion"
|
|
66
|
+
implementation "androidx.window:window:$androidxWindowVersion"
|
|
67
|
+
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$androidxLifecycleVersion"
|
|
68
|
+
testImplementation "junit:junit:$junitVersion"
|
|
69
|
+
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
|
|
70
|
+
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
|
|
71
|
+
}
|