@capgo/capacitor-updater 8.47.10 → 8.47.13

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.
@@ -89,6 +89,8 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
89
89
  static let autoUpdateModeLaunch = "onLaunch"
90
90
  static let autoUpdateModeAlways = "always"
91
91
  static let autoUpdateModeOnlyDownload = "onlyDownload"
92
+ static let shakeMenuGestureShake = "shake"
93
+ static let shakeMenuGestureThreeFingerPinch = "threeFingerPinch"
92
94
  private static let previewLoaderTimeoutMs = 60000
93
95
  private let keepUrlPathFlagKey = "__capgo_keep_url_path_after_reload"
94
96
  private let customIdDefaultsKey = "CapacitorUpdater.customId"
@@ -167,6 +169,9 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
167
169
  private var webViewStatsReporter: WebViewStatsReporter?
168
170
  public var shakeMenuEnabled = false
169
171
  public var shakeChannelSelectorEnabled = false
172
+ public var shakeMenuGesture = CapacitorUpdaterPlugin.shakeMenuGestureShake
173
+ var shakeMenuPinchGestureRecognizer: UIPinchGestureRecognizer?
174
+ var shakeMenuPinchGestureTriggered = false
170
175
  public var previewSessionEnabled = false
171
176
  private var previewSessionAlertPending = false
172
177
  private var isLeavingPreviewForIncomingLink = false
@@ -241,6 +246,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
241
246
  resetWhenUpdate = getConfig().getBoolean("resetWhenUpdate", true)
242
247
  shakeMenuEnabled = getConfig().getBoolean("shakeMenu", false)
243
248
  shakeChannelSelectorEnabled = getConfig().getBoolean("allowShakeChannelSelector", false)
249
+ shakeMenuGesture = Self.normalizedShakeMenuGesture(getConfig().getString("shakeMenuGesture", Self.shakeMenuGestureShake))
244
250
  let storedPreviewSessionEnabled = UserDefaults.standard.bool(forKey: previewSessionDefaultsKey)
245
251
  let shouldClearPreviewSessionBecauseDisabled = !allowPreview && storedPreviewSessionEnabled
246
252
  previewSessionEnabled = allowPreview && storedPreviewSessionEnabled
@@ -251,6 +257,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
251
257
  shakeChannelSelectorEnabled = UserDefaults.standard.object(forKey: previewPreviousShakeChannelSelectorDefaultsKey) as? Bool
252
258
  ?? shakeChannelSelectorEnabled
253
259
  }
260
+ syncShakeMenuGestureRecognizer()
254
261
  periodCheckDelay = Self.normalizedPeriodCheckDelaySeconds(getConfig().getInt("periodCheckDelay", 0))
255
262
 
256
263
  implementation.setPublicKey(getConfig().getString("publicKey") ?? "")
@@ -1212,6 +1219,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
1212
1219
  self.previewSessionAlertPending = true
1213
1220
  self.implementation.previewSession = true
1214
1221
  self.shakeMenuEnabled = true
1222
+ self.syncShakeMenuGestureRecognizer()
1215
1223
  UserDefaults.standard.set(true, forKey: self.previewSessionDefaultsKey)
1216
1224
  UserDefaults.standard.set(true, forKey: self.previewSessionAlertPendingDefaultsKey)
1217
1225
  UserDefaults.standard.synchronize()
@@ -1398,6 +1406,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
1398
1406
  }
1399
1407
  self.shakeMenuEnabled = previousShakeMenuEnabled
1400
1408
  self.shakeChannelSelectorEnabled = previousShakeChannelSelectorEnabled
1409
+ self.syncShakeMenuGestureRecognizer()
1401
1410
  _ = self.implementation.setPreviewFallbackBundle(fallback: nil)
1402
1411
  self.clearPreviewSessionPreferences()
1403
1412
  logger.info("Preview session ended")
@@ -1421,6 +1430,8 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
1421
1430
  self.hidePreviewTransitionLoader(reason: "preview-session-disabled")
1422
1431
  self.shakeMenuEnabled = self.getBooleanConfig("shakeMenu", defaultValue: false)
1423
1432
  self.shakeChannelSelectorEnabled = self.getBooleanConfig("allowShakeChannelSelector", defaultValue: false)
1433
+ self.shakeMenuGesture = Self.normalizedShakeMenuGesture(self.getStringConfig("shakeMenuGesture", defaultValue: Self.shakeMenuGestureShake))
1434
+ self.syncShakeMenuGestureRecognizer()
1424
1435
  self.clearPreviewSessionPreferences()
1425
1436
  }
1426
1437
 
@@ -1652,6 +1663,8 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
1652
1663
  self.implementation.previewSession = false
1653
1664
  self.shakeMenuEnabled = self.getBooleanConfig("shakeMenu", defaultValue: false)
1654
1665
  self.shakeChannelSelectorEnabled = self.getBooleanConfig("allowShakeChannelSelector", defaultValue: false)
1666
+ self.shakeMenuGesture = Self.normalizedShakeMenuGesture(self.getStringConfig("shakeMenuGesture", defaultValue: Self.shakeMenuGestureShake))
1667
+ self.syncShakeMenuGestureRecognizer()
1655
1668
  self.restorePreviewPreviousAppId()
1656
1669
  self.restorePreviewPreviousDefaultChannel()
1657
1670
  _ = self.implementation.setPreviewFallbackBundle(fallback: nil)
@@ -2765,6 +2778,28 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
2765
2778
  }
2766
2779
  }
2767
2780
 
2781
+ static func normalizedShakeMenuGesture(_ value: String?) -> String {
2782
+ guard let value, !value.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
2783
+ return shakeMenuGestureShake
2784
+ }
2785
+ let normalized = value.trimmingCharacters(in: .whitespacesAndNewlines)
2786
+ if normalized == shakeMenuGestureThreeFingerPinch {
2787
+ return shakeMenuGestureThreeFingerPinch
2788
+ }
2789
+ return shakeMenuGestureShake
2790
+ }
2791
+
2792
+ static func isSupportedShakeMenuGesture(_ value: String?) -> Bool {
2793
+ guard let value else {
2794
+ return true
2795
+ }
2796
+ let normalized = value.trimmingCharacters(in: .whitespacesAndNewlines)
2797
+ if normalized.isEmpty {
2798
+ return false
2799
+ }
2800
+ return normalized == shakeMenuGestureShake || normalized == shakeMenuGestureThreeFingerPinch
2801
+ }
2802
+
2768
2803
  static func autoUpdateModeForLegacyDirectUpdateMode(_ directUpdateMode: String) -> String {
2769
2804
  switch directUpdateMode {
2770
2805
  case autoUpdateModeInstall, autoUpdateModeLaunch, autoUpdateModeAlways:
@@ -3485,14 +3520,25 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
3485
3520
  return
3486
3521
  }
3487
3522
 
3523
+ if let gesture = call.getString("gesture") {
3524
+ guard Self.isSupportedShakeMenuGesture(gesture) else {
3525
+ logger.error("Unsupported shake menu gesture: \(gesture)")
3526
+ call.reject("Unsupported shake menu gesture. Use \"shake\" or \"threeFingerPinch\".")
3527
+ return
3528
+ }
3529
+ self.shakeMenuGesture = Self.normalizedShakeMenuGesture(gesture)
3530
+ }
3531
+
3488
3532
  self.shakeMenuEnabled = enabled
3489
- logger.info("Shake menu \(enabled ? "enabled" : "disabled")")
3533
+ self.syncShakeMenuGestureRecognizer()
3534
+ logger.info("Shake menu \(enabled ? "enabled" : "disabled") with \(self.shakeMenuGesture) gesture")
3490
3535
  call.resolve()
3491
3536
  }
3492
3537
 
3493
3538
  @objc func isShakeMenuEnabled(_ call: CAPPluginCall) {
3494
3539
  call.resolve([
3495
- "enabled": self.shakeMenuEnabled
3540
+ "enabled": self.shakeMenuEnabled,
3541
+ "gesture": self.shakeMenuGesture
3496
3542
  ])
3497
3543
  }
3498
3544
 
@@ -3504,6 +3550,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
3504
3550
  }
3505
3551
 
3506
3552
  self.shakeChannelSelectorEnabled = enabled
3553
+ self.syncShakeMenuGestureRecognizer()
3507
3554
  logger.info("Shake channel selector \(enabled ? "enabled" : "disabled")")
3508
3555
  call.resolve()
3509
3556
  }
@@ -9,6 +9,7 @@ import Capacitor
9
9
 
10
10
  private var lastShakeMenuShownAt: TimeInterval = 0
11
11
  private let shakeMenuCooldownSeconds: TimeInterval = 1.2
12
+ private let threeFingerPinchScaleDelta: CGFloat = 0.30
12
13
 
13
14
  extension UIApplication {
14
15
  // swiftlint:disable:next line_length
@@ -26,42 +27,124 @@ extension UIApplication {
26
27
  }
27
28
  }
28
29
 
30
+ extension CapacitorUpdaterPlugin: UIGestureRecognizerDelegate {
31
+ func syncShakeMenuGestureRecognizer() {
32
+ DispatchQueue.main.async { [weak self] in
33
+ guard let self else { return }
34
+
35
+ let shouldInstall = self.shakeMenuGesture == Self.shakeMenuGestureThreeFingerPinch &&
36
+ (self.shakeMenuEnabled || self.shakeChannelSelectorEnabled)
37
+
38
+ guard shouldInstall, let targetView = self.bridge?.webView ?? self.bridge?.viewController?.view else {
39
+ self.removeShakeMenuGestureRecognizer()
40
+ return
41
+ }
42
+
43
+ if self.shakeMenuPinchGestureRecognizer?.view === targetView {
44
+ return
45
+ }
46
+
47
+ self.removeShakeMenuGestureRecognizer()
48
+
49
+ let recognizer = UIPinchGestureRecognizer(target: self, action: #selector(self.handleShakeMenuPinch(_:)))
50
+ recognizer.cancelsTouchesInView = false
51
+ recognizer.delaysTouchesBegan = false
52
+ recognizer.delaysTouchesEnded = false
53
+ recognizer.delegate = self
54
+ targetView.addGestureRecognizer(recognizer)
55
+ self.shakeMenuPinchGestureRecognizer = recognizer
56
+ self.logger.info("Three finger pinch menu gesture initialized")
57
+ }
58
+ }
59
+
60
+ func removeShakeMenuGestureRecognizer() {
61
+ if let recognizer = self.shakeMenuPinchGestureRecognizer {
62
+ recognizer.view?.removeGestureRecognizer(recognizer)
63
+ self.shakeMenuPinchGestureRecognizer = nil
64
+ self.shakeMenuPinchGestureTriggered = false
65
+ self.logger.info("Three finger pinch menu gesture stopped")
66
+ }
67
+ }
68
+
69
+ @objc func handleShakeMenuPinch(_ recognizer: UIPinchGestureRecognizer) {
70
+ if recognizer.state == .ended || recognizer.state == .cancelled || recognizer.state == .failed {
71
+ self.shakeMenuPinchGestureTriggered = false
72
+ return
73
+ }
74
+ guard recognizer.state == .changed, !self.shakeMenuPinchGestureTriggered, recognizer.numberOfTouches == 3 else {
75
+ return
76
+ }
77
+ guard abs(recognizer.scale - 1) >= threeFingerPinchScaleDelta else {
78
+ return
79
+ }
80
+ guard self.shakeMenuGesture == Self.shakeMenuGestureThreeFingerPinch, let bridge = self.bridge else {
81
+ return
82
+ }
83
+ guard let window = recognizer.view?.window ?? bridge.viewController?.view.window else {
84
+ return
85
+ }
86
+
87
+ self.shakeMenuPinchGestureTriggered = true
88
+ _ = window.showCapacitorUpdaterMenu(plugin: self, bridge: bridge, gestureName: "Three finger pinch")
89
+ }
90
+
91
+ public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
92
+ if gestureRecognizer === self.shakeMenuPinchGestureRecognizer {
93
+ self.shakeMenuPinchGestureTriggered = false
94
+ }
95
+ return true
96
+ }
97
+
98
+ public func gestureRecognizer(
99
+ _: UIGestureRecognizer,
100
+ shouldRecognizeSimultaneouslyWith _: UIGestureRecognizer
101
+ ) -> Bool {
102
+ true
103
+ }
104
+ }
105
+
29
106
  extension UIWindow {
30
107
  override open func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
31
108
  if motion == .motionShake {
32
- // Find the CapacitorUpdaterPlugin instance
33
109
  guard let bridgeViewController = rootViewController as? CAPBridgeViewController,
34
110
  let bridge = bridgeViewController.bridge,
35
111
  let plugin = bridge.plugin(withName: "CapacitorUpdater") as? CapacitorUpdaterPlugin else {
36
112
  return
37
113
  }
38
-
39
- let canShowPreviewMenu = plugin.shakeMenuEnabled && plugin.hasActivePreviewSession()
40
- let canShowChannelSelector = plugin.shakeChannelSelectorEnabled
41
-
42
- if !canShowPreviewMenu && !canShowChannelSelector {
43
- if plugin.shakeMenuEnabled {
44
- plugin.logger.info("Shake preview menu ignored because no preview session is active")
45
- }
114
+ guard plugin.shakeMenuGesture == CapacitorUpdaterPlugin.shakeMenuGestureShake else {
46
115
  return
47
116
  }
48
117
 
49
- let now = Date().timeIntervalSince1970
50
- guard now - lastShakeMenuShownAt >= shakeMenuCooldownSeconds else {
51
- plugin.logger.info("Shake menu ignored because cooldown is active")
52
- return
53
- }
118
+ _ = showCapacitorUpdaterMenu(plugin: plugin, bridge: bridge, gestureName: "Shake")
119
+ }
120
+ }
54
121
 
55
- if canShowPreviewMenu {
56
- if showDefaultMenu(plugin: plugin, bridge: bridge) {
57
- lastShakeMenuShownAt = now
58
- }
59
- } else {
60
- if showChannelSelector(plugin: plugin, bridge: bridge) {
61
- lastShakeMenuShownAt = now
62
- }
122
+ @discardableResult
123
+ fileprivate func showCapacitorUpdaterMenu(plugin: CapacitorUpdaterPlugin, bridge: CAPBridgeProtocol, gestureName: String) -> Bool {
124
+ let canShowPreviewMenu = plugin.shakeMenuEnabled && plugin.hasActivePreviewSession()
125
+ let canShowChannelSelector = plugin.shakeChannelSelectorEnabled
126
+
127
+ if !canShowPreviewMenu && !canShowChannelSelector {
128
+ if plugin.shakeMenuEnabled {
129
+ plugin.logger.info("\(gestureName) preview menu ignored because no preview session is active")
63
130
  }
131
+ return false
132
+ }
133
+
134
+ let now = Date().timeIntervalSince1970
135
+ guard now - lastShakeMenuShownAt >= shakeMenuCooldownSeconds else {
136
+ plugin.logger.info("\(gestureName) menu ignored because cooldown is active")
137
+ return false
138
+ }
139
+
140
+ let didShow = canShowPreviewMenu
141
+ ? showDefaultMenu(plugin: plugin, bridge: bridge)
142
+ : showChannelSelector(plugin: plugin, bridge: bridge)
143
+
144
+ if didShow {
145
+ lastShakeMenuShownAt = now
64
146
  }
147
+ return didShow
65
148
  }
66
149
 
67
150
  @discardableResult
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-updater",
3
- "version": "8.47.10",
3
+ "version": "8.47.13",
4
4
  "license": "MPL-2.0",
5
5
  "description": "Live update for capacitor apps",
6
6
  "main": "dist/plugin.cjs.js",