@capgo/capacitor-updater 8.47.10 → 8.47.11
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 +33 -15
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +65 -4
- package/android/src/main/java/ee/forgr/capacitor_updater/ShakeMenu.java +24 -6
- package/android/src/main/java/ee/forgr/capacitor_updater/ThreeFingerPinchDetector.java +169 -0
- package/dist/docs.json +67 -6
- package/dist/esm/definitions.d.ts +47 -10
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.js +1 -1
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +1 -1
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +1 -1
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CapacitorUpdaterPlugin/CapacitorUpdaterPlugin.swift +49 -2
- package/ios/Sources/CapacitorUpdaterPlugin/ShakeMenu.swift +105 -22
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -323,8 +323,9 @@ CapacitorUpdater can be configured with these options:
|
|
|
323
323
|
| **`keepUrlPathAfterReload`** | <code>boolean</code> | Configure the plugin to keep the URL path after a reload. WARNING: When a reload is triggered, 'window.history' will be cleared. | <code>false</code> | 6.8.0 |
|
|
324
324
|
| **`disableJSLogging`** | <code>boolean</code> | Disable the JavaScript logging of the plugin. if true, the plugin will not log to the JavaScript console. only the native log will be done | <code>false</code> | 7.3.0 |
|
|
325
325
|
| **`osLogging`** | <code>boolean</code> | Enable OS-level logging. When enabled, logs are written to the system log which can be inspected in production builds. - **iOS**: Uses os_log instead of Swift.print, logs accessible via Console.app or Instruments - **Android**: Logs to Logcat (android.util.Log) When set to false, system logging is disabled on both platforms (only JavaScript console logging will occur if enabled). This is useful for debugging production apps (App Store/TestFlight builds on iOS, or production APKs on Android). | <code>true</code> | 8.42.0 |
|
|
326
|
-
| **`shakeMenu`** | <code>boolean</code> | Enable the
|
|
327
|
-
| **`
|
|
326
|
+
| **`shakeMenu`** | <code>boolean</code> | Enable the native preview menu gesture while a preview session is active. Outside preview sessions this preview menu is ignored, unless {@link PluginsConfig.CapacitorUpdater.allowShakeChannelSelector} is enabled. | <code>false</code> | 7.5.0 |
|
|
327
|
+
| **`shakeMenuGesture`** | <code><a href="#shakemenugesture">ShakeMenuGesture</a></code> | Choose which native gesture opens the preview/channel menu. This applies to both {@link PluginsConfig.CapacitorUpdater.shakeMenu} and {@link PluginsConfig.CapacitorUpdater.allowShakeChannelSelector}. Only available for Android and iOS. | <code>'shake'</code> | 8.48.0 |
|
|
328
|
+
| **`allowShakeChannelSelector`** | <code>boolean</code> | Enable the native menu gesture to show a channel selector menu for switching between update channels. If {@link PluginsConfig.CapacitorUpdater.shakeMenu} is also enabled while a preview session is active, the shake menu includes both preview actions and channel switching. The native gesture can be changed with {@link PluginsConfig.CapacitorUpdater.shakeMenuGesture}. Only available for Android and iOS. | <code>false</code> | 8.43.0 |
|
|
328
329
|
|
|
329
330
|
### Examples
|
|
330
331
|
|
|
@@ -370,6 +371,7 @@ In `capacitor.config.json`:
|
|
|
370
371
|
"disableJSLogging": undefined,
|
|
371
372
|
"osLogging": undefined,
|
|
372
373
|
"shakeMenu": undefined,
|
|
374
|
+
"shakeMenuGesture": undefined,
|
|
373
375
|
"allowShakeChannelSelector": undefined
|
|
374
376
|
}
|
|
375
377
|
}
|
|
@@ -422,6 +424,7 @@ const config: CapacitorConfig = {
|
|
|
422
424
|
disableJSLogging: undefined,
|
|
423
425
|
osLogging: undefined,
|
|
424
426
|
shakeMenu: undefined,
|
|
427
|
+
shakeMenuGesture: undefined,
|
|
425
428
|
allowShakeChannelSelector: undefined,
|
|
426
429
|
},
|
|
427
430
|
},
|
|
@@ -1826,9 +1829,9 @@ Use this to:
|
|
|
1826
1829
|
setShakeMenu(options: SetShakeMenuOptions) => Promise<void>
|
|
1827
1830
|
```
|
|
1828
1831
|
|
|
1829
|
-
Enable or disable the
|
|
1832
|
+
Enable or disable the native preview menu gesture.
|
|
1830
1833
|
|
|
1831
|
-
During preview sessions, users can
|
|
1834
|
+
During preview sessions, users can use the configured native gesture to:
|
|
1832
1835
|
- Reload the current preview
|
|
1833
1836
|
- Leave the test app and return to the fallback bundle
|
|
1834
1837
|
- Switch update channel, when {@link PluginsConfig.CapacitorUpdater.allowShakeChannelSelector} is also enabled
|
|
@@ -1838,7 +1841,9 @@ shown outside preview sessions when {@link PluginsConfig.CapacitorUpdater.allowS
|
|
|
1838
1841
|
|
|
1839
1842
|
**Important:** Disable this in production builds or only enable for internal testers.
|
|
1840
1843
|
|
|
1841
|
-
|
|
1844
|
+
This can also be configured via {@link PluginsConfig.CapacitorUpdater.shakeMenu}.
|
|
1845
|
+
The native gesture can be configured via {@link PluginsConfig.CapacitorUpdater.shakeMenuGesture}
|
|
1846
|
+
or `options.gesture`.
|
|
1842
1847
|
|
|
1843
1848
|
| Param | Type |
|
|
1844
1849
|
| ------------- | ------------------------------------------------------------------- |
|
|
@@ -1855,7 +1860,7 @@ Can also be configured via {@link PluginsConfig.CapacitorUpdater.shakeMenu}.
|
|
|
1855
1860
|
isShakeMenuEnabled() => Promise<ShakeMenuEnabled>
|
|
1856
1861
|
```
|
|
1857
1862
|
|
|
1858
|
-
Check if the
|
|
1863
|
+
Check if the native preview menu gesture is currently enabled.
|
|
1859
1864
|
|
|
1860
1865
|
Returns the current state of the shake menu feature that can be toggled via
|
|
1861
1866
|
{@link setShakeMenu} or configured via {@link PluginsConfig.CapacitorUpdater.shakeMenu}.
|
|
@@ -1878,13 +1883,15 @@ Use this to:
|
|
|
1878
1883
|
setShakeChannelSelector(options: SetShakeChannelSelectorOptions) => Promise<void>
|
|
1879
1884
|
```
|
|
1880
1885
|
|
|
1881
|
-
Enable or disable the
|
|
1886
|
+
Enable or disable the channel selector menu gesture at runtime.
|
|
1882
1887
|
|
|
1883
|
-
When enabled,
|
|
1888
|
+
When enabled, the configured native gesture can show a channel selector, including outside preview sessions.
|
|
1884
1889
|
If {@link setShakeMenu} is also enabled while a preview session is active, the shake menu includes
|
|
1885
1890
|
both preview actions and channel switching.
|
|
1886
1891
|
|
|
1887
|
-
|
|
1892
|
+
This can also be configured via {@link PluginsConfig.CapacitorUpdater.allowShakeChannelSelector}.
|
|
1893
|
+
The native gesture can be configured via {@link PluginsConfig.CapacitorUpdater.shakeMenuGesture}
|
|
1894
|
+
or {@link setShakeMenu}.
|
|
1888
1895
|
|
|
1889
1896
|
| Param | Type |
|
|
1890
1897
|
| ------------- | ----------------------------------------------------------------------------------------- |
|
|
@@ -2547,16 +2554,18 @@ State information for flexible update progress (Android only).
|
|
|
2547
2554
|
|
|
2548
2555
|
##### SetShakeMenuOptions
|
|
2549
2556
|
|
|
2550
|
-
| Prop | Type
|
|
2551
|
-
| ------------- | -------------------- |
|
|
2552
|
-
| **`enabled`** | <code>boolean</code>
|
|
2557
|
+
| Prop | Type | Description | Default |
|
|
2558
|
+
| ------------- | ------------------------------------------------------------- | ----------------------------------------------------- | -------------------- |
|
|
2559
|
+
| **`enabled`** | <code>boolean</code> | | |
|
|
2560
|
+
| **`gesture`** | <code><a href="#shakemenugesture">ShakeMenuGesture</a></code> | Native gesture used to open the preview/channel menu. | <code>'shake'</code> |
|
|
2553
2561
|
|
|
2554
2562
|
|
|
2555
2563
|
##### ShakeMenuEnabled
|
|
2556
2564
|
|
|
2557
|
-
| Prop | Type
|
|
2558
|
-
| ------------- |
|
|
2559
|
-
| **`enabled`** | <code>boolean</code>
|
|
2565
|
+
| Prop | Type | Description | Since |
|
|
2566
|
+
| ------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------ |
|
|
2567
|
+
| **`enabled`** | <code>boolean</code> | | |
|
|
2568
|
+
| **`gesture`** | <code><a href="#shakemenugesture">ShakeMenuGesture</a></code> | The currently configured native gesture used to open the preview/channel menu. Undefined means consumers should treat the gesture as the default `shake` behavior. | 8.48.0 |
|
|
2560
2569
|
|
|
2561
2570
|
|
|
2562
2571
|
##### SetShakeChannelSelectorOptions
|
|
@@ -2669,6 +2678,15 @@ Payload emitted by {@link CapacitorUpdaterPlugin.addListener} with `breakingAvai
|
|
|
2669
2678
|
<code><a href="#majoravailableevent">MajorAvailableEvent</a></code>
|
|
2670
2679
|
|
|
2671
2680
|
|
|
2681
|
+
##### ShakeMenuGesture
|
|
2682
|
+
|
|
2683
|
+
Native gesture options that open the shake menu.
|
|
2684
|
+
|
|
2685
|
+
Supported values are `shake` and `threeFingerPinch`.
|
|
2686
|
+
|
|
2687
|
+
<code>'shake' | 'threeFingerPinch'</code>
|
|
2688
|
+
|
|
2689
|
+
|
|
2672
2690
|
#### Enums
|
|
2673
2691
|
|
|
2674
2692
|
|
|
@@ -79,6 +79,9 @@ import org.json.JSONObject;
|
|
|
79
79
|
@CapacitorPlugin(name = "CapacitorUpdater")
|
|
80
80
|
public class CapacitorUpdaterPlugin extends Plugin {
|
|
81
81
|
|
|
82
|
+
static final String SHAKE_MENU_GESTURE_SHAKE = "shake";
|
|
83
|
+
static final String SHAKE_MENU_GESTURE_THREE_FINGER_PINCH = "threeFingerPinch";
|
|
84
|
+
|
|
82
85
|
private static final String AUTO_UPDATE_MODE_OFF = "off";
|
|
83
86
|
private static final String AUTO_UPDATE_MODE_BACKGROUND = "atBackground";
|
|
84
87
|
private static final String AUTO_UPDATE_MODE_INSTALL = "atInstall";
|
|
@@ -160,6 +163,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
160
163
|
private volatile boolean onLaunchDirectUpdateUsed = false;
|
|
161
164
|
Boolean shakeMenuEnabled = false;
|
|
162
165
|
Boolean shakeChannelSelectorEnabled = false;
|
|
166
|
+
String shakeMenuGesture = SHAKE_MENU_GESTURE_SHAKE;
|
|
163
167
|
volatile Boolean previewSessionEnabled = false;
|
|
164
168
|
private Boolean previewSessionAlertPending = false;
|
|
165
169
|
private volatile Boolean isLeavingPreviewForIncomingLink = false;
|
|
@@ -526,6 +530,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
526
530
|
this.implementation.timeout = this.getConfig().getInt("responseTimeout", 20) * 1000;
|
|
527
531
|
this.shakeMenuEnabled = this.getConfig().getBoolean("shakeMenu", false);
|
|
528
532
|
this.shakeChannelSelectorEnabled = this.getConfig().getBoolean("allowShakeChannelSelector", false);
|
|
533
|
+
this.shakeMenuGesture = normalizedShakeMenuGesture(this.getConfig().getString("shakeMenuGesture", SHAKE_MENU_GESTURE_SHAKE));
|
|
529
534
|
this.previewSessionEnabled = Boolean.TRUE.equals(this.allowPreview) && this.prefs.getBoolean(PREVIEW_SESSION_PREF_KEY, false);
|
|
530
535
|
if (!Boolean.TRUE.equals(this.allowPreview) && this.prefs.getBoolean(PREVIEW_SESSION_PREF_KEY, false)) {
|
|
531
536
|
this.clearPreviewSessionBecauseDisabled();
|
|
@@ -1504,6 +1509,28 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1504
1509
|
}
|
|
1505
1510
|
}
|
|
1506
1511
|
|
|
1512
|
+
static String normalizedShakeMenuGesture(final String value) {
|
|
1513
|
+
if (value == null || value.trim().isEmpty()) {
|
|
1514
|
+
return SHAKE_MENU_GESTURE_SHAKE;
|
|
1515
|
+
}
|
|
1516
|
+
final String normalized = value.trim();
|
|
1517
|
+
if (SHAKE_MENU_GESTURE_THREE_FINGER_PINCH.equals(normalized)) {
|
|
1518
|
+
return SHAKE_MENU_GESTURE_THREE_FINGER_PINCH;
|
|
1519
|
+
}
|
|
1520
|
+
return SHAKE_MENU_GESTURE_SHAKE;
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
static boolean isSupportedShakeMenuGesture(final String value) {
|
|
1524
|
+
if (value == null) {
|
|
1525
|
+
return true;
|
|
1526
|
+
}
|
|
1527
|
+
final String normalized = value.trim();
|
|
1528
|
+
if (normalized.isEmpty()) {
|
|
1529
|
+
return false;
|
|
1530
|
+
}
|
|
1531
|
+
return SHAKE_MENU_GESTURE_SHAKE.equals(normalized) || SHAKE_MENU_GESTURE_THREE_FINGER_PINCH.equals(normalized);
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1507
1534
|
static String autoUpdateModeForLegacyDirectUpdateMode(final String directUpdateMode) {
|
|
1508
1535
|
switch (directUpdateMode) {
|
|
1509
1536
|
case AUTO_UPDATE_MODE_INSTALL:
|
|
@@ -2736,6 +2763,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
2736
2763
|
this.hidePreviewTransitionLoader("preview-session-disabled");
|
|
2737
2764
|
this.shakeMenuEnabled = this.getConfig().getBoolean("shakeMenu", false);
|
|
2738
2765
|
this.shakeChannelSelectorEnabled = this.getConfig().getBoolean("allowShakeChannelSelector", false);
|
|
2766
|
+
this.shakeMenuGesture = normalizedShakeMenuGesture(this.getConfig().getString("shakeMenuGesture", SHAKE_MENU_GESTURE_SHAKE));
|
|
2739
2767
|
this.syncShakeMenuLifecycle();
|
|
2740
2768
|
this.clearPreviewSessionPreferences();
|
|
2741
2769
|
}
|
|
@@ -2959,6 +2987,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
2959
2987
|
this.implementation.previewSession = false;
|
|
2960
2988
|
this.shakeMenuEnabled = this.getConfig().getBoolean("shakeMenu", false);
|
|
2961
2989
|
this.shakeChannelSelectorEnabled = this.getConfig().getBoolean("allowShakeChannelSelector", false);
|
|
2990
|
+
this.shakeMenuGesture = normalizedShakeMenuGesture(this.getConfig().getString("shakeMenuGesture", SHAKE_MENU_GESTURE_SHAKE));
|
|
2962
2991
|
this.syncShakeMenuLifecycle();
|
|
2963
2992
|
this.restorePreviewPreviousAppId();
|
|
2964
2993
|
this.restorePreviewPreviousDefaultChannel();
|
|
@@ -2982,14 +3011,26 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
2982
3011
|
private void ensureShakeMenuStarted() {
|
|
2983
3012
|
if (getActivity() instanceof com.getcapacitor.BridgeActivity && shakeMenu == null) {
|
|
2984
3013
|
try {
|
|
2985
|
-
shakeMenu = new ShakeMenu(this, (com.getcapacitor.BridgeActivity) getActivity(), logger);
|
|
2986
|
-
logger.info("Shake menu initialized");
|
|
3014
|
+
shakeMenu = new ShakeMenu(this, (com.getcapacitor.BridgeActivity) getActivity(), logger, this.shakeMenuGesture);
|
|
3015
|
+
logger.info("Shake menu initialized with " + this.shakeMenuGesture + " gesture");
|
|
2987
3016
|
} catch (Exception e) {
|
|
2988
3017
|
logger.error("Failed to initialize shake menu: " + e.getMessage());
|
|
2989
3018
|
}
|
|
2990
3019
|
}
|
|
2991
3020
|
}
|
|
2992
3021
|
|
|
3022
|
+
private void restartShakeMenuListener() {
|
|
3023
|
+
if (shakeMenu != null) {
|
|
3024
|
+
try {
|
|
3025
|
+
shakeMenu.stop();
|
|
3026
|
+
} catch (Exception e) {
|
|
3027
|
+
logger.error("Failed to restart shake menu listener: " + e.getMessage());
|
|
3028
|
+
}
|
|
3029
|
+
shakeMenu = null;
|
|
3030
|
+
}
|
|
3031
|
+
this.syncShakeMenuLifecycle();
|
|
3032
|
+
}
|
|
3033
|
+
|
|
2993
3034
|
private void syncShakeMenuLifecycle() {
|
|
2994
3035
|
if (this.shouldListenForShake()) {
|
|
2995
3036
|
this.ensureShakeMenuStarted();
|
|
@@ -4423,10 +4464,29 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
4423
4464
|
return;
|
|
4424
4465
|
}
|
|
4425
4466
|
|
|
4467
|
+
final String gesture = call.getString("gesture", null);
|
|
4468
|
+
final boolean gestureChanged;
|
|
4469
|
+
if (gesture != null) {
|
|
4470
|
+
if (!isSupportedShakeMenuGesture(gesture)) {
|
|
4471
|
+
logger.error("Unsupported shake menu gesture: " + gesture);
|
|
4472
|
+
call.reject("Unsupported shake menu gesture. Use \"shake\" or \"threeFingerPinch\".");
|
|
4473
|
+
return;
|
|
4474
|
+
}
|
|
4475
|
+
final String normalizedGesture = normalizedShakeMenuGesture(gesture);
|
|
4476
|
+
gestureChanged = !normalizedGesture.equals(this.shakeMenuGesture);
|
|
4477
|
+
this.shakeMenuGesture = normalizedGesture;
|
|
4478
|
+
} else {
|
|
4479
|
+
gestureChanged = false;
|
|
4480
|
+
}
|
|
4481
|
+
|
|
4426
4482
|
this.shakeMenuEnabled = enabled;
|
|
4427
|
-
logger.info("Shake menu " + (enabled ? "enabled" : "disabled"));
|
|
4483
|
+
logger.info("Shake menu " + (enabled ? "enabled" : "disabled") + " with " + this.shakeMenuGesture + " gesture");
|
|
4428
4484
|
|
|
4429
|
-
|
|
4485
|
+
if (gestureChanged) {
|
|
4486
|
+
this.restartShakeMenuListener();
|
|
4487
|
+
} else {
|
|
4488
|
+
this.syncShakeMenuLifecycle();
|
|
4489
|
+
}
|
|
4430
4490
|
|
|
4431
4491
|
call.resolve();
|
|
4432
4492
|
}
|
|
@@ -4436,6 +4496,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
4436
4496
|
try {
|
|
4437
4497
|
final JSObject ret = new JSObject();
|
|
4438
4498
|
ret.put("enabled", this.shakeMenuEnabled);
|
|
4499
|
+
ret.put("gesture", this.shakeMenuGesture);
|
|
4439
4500
|
call.resolve(ret);
|
|
4440
4501
|
} catch (final Exception e) {
|
|
4441
4502
|
logger.error("Could not get shake menu status " + e.getMessage());
|
|
@@ -24,7 +24,7 @@ import java.util.List;
|
|
|
24
24
|
import java.util.Map;
|
|
25
25
|
import org.json.JSONArray;
|
|
26
26
|
|
|
27
|
-
public class ShakeMenu implements ShakeDetector.Listener {
|
|
27
|
+
public class ShakeMenu implements ShakeDetector.Listener, ThreeFingerPinchDetector.Listener {
|
|
28
28
|
|
|
29
29
|
private interface PreviewMenuAction {
|
|
30
30
|
boolean run();
|
|
@@ -33,28 +33,46 @@ public class ShakeMenu implements ShakeDetector.Listener {
|
|
|
33
33
|
private CapacitorUpdaterPlugin plugin;
|
|
34
34
|
private BridgeActivity activity;
|
|
35
35
|
private ShakeDetector shakeDetector;
|
|
36
|
+
private ThreeFingerPinchDetector pinchDetector;
|
|
36
37
|
private boolean isShowing = false;
|
|
37
38
|
private Logger logger;
|
|
38
39
|
|
|
39
|
-
public ShakeMenu(CapacitorUpdaterPlugin plugin, BridgeActivity activity, Logger logger) {
|
|
40
|
+
public ShakeMenu(CapacitorUpdaterPlugin plugin, BridgeActivity activity, Logger logger, String gesture) {
|
|
40
41
|
this.plugin = plugin;
|
|
41
42
|
this.activity = activity;
|
|
42
43
|
this.logger = logger;
|
|
43
44
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
if (CapacitorUpdaterPlugin.SHAKE_MENU_GESTURE_THREE_FINGER_PINCH.equals(gesture)) {
|
|
46
|
+
this.pinchDetector = new ThreeFingerPinchDetector(this, logger);
|
|
47
|
+
this.pinchDetector.start(activity);
|
|
48
|
+
} else {
|
|
49
|
+
SensorManager sensorManager = (SensorManager) activity.getSystemService(Activity.SENSOR_SERVICE);
|
|
50
|
+
this.shakeDetector = new ShakeDetector(this);
|
|
51
|
+
this.shakeDetector.start(sensorManager);
|
|
52
|
+
}
|
|
47
53
|
}
|
|
48
54
|
|
|
49
55
|
public void stop() {
|
|
50
56
|
if (shakeDetector != null) {
|
|
51
57
|
shakeDetector.stop();
|
|
52
58
|
}
|
|
59
|
+
if (pinchDetector != null) {
|
|
60
|
+
pinchDetector.stop();
|
|
61
|
+
}
|
|
53
62
|
}
|
|
54
63
|
|
|
55
64
|
@Override
|
|
56
65
|
public void onShakeDetected() {
|
|
57
|
-
|
|
66
|
+
onMenuGestureDetected("Shake");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
@Override
|
|
70
|
+
public void onThreeFingerPinchDetected() {
|
|
71
|
+
onMenuGestureDetected("Three finger pinch");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private void onMenuGestureDetected(String gestureName) {
|
|
75
|
+
logger.info(gestureName + " detected");
|
|
58
76
|
|
|
59
77
|
boolean canShowPreviewMenu = Boolean.TRUE.equals(plugin.shakeMenuEnabled) && plugin.hasActivePreviewSession();
|
|
60
78
|
boolean canShowChannelSelector = Boolean.TRUE.equals(plugin.shakeChannelSelectorEnabled);
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
package ee.forgr.capacitor_updater;
|
|
8
|
+
|
|
9
|
+
import android.view.MotionEvent;
|
|
10
|
+
import android.view.View;
|
|
11
|
+
import com.getcapacitor.Bridge;
|
|
12
|
+
import com.getcapacitor.BridgeActivity;
|
|
13
|
+
import java.lang.reflect.Field;
|
|
14
|
+
|
|
15
|
+
public class ThreeFingerPinchDetector implements View.OnTouchListener {
|
|
16
|
+
|
|
17
|
+
public interface Listener {
|
|
18
|
+
void onThreeFingerPinchDetected();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
private static final int REQUIRED_POINTER_COUNT = 3;
|
|
22
|
+
private static final float MIN_SCALE_DELTA = 0.30f;
|
|
23
|
+
private static final long PINCH_TIMEOUT = 1000;
|
|
24
|
+
|
|
25
|
+
private final Listener listener;
|
|
26
|
+
private final Logger logger;
|
|
27
|
+
private View targetView;
|
|
28
|
+
private View.OnTouchListener previousOnTouchListener;
|
|
29
|
+
private boolean touchListenerInstalled = false;
|
|
30
|
+
private float initialSpan = 0;
|
|
31
|
+
private boolean tracking = false;
|
|
32
|
+
private boolean triggered = false;
|
|
33
|
+
private long lastPinchTime = 0;
|
|
34
|
+
|
|
35
|
+
public ThreeFingerPinchDetector(Listener listener, Logger logger) {
|
|
36
|
+
this.listener = listener;
|
|
37
|
+
this.logger = logger;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public void start(BridgeActivity activity) {
|
|
41
|
+
if (targetView != null) {
|
|
42
|
+
stop();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
View view = null;
|
|
46
|
+
Bridge bridge = activity.getBridge();
|
|
47
|
+
if (bridge != null && bridge.getWebView() != null) {
|
|
48
|
+
view = bridge.getWebView();
|
|
49
|
+
}
|
|
50
|
+
if (view == null && activity.getWindow() != null) {
|
|
51
|
+
view = activity.getWindow().getDecorView().getRootView();
|
|
52
|
+
}
|
|
53
|
+
if (view == null) {
|
|
54
|
+
logger.warn("Three finger pinch detector could not find a target view");
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
this.targetView = view;
|
|
59
|
+
this.previousOnTouchListener = getCurrentOnTouchListener(view);
|
|
60
|
+
if (this.previousOnTouchListener != this) {
|
|
61
|
+
this.targetView.setOnTouchListener(this);
|
|
62
|
+
this.touchListenerInstalled = true;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
public void stop() {
|
|
67
|
+
if (targetView != null) {
|
|
68
|
+
View.OnTouchListener currentOnTouchListener = getCurrentOnTouchListener(targetView);
|
|
69
|
+
if (touchListenerInstalled && (currentOnTouchListener == this || currentOnTouchListener == null)) {
|
|
70
|
+
targetView.setOnTouchListener(previousOnTouchListener);
|
|
71
|
+
}
|
|
72
|
+
targetView = null;
|
|
73
|
+
previousOnTouchListener = null;
|
|
74
|
+
touchListenerInstalled = false;
|
|
75
|
+
}
|
|
76
|
+
reset();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@Override
|
|
80
|
+
public boolean onTouch(View view, MotionEvent event) {
|
|
81
|
+
boolean consumedByPreviousListener = false;
|
|
82
|
+
if (previousOnTouchListener != null && previousOnTouchListener != this) {
|
|
83
|
+
consumedByPreviousListener = previousOnTouchListener.onTouch(view, event);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
int action = event.getActionMasked();
|
|
87
|
+
if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_POINTER_UP) {
|
|
88
|
+
reset();
|
|
89
|
+
return consumedByPreviousListener;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (event.getPointerCount() != REQUIRED_POINTER_COUNT) {
|
|
93
|
+
if (action == MotionEvent.ACTION_POINTER_DOWN) {
|
|
94
|
+
reset();
|
|
95
|
+
}
|
|
96
|
+
return consumedByPreviousListener;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
float span = calculateSpan(event);
|
|
100
|
+
if (span <= 0) {
|
|
101
|
+
return consumedByPreviousListener;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (!tracking || action == MotionEvent.ACTION_POINTER_DOWN) {
|
|
105
|
+
initialSpan = span;
|
|
106
|
+
tracking = true;
|
|
107
|
+
triggered = false;
|
|
108
|
+
return consumedByPreviousListener;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (!triggered && Math.abs(span - initialSpan) / initialSpan >= MIN_SCALE_DELTA) {
|
|
112
|
+
long currentTime = System.currentTimeMillis();
|
|
113
|
+
if (currentTime - lastPinchTime > PINCH_TIMEOUT) {
|
|
114
|
+
triggered = true;
|
|
115
|
+
lastPinchTime = currentTime;
|
|
116
|
+
if (listener != null) {
|
|
117
|
+
listener.onThreeFingerPinchDetected();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return consumedByPreviousListener;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private float calculateSpan(MotionEvent event) {
|
|
126
|
+
float centerX = 0;
|
|
127
|
+
float centerY = 0;
|
|
128
|
+
for (int i = 0; i < REQUIRED_POINTER_COUNT; i++) {
|
|
129
|
+
centerX += event.getX(i);
|
|
130
|
+
centerY += event.getY(i);
|
|
131
|
+
}
|
|
132
|
+
centerX /= REQUIRED_POINTER_COUNT;
|
|
133
|
+
centerY /= REQUIRED_POINTER_COUNT;
|
|
134
|
+
|
|
135
|
+
float totalDistance = 0;
|
|
136
|
+
for (int i = 0; i < REQUIRED_POINTER_COUNT; i++) {
|
|
137
|
+
float dx = event.getX(i) - centerX;
|
|
138
|
+
float dy = event.getY(i) - centerY;
|
|
139
|
+
totalDistance += Math.sqrt(dx * dx + dy * dy);
|
|
140
|
+
}
|
|
141
|
+
return totalDistance / REQUIRED_POINTER_COUNT;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private View.OnTouchListener getCurrentOnTouchListener(View view) {
|
|
145
|
+
try {
|
|
146
|
+
Field listenerInfoField = View.class.getDeclaredField("mListenerInfo");
|
|
147
|
+
listenerInfoField.setAccessible(true);
|
|
148
|
+
Object listenerInfo = listenerInfoField.get(view);
|
|
149
|
+
if (listenerInfo == null) {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
Field onTouchListenerField = listenerInfo.getClass().getDeclaredField("mOnTouchListener");
|
|
153
|
+
onTouchListenerField.setAccessible(true);
|
|
154
|
+
Object listener = onTouchListenerField.get(listenerInfo);
|
|
155
|
+
if (listener instanceof View.OnTouchListener) {
|
|
156
|
+
return (View.OnTouchListener) listener;
|
|
157
|
+
}
|
|
158
|
+
} catch (ReflectiveOperationException | RuntimeException exception) {
|
|
159
|
+
logger.warn("Three finger pinch detector could not inspect the current touch listener: " + exception.getMessage());
|
|
160
|
+
}
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
private void reset() {
|
|
165
|
+
initialSpan = 0;
|
|
166
|
+
tracking = false;
|
|
167
|
+
triggered = false;
|
|
168
|
+
}
|
|
169
|
+
}
|
package/dist/docs.json
CHANGED
|
@@ -1431,7 +1431,7 @@
|
|
|
1431
1431
|
"text": "7.5.0"
|
|
1432
1432
|
}
|
|
1433
1433
|
],
|
|
1434
|
-
"docs": "Enable or disable the
|
|
1434
|
+
"docs": "Enable or disable the native preview menu gesture.\n\nDuring preview sessions, users can use the configured native gesture to:\n- Reload the current preview\n- Leave the test app and return to the fallback bundle\n- Switch update channel, when {@link PluginsConfig.CapacitorUpdater.allowShakeChannelSelector} is also enabled\n\nOutside preview sessions, this preview menu is ignored. The channel selector can still be\nshown outside preview sessions when {@link PluginsConfig.CapacitorUpdater.allowShakeChannelSelector} is enabled.\n\n**Important:** Disable this in production builds or only enable for internal testers.\n\nThis can also be configured via {@link PluginsConfig.CapacitorUpdater.shakeMenu}.\nThe native gesture can be configured via {@link PluginsConfig.CapacitorUpdater.shakeMenuGesture}\nor `options.gesture`.",
|
|
1435
1435
|
"complexTypes": [
|
|
1436
1436
|
"SetShakeMenuOptions"
|
|
1437
1437
|
],
|
|
@@ -1445,7 +1445,7 @@
|
|
|
1445
1445
|
"tags": [
|
|
1446
1446
|
{
|
|
1447
1447
|
"name": "returns",
|
|
1448
|
-
"text": "Object with
|
|
1448
|
+
"text": "Object with the current enabled state and gesture."
|
|
1449
1449
|
},
|
|
1450
1450
|
{
|
|
1451
1451
|
"name": "throws",
|
|
@@ -1456,7 +1456,7 @@
|
|
|
1456
1456
|
"text": "7.5.0"
|
|
1457
1457
|
}
|
|
1458
1458
|
],
|
|
1459
|
-
"docs": "Check if the
|
|
1459
|
+
"docs": "Check if the native preview menu gesture is currently enabled.\n\nReturns the current state of the shake menu feature that can be toggled via\n{@link setShakeMenu} or configured via {@link PluginsConfig.CapacitorUpdater.shakeMenu}.\n\nUse this to:\n- Check if debug features are enabled\n- Show/hide debug settings UI\n- Verify configuration during testing",
|
|
1460
1460
|
"complexTypes": [
|
|
1461
1461
|
"ShakeMenuEnabled"
|
|
1462
1462
|
],
|
|
@@ -1495,7 +1495,7 @@
|
|
|
1495
1495
|
"text": "8.43.0"
|
|
1496
1496
|
}
|
|
1497
1497
|
],
|
|
1498
|
-
"docs": "Enable or disable the
|
|
1498
|
+
"docs": "Enable or disable the channel selector menu gesture at runtime.\n\nWhen enabled, the configured native gesture can show a channel selector, including outside preview sessions.\nIf {@link setShakeMenu} is also enabled while a preview session is active, the shake menu includes\nboth preview actions and channel switching.\n\nThis can also be configured via {@link PluginsConfig.CapacitorUpdater.allowShakeChannelSelector}.\nThe native gesture can be configured via {@link PluginsConfig.CapacitorUpdater.shakeMenuGesture}\nor {@link setShakeMenu}.",
|
|
1499
1499
|
"complexTypes": [
|
|
1500
1500
|
"SetShakeChannelSelectorOptions"
|
|
1501
1501
|
],
|
|
@@ -3442,6 +3442,20 @@
|
|
|
3442
3442
|
"docs": "",
|
|
3443
3443
|
"complexTypes": [],
|
|
3444
3444
|
"type": "boolean"
|
|
3445
|
+
},
|
|
3446
|
+
{
|
|
3447
|
+
"name": "gesture",
|
|
3448
|
+
"tags": [
|
|
3449
|
+
{
|
|
3450
|
+
"text": "'shake'",
|
|
3451
|
+
"name": "default"
|
|
3452
|
+
}
|
|
3453
|
+
],
|
|
3454
|
+
"docs": "Native gesture used to open the preview/channel menu.",
|
|
3455
|
+
"complexTypes": [
|
|
3456
|
+
"ShakeMenuGesture"
|
|
3457
|
+
],
|
|
3458
|
+
"type": "ShakeMenuGesture"
|
|
3445
3459
|
}
|
|
3446
3460
|
]
|
|
3447
3461
|
},
|
|
@@ -3458,6 +3472,20 @@
|
|
|
3458
3472
|
"docs": "",
|
|
3459
3473
|
"complexTypes": [],
|
|
3460
3474
|
"type": "boolean"
|
|
3475
|
+
},
|
|
3476
|
+
{
|
|
3477
|
+
"name": "gesture",
|
|
3478
|
+
"tags": [
|
|
3479
|
+
{
|
|
3480
|
+
"text": "8.48.0",
|
|
3481
|
+
"name": "since"
|
|
3482
|
+
}
|
|
3483
|
+
],
|
|
3484
|
+
"docs": "The currently configured native gesture used to open the preview/channel menu.\nUndefined means consumers should treat the gesture as the default `shake` behavior.",
|
|
3485
|
+
"complexTypes": [
|
|
3486
|
+
"ShakeMenuGesture"
|
|
3487
|
+
],
|
|
3488
|
+
"type": "ShakeMenuGesture"
|
|
3461
3489
|
}
|
|
3462
3490
|
]
|
|
3463
3491
|
},
|
|
@@ -3986,6 +4014,21 @@
|
|
|
3986
4014
|
]
|
|
3987
4015
|
}
|
|
3988
4016
|
]
|
|
4017
|
+
},
|
|
4018
|
+
{
|
|
4019
|
+
"name": "ShakeMenuGesture",
|
|
4020
|
+
"slug": "shakemenugesture",
|
|
4021
|
+
"docs": "Native gesture options that open the shake menu.\n\nSupported values are `shake` and `threeFingerPinch`.",
|
|
4022
|
+
"types": [
|
|
4023
|
+
{
|
|
4024
|
+
"text": "'shake'",
|
|
4025
|
+
"complexTypes": []
|
|
4026
|
+
},
|
|
4027
|
+
{
|
|
4028
|
+
"text": "'threeFingerPinch'",
|
|
4029
|
+
"complexTypes": []
|
|
4030
|
+
}
|
|
4031
|
+
]
|
|
3989
4032
|
}
|
|
3990
4033
|
],
|
|
3991
4034
|
"pluginConfigs": [
|
|
@@ -4573,10 +4616,28 @@
|
|
|
4573
4616
|
"name": "since"
|
|
4574
4617
|
}
|
|
4575
4618
|
],
|
|
4576
|
-
"docs": "Enable the
|
|
4619
|
+
"docs": "Enable the native preview menu gesture while a preview session is active.\nOutside preview sessions this preview menu is ignored, unless\n{@link PluginsConfig.CapacitorUpdater.allowShakeChannelSelector} is enabled.",
|
|
4577
4620
|
"complexTypes": [],
|
|
4578
4621
|
"type": "boolean | undefined"
|
|
4579
4622
|
},
|
|
4623
|
+
{
|
|
4624
|
+
"name": "shakeMenuGesture",
|
|
4625
|
+
"tags": [
|
|
4626
|
+
{
|
|
4627
|
+
"text": "'shake'",
|
|
4628
|
+
"name": "default"
|
|
4629
|
+
},
|
|
4630
|
+
{
|
|
4631
|
+
"text": "8.48.0",
|
|
4632
|
+
"name": "since"
|
|
4633
|
+
}
|
|
4634
|
+
],
|
|
4635
|
+
"docs": "Choose which native gesture opens the preview/channel menu.\nThis applies to both {@link PluginsConfig.CapacitorUpdater.shakeMenu}\nand {@link PluginsConfig.CapacitorUpdater.allowShakeChannelSelector}.\n\nOnly available for Android and iOS.",
|
|
4636
|
+
"complexTypes": [
|
|
4637
|
+
"ShakeMenuGesture"
|
|
4638
|
+
],
|
|
4639
|
+
"type": "ShakeMenuGesture"
|
|
4640
|
+
},
|
|
4580
4641
|
{
|
|
4581
4642
|
"name": "allowShakeChannelSelector",
|
|
4582
4643
|
"tags": [
|
|
@@ -4589,7 +4650,7 @@
|
|
|
4589
4650
|
"name": "since"
|
|
4590
4651
|
}
|
|
4591
4652
|
],
|
|
4592
|
-
"docs": "Enable the
|
|
4653
|
+
"docs": "Enable the native menu gesture to show a channel selector menu for switching between update channels.\nIf {@link PluginsConfig.CapacitorUpdater.shakeMenu} is also enabled while a preview session is active,\nthe shake menu includes both preview actions and channel switching.\nThe native gesture can be changed with {@link PluginsConfig.CapacitorUpdater.shakeMenuGesture}.\n\nOnly available for Android and iOS.",
|
|
4593
4654
|
"complexTypes": [],
|
|
4594
4655
|
"type": "boolean | undefined"
|
|
4595
4656
|
}
|