@capgo/capacitor-screen-orientation 8.1.20 → 8.2.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/README.md +235 -9
- package/android/build.gradle +2 -0
- package/android/src/main/java/ee/forgr/capacitor/screenorientation/CapacitorScreenOrientationPlugin.java +189 -1
- package/android/src/main/java/ee/forgr/capacitor/screenorientation/FoldLayout.java +176 -0
- package/dist/docs.json +525 -10
- package/dist/esm/definitions.d.ts +234 -0
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +8 -1
- package/dist/esm/web.js +43 -0
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +45 -1
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +45 -1
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CapacitorScreenOrientationPlugin/CapacitorScreenOrientationPlugin.swift +100 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -20,6 +20,7 @@ Screen orientation plugin with support for detecting true physical device orient
|
|
|
20
20
|
- 🔒 **Detect if orientation lock is enabled** by comparing physical vs UI orientation
|
|
21
21
|
- 🔄 Real-time orientation change detection
|
|
22
22
|
- 🎯 Lock orientation to specific modes
|
|
23
|
+
- 📖 **Foldable posture** on Android: flat, tabletop, or book, plus hinge angle and window size class
|
|
23
24
|
- 🌐 Web platform support
|
|
24
25
|
|
|
25
26
|
**Important Note:** This plugin can detect the physical orientation of the device using motion sensors, but it **cannot bypass the UI orientation lock**. The screen will still respect the user's orientation lock setting. This is useful for knowing how the device is physically held vs. how the UI is displayed.
|
|
@@ -131,6 +132,10 @@ await ScreenOrientation.stopOrientationTracking();
|
|
|
131
132
|
|
|
132
133
|
// Remove listener
|
|
133
134
|
await listener.remove();
|
|
135
|
+
|
|
136
|
+
// Foldables: flat, tabletop, or book. Flat on phones that do not fold.
|
|
137
|
+
const { posture } = await ScreenOrientation.getFoldState();
|
|
138
|
+
const { widthClass } = await ScreenOrientation.getSizeClass();
|
|
134
139
|
```
|
|
135
140
|
|
|
136
141
|
## API
|
|
@@ -143,7 +148,14 @@ await listener.remove();
|
|
|
143
148
|
* [`startOrientationTracking(...)`](#startorientationtracking)
|
|
144
149
|
* [`stopOrientationTracking()`](#stoporientationtracking)
|
|
145
150
|
* [`isOrientationLocked()`](#isorientationlocked)
|
|
151
|
+
* [`isDeviceFoldable()`](#isdevicefoldable)
|
|
152
|
+
* [`getFoldState()`](#getfoldstate)
|
|
153
|
+
* [`getHingeAngle()`](#gethingeangle)
|
|
154
|
+
* [`getSizeClass()`](#getsizeclass)
|
|
146
155
|
* [`addListener('screenOrientationChange', ...)`](#addlistenerscreenorientationchange-)
|
|
156
|
+
* [`addListener('foldStateChange', ...)`](#addlistenerfoldstatechange-)
|
|
157
|
+
* [`addListener('hingeAngleChange', ...)`](#addlistenerhingeanglechange-)
|
|
158
|
+
* [`addListener('sizeClassChange', ...)`](#addlistenersizeclasschange-)
|
|
147
159
|
* [`removeAllListeners()`](#removealllisteners)
|
|
148
160
|
* [`getPluginVersion()`](#getpluginversion)
|
|
149
161
|
* [Interfaces](#interfaces)
|
|
@@ -275,26 +287,150 @@ Works on both iOS (Core Motion) and Android (Accelerometer).
|
|
|
275
287
|
--------------------
|
|
276
288
|
|
|
277
289
|
|
|
290
|
+
### isDeviceFoldable()
|
|
291
|
+
|
|
292
|
+
```typescript
|
|
293
|
+
isDeviceFoldable() => Promise<DeviceFoldableResult>
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
Whether this device folds, and whether it can stand half-open like a laptop.
|
|
297
|
+
|
|
298
|
+
Both flags are `false` on web and iOS.
|
|
299
|
+
|
|
300
|
+
**Returns:** <code>Promise<<a href="#devicefoldableresult">DeviceFoldableResult</a>></code>
|
|
301
|
+
|
|
302
|
+
**Since:** 8.2.0
|
|
303
|
+
|
|
304
|
+
--------------------
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
### getFoldState()
|
|
308
|
+
|
|
309
|
+
```typescript
|
|
310
|
+
getFoldState() => Promise<FoldState>
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
Read the current fold.
|
|
314
|
+
|
|
315
|
+
Resolves to a flat state when the device has no fold.
|
|
316
|
+
|
|
317
|
+
**Returns:** <code>Promise<<a href="#foldstate">FoldState</a>></code>
|
|
318
|
+
|
|
319
|
+
**Since:** 8.2.0
|
|
320
|
+
|
|
321
|
+
--------------------
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
### getHingeAngle()
|
|
325
|
+
|
|
326
|
+
```typescript
|
|
327
|
+
getHingeAngle() => Promise<HingeAngleResult>
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
Read the hinge angle in degrees.
|
|
331
|
+
|
|
332
|
+
`0` is closed and `180` is flat. `angle` is `null` without a hinge sensor.
|
|
333
|
+
|
|
334
|
+
**Returns:** <code>Promise<<a href="#hingeangleresult">HingeAngleResult</a>></code>
|
|
335
|
+
|
|
336
|
+
**Since:** 8.2.0
|
|
337
|
+
|
|
338
|
+
--------------------
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
### getSizeClass()
|
|
342
|
+
|
|
343
|
+
```typescript
|
|
344
|
+
getSizeClass() => Promise<SizeClass>
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
Read the window size classes.
|
|
348
|
+
|
|
349
|
+
**Returns:** <code>Promise<<a href="#sizeclass">SizeClass</a>></code>
|
|
350
|
+
|
|
351
|
+
**Since:** 8.2.0
|
|
352
|
+
|
|
353
|
+
--------------------
|
|
354
|
+
|
|
355
|
+
|
|
278
356
|
### addListener('screenOrientationChange', ...)
|
|
279
357
|
|
|
280
358
|
```typescript
|
|
281
359
|
addListener(eventName: 'screenOrientationChange', listenerFunc: (result: ScreenOrientationResult) => void) => Promise<PluginListenerHandle>
|
|
282
360
|
```
|
|
283
361
|
|
|
284
|
-
|
|
362
|
+
| Param | Type |
|
|
363
|
+
| ------------------ | ------------------------------------------------------------------------------------------------ |
|
|
364
|
+
| **`eventName`** | <code>'screenOrientationChange'</code> |
|
|
365
|
+
| **`listenerFunc`** | <code>(result: <a href="#screenorientationresult">ScreenOrientationResult</a>) => void</code> |
|
|
285
366
|
|
|
286
|
-
|
|
287
|
-
If motion-based tracking is enabled, this will also fire for orientation changes
|
|
288
|
-
detected by motion sensors even when orientation lock is enabled.
|
|
367
|
+
**Returns:** <code>Promise<<a href="#pluginlistenerhandle">PluginListenerHandle</a>></code>
|
|
289
368
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
369
|
+
--------------------
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
### addListener('foldStateChange', ...)
|
|
373
|
+
|
|
374
|
+
```typescript
|
|
375
|
+
addListener(eventName: 'foldStateChange', listenerFunc: (state: FoldState) => void) => Promise<PluginListenerHandle>
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
Listen for fold changes.
|
|
379
|
+
|
|
380
|
+
On a foldable this also fires when the device rotates, because the hinge
|
|
381
|
+
bounds rotate with the window.
|
|
382
|
+
|
|
383
|
+
| Param | Type | Description |
|
|
384
|
+
| ------------------ | ------------------------------------------------------------------- | ------------------------------------------ |
|
|
385
|
+
| **`eventName`** | <code>'foldStateChange'</code> | The event name. Must be 'foldStateChange'. |
|
|
386
|
+
| **`listenerFunc`** | <code>(state: <a href="#foldstate">FoldState</a>) => void</code> | Callback invoked with the new fold state. |
|
|
294
387
|
|
|
295
388
|
**Returns:** <code>Promise<<a href="#pluginlistenerhandle">PluginListenerHandle</a>></code>
|
|
296
389
|
|
|
297
|
-
**Since:**
|
|
390
|
+
**Since:** 8.2.0
|
|
391
|
+
|
|
392
|
+
--------------------
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
### addListener('hingeAngleChange', ...)
|
|
396
|
+
|
|
397
|
+
```typescript
|
|
398
|
+
addListener(eventName: 'hingeAngleChange', listenerFunc: (event: HingeAngleResult) => void) => Promise<PluginListenerHandle>
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
Listen for hinge angle changes.
|
|
402
|
+
|
|
403
|
+
On Android the hinge sensor runs only while at least one listener is registered.
|
|
404
|
+
Never fires on web or iOS.
|
|
405
|
+
|
|
406
|
+
| Param | Type | Description |
|
|
407
|
+
| ------------------ | --------------------------------------------------------------------------------- | ------------------------------------------- |
|
|
408
|
+
| **`eventName`** | <code>'hingeAngleChange'</code> | The event name. Must be 'hingeAngleChange'. |
|
|
409
|
+
| **`listenerFunc`** | <code>(event: <a href="#hingeangleresult">HingeAngleResult</a>) => void</code> | Callback invoked with the angle in degrees. |
|
|
410
|
+
|
|
411
|
+
**Returns:** <code>Promise<<a href="#pluginlistenerhandle">PluginListenerHandle</a>></code>
|
|
412
|
+
|
|
413
|
+
**Since:** 8.2.0
|
|
414
|
+
|
|
415
|
+
--------------------
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
### addListener('sizeClassChange', ...)
|
|
419
|
+
|
|
420
|
+
```typescript
|
|
421
|
+
addListener(eventName: 'sizeClassChange', listenerFunc: (sizeClass: SizeClass) => void) => Promise<PluginListenerHandle>
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
Listen for size class changes, such as unfolding, rotating, or resizing.
|
|
425
|
+
|
|
426
|
+
| Param | Type | Description |
|
|
427
|
+
| ------------------ | ----------------------------------------------------------------------- | ------------------------------------------ |
|
|
428
|
+
| **`eventName`** | <code>'sizeClassChange'</code> | The event name. Must be 'sizeClassChange'. |
|
|
429
|
+
| **`listenerFunc`** | <code>(sizeClass: <a href="#sizeclass">SizeClass</a>) => void</code> | Callback invoked with the new size class. |
|
|
430
|
+
|
|
431
|
+
**Returns:** <code>Promise<<a href="#pluginlistenerhandle">PluginListenerHandle</a>></code>
|
|
432
|
+
|
|
433
|
+
**Since:** 8.2.0
|
|
298
434
|
|
|
299
435
|
--------------------
|
|
300
436
|
|
|
@@ -373,6 +509,71 @@ Result returned by the isOrientationLocked() method.
|
|
|
373
509
|
| **`uiOrientation`** | <code><a href="#orientationtype">OrientationType</a></code> | The current UI orientation reported by the system. | 1.0.0 |
|
|
374
510
|
|
|
375
511
|
|
|
512
|
+
#### DeviceFoldableResult
|
|
513
|
+
|
|
514
|
+
Whether this device can fold.
|
|
515
|
+
|
|
516
|
+
| Prop | Type | Description | Since |
|
|
517
|
+
| ---------------------- | -------------------- | --------------------------------------------- | ----- |
|
|
518
|
+
| **`foldable`** | <code>boolean</code> | Whether the device has a fold. | 8.2.0 |
|
|
519
|
+
| **`supportsTabletop`** | <code>boolean</code> | Whether it can stand half-open like a laptop. | 8.2.0 |
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
#### FoldState
|
|
523
|
+
|
|
524
|
+
Current fold of the window.
|
|
525
|
+
|
|
526
|
+
Phones that do not fold, iOS, and web resolve to a flat state.
|
|
527
|
+
|
|
528
|
+
| Prop | Type | Description | Since |
|
|
529
|
+
| ---------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------ | ----- |
|
|
530
|
+
| **`state`** | <code><a href="#foldstatevalue">FoldStateValue</a></code> | Posture of the fold. | 8.2.0 |
|
|
531
|
+
| **`isSeparating`** | <code>boolean</code> | Whether the fold splits the web view into two areas. | 8.2.0 |
|
|
532
|
+
| **`posture`** | <code><a href="#foldposture">FoldPosture</a></code> | How the device is held. | 8.2.0 |
|
|
533
|
+
| **`hingeOrientation`** | <code><a href="#hingeorientation">HingeOrientation</a></code> | Direction of the hinge. Omitted when there is no fold. | 8.2.0 |
|
|
534
|
+
| **`hingeBounds`** | <code><a href="#foldbounds">FoldBounds</a></code> | Position of the fold in CSS pixels. Omitted when there is no fold. | 8.2.0 |
|
|
535
|
+
| **`occludedBounds`** | <code><a href="#foldbounds">FoldBounds</a></code> | Area the hinge covers. Present when the hinge has a physical gap. | 8.2.0 |
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
#### FoldBounds
|
|
539
|
+
|
|
540
|
+
A rectangle in CSS pixels, relative to the web view.
|
|
541
|
+
|
|
542
|
+
| Prop | Type |
|
|
543
|
+
| ------------ | ------------------- |
|
|
544
|
+
| **`x`** | <code>number</code> |
|
|
545
|
+
| **`y`** | <code>number</code> |
|
|
546
|
+
| **`width`** | <code>number</code> |
|
|
547
|
+
| **`height`** | <code>number</code> |
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
#### HingeAngleResult
|
|
551
|
+
|
|
552
|
+
Angle between the two halves of a foldable, in degrees.
|
|
553
|
+
|
|
554
|
+
`0` is closed and `180` is flat. `angle` is `null` when the device has no hinge sensor.
|
|
555
|
+
|
|
556
|
+
| Prop | Type | Description | Since |
|
|
557
|
+
| ----------- | --------------------------- | -------------------------------------------------------------- | ----- |
|
|
558
|
+
| **`angle`** | <code>number \| null</code> | Hinge angle in degrees, or `null` when no sensor is available. | 8.2.0 |
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
#### SizeClass
|
|
562
|
+
|
|
563
|
+
Window size classes.
|
|
564
|
+
|
|
565
|
+
`horizontal` and `vertical` follow Apple's compact/regular split.
|
|
566
|
+
On Android and web, `regular` starts at 600 CSS pixels wide and 480 tall.
|
|
567
|
+
`widthClass` and `heightClass` follow Material window size classes.
|
|
568
|
+
|
|
569
|
+
| Prop | Type | Description | Since |
|
|
570
|
+
| ----------------- | --------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | ----- |
|
|
571
|
+
| **`horizontal`** | <code>'compact' \| 'regular'</code> | Width size class. `compact` on a phone, `regular` on the inner display of a foldable, a tablet, or a window at least 600 CSS pixels wide. | 8.2.0 |
|
|
572
|
+
| **`vertical`** | <code>'compact' \| 'regular'</code> | Height size class. `regular` starts at 480 CSS pixels. | 8.2.0 |
|
|
573
|
+
| **`widthClass`** | <code>'compact' \| 'medium' \| 'expanded' \| 'large' \| 'extraLarge'</code> | Material window width class. | 8.2.0 |
|
|
574
|
+
| **`heightClass`** | <code>'compact' \| 'medium' \| 'expanded'</code> | Material window height class. | 8.2.0 |
|
|
575
|
+
|
|
576
|
+
|
|
376
577
|
#### PluginListenerHandle
|
|
377
578
|
|
|
378
579
|
| Prop | Type |
|
|
@@ -396,4 +597,29 @@ Orientation lock type that can be used to lock the device orientation.
|
|
|
396
597
|
|
|
397
598
|
<code>'any' | 'natural' | 'landscape' | 'portrait' | 'portrait-primary' | 'portrait-secondary' | 'landscape-primary' | 'landscape-secondary'</code>
|
|
398
599
|
|
|
600
|
+
|
|
601
|
+
#### FoldStateValue
|
|
602
|
+
|
|
603
|
+
How open a foldable is.
|
|
604
|
+
|
|
605
|
+
`closed` is reserved. A device shut onto its cover display reports `flat`.
|
|
606
|
+
|
|
607
|
+
<code>'flat' | 'half-opened'</code>
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
#### FoldPosture
|
|
611
|
+
|
|
612
|
+
How a half-open foldable is held.
|
|
613
|
+
|
|
614
|
+
`tabletop` is a horizontal hinge, like a laptop. `book` is a vertical hinge.
|
|
615
|
+
|
|
616
|
+
<code>'flat' | 'tabletop' | 'book'</code>
|
|
617
|
+
|
|
618
|
+
|
|
619
|
+
#### HingeOrientation
|
|
620
|
+
|
|
621
|
+
Direction of the hinge relative to the window.
|
|
622
|
+
|
|
623
|
+
<code>'horizontal' | 'vertical'</code>
|
|
624
|
+
|
|
399
625
|
</docgen-api>
|
package/android/build.gradle
CHANGED
|
@@ -3,6 +3,7 @@ ext {
|
|
|
3
3
|
androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.1'
|
|
4
4
|
androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.3.0'
|
|
5
5
|
androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.7.0'
|
|
6
|
+
androidxWindowVersion = project.hasProperty('androidxWindowVersion') ? rootProject.ext.androidxWindowVersion : '1.5.1'
|
|
6
7
|
}
|
|
7
8
|
|
|
8
9
|
buildscript {
|
|
@@ -52,6 +53,7 @@ dependencies {
|
|
|
52
53
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
|
53
54
|
implementation project(':capacitor-android')
|
|
54
55
|
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
|
|
56
|
+
implementation "androidx.window:window-java:$androidxWindowVersion"
|
|
55
57
|
testImplementation "junit:junit:$junitVersion"
|
|
56
58
|
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
|
|
57
59
|
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
|
|
@@ -7,23 +7,60 @@ import android.hardware.Sensor;
|
|
|
7
7
|
import android.hardware.SensorEvent;
|
|
8
8
|
import android.hardware.SensorEventListener;
|
|
9
9
|
import android.hardware.SensorManager;
|
|
10
|
+
import android.os.Build;
|
|
11
|
+
import android.os.Handler;
|
|
12
|
+
import android.os.Looper;
|
|
10
13
|
import android.view.Surface;
|
|
11
14
|
import com.getcapacitor.JSObject;
|
|
12
15
|
import com.getcapacitor.Plugin;
|
|
13
16
|
import com.getcapacitor.PluginCall;
|
|
14
17
|
import com.getcapacitor.PluginMethod;
|
|
15
18
|
import com.getcapacitor.annotation.CapacitorPlugin;
|
|
19
|
+
import java.util.ArrayList;
|
|
20
|
+
import java.util.List;
|
|
21
|
+
import java.util.concurrent.Executor;
|
|
22
|
+
import org.json.JSONObject;
|
|
16
23
|
|
|
17
24
|
@CapacitorPlugin(name = "CapacitorScreenOrientation")
|
|
18
25
|
public class CapacitorScreenOrientationPlugin extends Plugin implements SensorEventListener {
|
|
19
26
|
|
|
20
|
-
private final String pluginVersion = "8.
|
|
27
|
+
private final String pluginVersion = "8.2.0";
|
|
21
28
|
private int currentOrientation;
|
|
22
29
|
private SensorManager sensorManager;
|
|
23
30
|
private Sensor accelerometer;
|
|
24
31
|
private boolean isTrackingMotion = false;
|
|
25
32
|
private String currentPhysicalOrientation = "portrait-primary";
|
|
26
33
|
private String lastNotifiedOrientation = null;
|
|
34
|
+
private FoldLayout foldLayout;
|
|
35
|
+
private Sensor hingeSensor;
|
|
36
|
+
private boolean hingeRegistered = false;
|
|
37
|
+
private Float lastHingeAngle = null;
|
|
38
|
+
private String lastSizeClassKey = null;
|
|
39
|
+
private final List<PluginCall> pendingHingeCalls = new ArrayList<>();
|
|
40
|
+
private final Handler mainHandler = new Handler(Looper.getMainLooper());
|
|
41
|
+
private final SensorEventListener hingeListener = new SensorEventListener() {
|
|
42
|
+
@Override
|
|
43
|
+
public void onSensorChanged(SensorEvent event) {
|
|
44
|
+
if (event.values.length == 0) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
float angle = Math.max(0f, Math.min(360f, event.values[0]));
|
|
48
|
+
boolean unchanged = lastHingeAngle != null && Math.abs(lastHingeAngle - angle) < 0.5f;
|
|
49
|
+
lastHingeAngle = angle;
|
|
50
|
+
if (unchanged && pendingHingeCalls.isEmpty()) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
JSObject payload = hingePayload(angle);
|
|
54
|
+
notifyListeners("hingeAngleChange", payload);
|
|
55
|
+
resolvePendingHinge(payload);
|
|
56
|
+
if (!hasListeners("hingeAngleChange")) {
|
|
57
|
+
stopHingeTracking();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@Override
|
|
62
|
+
public void onAccuracyChanged(Sensor sensor, int accuracy) {}
|
|
63
|
+
};
|
|
27
64
|
|
|
28
65
|
@Override
|
|
29
66
|
public void load() {
|
|
@@ -34,12 +71,26 @@ public class CapacitorScreenOrientationPlugin extends Plugin implements SensorEv
|
|
|
34
71
|
sensorManager = (SensorManager) getActivity().getSystemService(Context.SENSOR_SERVICE);
|
|
35
72
|
if (sensorManager != null) {
|
|
36
73
|
accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
|
|
74
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
75
|
+
hingeSensor = sensorManager.getDefaultSensor(Sensor.TYPE_HINGE_ANGLE);
|
|
76
|
+
}
|
|
37
77
|
}
|
|
78
|
+
|
|
79
|
+
Executor executor = mainHandler::post;
|
|
80
|
+
foldLayout = new FoldLayout(getActivity(), bridge.getWebView(), executor, (state) -> {
|
|
81
|
+
notifyListeners("foldStateChange", state);
|
|
82
|
+
notifySizeClassIfChanged();
|
|
83
|
+
});
|
|
84
|
+
notifySizeClassIfChanged();
|
|
38
85
|
}
|
|
39
86
|
|
|
40
87
|
@Override
|
|
41
88
|
protected void handleOnDestroy() {
|
|
42
89
|
stopMotionTracking();
|
|
90
|
+
stopHingeTracking();
|
|
91
|
+
if (foldLayout != null) {
|
|
92
|
+
foldLayout.stop();
|
|
93
|
+
}
|
|
43
94
|
super.handleOnDestroy();
|
|
44
95
|
}
|
|
45
96
|
|
|
@@ -47,6 +98,8 @@ public class CapacitorScreenOrientationPlugin extends Plugin implements SensorEv
|
|
|
47
98
|
protected void handleOnConfigurationChanged(Configuration newConfig) {
|
|
48
99
|
super.handleOnConfigurationChanged(newConfig);
|
|
49
100
|
|
|
101
|
+
notifySizeClassIfChanged();
|
|
102
|
+
|
|
50
103
|
// Skip system orientation changes when motion tracking is active
|
|
51
104
|
// to avoid duplicate events
|
|
52
105
|
if (isTrackingMotion) {
|
|
@@ -59,6 +112,79 @@ public class CapacitorScreenOrientationPlugin extends Plugin implements SensorEv
|
|
|
59
112
|
}
|
|
60
113
|
}
|
|
61
114
|
|
|
115
|
+
@PluginMethod(returnType = PluginMethod.RETURN_NONE)
|
|
116
|
+
@Override
|
|
117
|
+
public void addListener(PluginCall call) {
|
|
118
|
+
super.addListener(call);
|
|
119
|
+
String eventName = call.getString("eventName");
|
|
120
|
+
if ("hingeAngleChange".equals(eventName)) {
|
|
121
|
+
mainHandler.post(this::startHingeTracking);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
@PluginMethod(returnType = PluginMethod.RETURN_NONE)
|
|
126
|
+
@Override
|
|
127
|
+
public void removeListener(PluginCall call) {
|
|
128
|
+
super.removeListener(call);
|
|
129
|
+
if (!hasListeners("hingeAngleChange")) {
|
|
130
|
+
mainHandler.post(this::stopHingeTracking);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
@PluginMethod
|
|
135
|
+
@Override
|
|
136
|
+
public void removeAllListeners(PluginCall call) {
|
|
137
|
+
super.removeAllListeners(call);
|
|
138
|
+
mainHandler.post(this::stopHingeTracking);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
@PluginMethod
|
|
142
|
+
public void isDeviceFoldable(PluginCall call) {
|
|
143
|
+
JSObject result = new JSObject();
|
|
144
|
+
result.put("foldable", foldLayout != null && foldLayout.isFoldable());
|
|
145
|
+
result.put("supportsTabletop", foldLayout != null && foldLayout.supportsTabletop());
|
|
146
|
+
call.resolve(result);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
@PluginMethod
|
|
150
|
+
public void getFoldState(PluginCall call) {
|
|
151
|
+
call.resolve(foldLayout == null ? flatFold() : foldLayout.currentState());
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
@PluginMethod
|
|
155
|
+
public void getHingeAngle(PluginCall call) {
|
|
156
|
+
mainHandler.post(() -> {
|
|
157
|
+
if (hingeRegistered && lastHingeAngle != null) {
|
|
158
|
+
call.resolve(hingePayload(lastHingeAngle));
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (hingeSensor == null) {
|
|
162
|
+
call.resolve(hingePayload(null));
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
pendingHingeCalls.add(call);
|
|
166
|
+
startHingeTracking();
|
|
167
|
+
mainHandler.postDelayed(
|
|
168
|
+
() -> {
|
|
169
|
+
if (!pendingHingeCalls.contains(call)) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
pendingHingeCalls.remove(call);
|
|
173
|
+
call.resolve(hingePayload(null));
|
|
174
|
+
if (!hasListeners("hingeAngleChange") && pendingHingeCalls.isEmpty()) {
|
|
175
|
+
stopHingeTracking();
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
800
|
|
179
|
+
);
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
@PluginMethod
|
|
184
|
+
public void getSizeClass(PluginCall call) {
|
|
185
|
+
call.resolve(currentSizeClass());
|
|
186
|
+
}
|
|
187
|
+
|
|
62
188
|
@PluginMethod
|
|
63
189
|
public void orientation(final PluginCall call) {
|
|
64
190
|
try {
|
|
@@ -198,6 +324,68 @@ public class CapacitorScreenOrientationPlugin extends Plugin implements SensorEv
|
|
|
198
324
|
}
|
|
199
325
|
}
|
|
200
326
|
|
|
327
|
+
private JSObject currentSizeClass() {
|
|
328
|
+
if (foldLayout != null) {
|
|
329
|
+
return foldLayout.sizeClass();
|
|
330
|
+
}
|
|
331
|
+
return FoldLayout.sizeClassOf(0f, 0f);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
private void notifySizeClassIfChanged() {
|
|
335
|
+
JSObject sizeClass = currentSizeClass();
|
|
336
|
+
String key = sizeClass.toString();
|
|
337
|
+
if (key.equals(lastSizeClassKey)) {
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
lastSizeClassKey = key;
|
|
341
|
+
notifyListeners("sizeClassChange", sizeClass);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
private void startHingeTracking() {
|
|
345
|
+
if (hingeRegistered || hingeSensor == null || sensorManager == null) {
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
hingeRegistered = true;
|
|
349
|
+
sensorManager.registerListener(hingeListener, hingeSensor, SensorManager.SENSOR_DELAY_NORMAL, mainHandler);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
private void stopHingeTracking() {
|
|
353
|
+
if (!hingeRegistered || sensorManager == null) {
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
hingeRegistered = false;
|
|
357
|
+
sensorManager.unregisterListener(hingeListener);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
private void resolvePendingHinge(JSObject payload) {
|
|
361
|
+
if (pendingHingeCalls.isEmpty()) {
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
List<PluginCall> pending = new ArrayList<>(pendingHingeCalls);
|
|
365
|
+
pendingHingeCalls.clear();
|
|
366
|
+
for (PluginCall call : pending) {
|
|
367
|
+
call.resolve(payload);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
private static JSObject hingePayload(Float angle) {
|
|
372
|
+
JSObject result = new JSObject();
|
|
373
|
+
if (angle == null) {
|
|
374
|
+
result.put("angle", JSONObject.NULL);
|
|
375
|
+
} else {
|
|
376
|
+
result.put("angle", angle.doubleValue());
|
|
377
|
+
}
|
|
378
|
+
return result;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
private static JSObject flatFold() {
|
|
382
|
+
JSObject state = new JSObject();
|
|
383
|
+
state.put("state", "flat");
|
|
384
|
+
state.put("isSeparating", false);
|
|
385
|
+
state.put("posture", "flat");
|
|
386
|
+
return state;
|
|
387
|
+
}
|
|
388
|
+
|
|
201
389
|
// Motion tracking methods
|
|
202
390
|
private void startMotionTracking() {
|
|
203
391
|
if (isTrackingMotion || accelerometer == null) {
|