@capgo/capacitor-updater 8.47.9 → 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.
@@ -7,6 +7,10 @@
7
7
  import UIKit
8
8
  import Capacitor
9
9
 
10
+ private var lastShakeMenuShownAt: TimeInterval = 0
11
+ private let shakeMenuCooldownSeconds: TimeInterval = 1.2
12
+ private let threeFingerPinchScaleDelta: CGFloat = 0.30
13
+
10
14
  extension UIApplication {
11
15
  // swiftlint:disable:next line_length
12
16
  public class func topViewController(_ base: UIViewController? = UIApplication.shared.windows.first?.rootViewController) -> UIViewController? {
@@ -23,45 +27,140 @@ extension UIApplication {
23
27
  }
24
28
  }
25
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
+
26
106
  extension UIWindow {
27
107
  override open func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
28
108
  if motion == .motionShake {
29
- // Find the CapacitorUpdaterPlugin instance
30
109
  guard let bridgeViewController = rootViewController as? CAPBridgeViewController,
31
110
  let bridge = bridgeViewController.bridge,
32
111
  let plugin = bridge.plugin(withName: "CapacitorUpdater") as? CapacitorUpdaterPlugin else {
33
112
  return
34
113
  }
35
-
36
- let canShowPreviewMenu = plugin.shakeMenuEnabled && plugin.hasActivePreviewSession()
37
- let canShowChannelSelector = plugin.shakeChannelSelectorEnabled
38
-
39
- if !canShowPreviewMenu && !canShowChannelSelector {
40
- if plugin.shakeMenuEnabled {
41
- plugin.logger.info("Shake preview menu ignored because no preview session is active")
42
- }
114
+ guard plugin.shakeMenuGesture == CapacitorUpdaterPlugin.shakeMenuGestureShake else {
43
115
  return
44
116
  }
45
117
 
46
- if canShowPreviewMenu {
47
- showDefaultMenu(plugin: plugin, bridge: bridge)
48
- } else {
49
- showChannelSelector(plugin: plugin, bridge: bridge)
118
+ _ = showCapacitorUpdaterMenu(plugin: plugin, bridge: bridge, gestureName: "Shake")
119
+ }
120
+ }
121
+
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")
50
130
  }
131
+ return false
51
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
146
+ }
147
+ return didShow
52
148
  }
53
149
 
54
- private func showDefaultMenu(plugin: CapacitorUpdaterPlugin, bridge: CAPBridgeProtocol) {
150
+ @discardableResult
151
+ private func showDefaultMenu(plugin: CapacitorUpdaterPlugin, bridge: CAPBridgeProtocol) -> Bool {
55
152
  // Prevent multiple alerts from showing
56
- if let topVC = UIApplication.topViewController(),
57
- topVC.isKind(of: UIAlertController.self) {
153
+ guard let topVC = UIApplication.topViewController() else {
154
+ return false
155
+ }
156
+ if topVC.isKind(of: UIAlertController.self) {
58
157
  plugin.logger.info("UIAlertController is already presented")
59
- return
158
+ return false
60
159
  }
61
160
 
62
161
  guard plugin.hasActivePreviewSession() else {
63
162
  plugin.logger.info("Shake preview menu ignored because no preview session is active")
64
- return
163
+ return false
65
164
  }
66
165
 
67
166
  let appName = Bundle.main.infoDictionary?["CFBundleDisplayName"] as? String ?? "App"
@@ -96,7 +195,7 @@ extension UIWindow {
96
195
  if plugin.shakeChannelSelectorEnabled {
97
196
  alertShake.addAction(UIAlertAction(title: "Switch channel", style: .default) { _ in
98
197
  let showSelector = {
99
- self.showChannelSelector(plugin: plugin, bridge: bridge)
198
+ _ = self.showChannelSelector(plugin: plugin, bridge: bridge)
100
199
  }
101
200
 
102
201
  if let presenter = alertShake.presentingViewController {
@@ -110,10 +209,9 @@ extension UIWindow {
110
209
  alertShake.addAction(UIAlertAction(title: cancelButtonTitle, style: .default))
111
210
 
112
211
  DispatchQueue.main.async {
113
- if let topVC = UIApplication.topViewController() {
114
- topVC.present(alertShake, animated: true)
115
- }
212
+ topVC.present(alertShake, animated: true)
116
213
  }
214
+ return true
117
215
  }
118
216
 
119
217
  private func showConfiguredDefaultMenu(plugin: CapacitorUpdaterPlugin, bridge: CAPBridgeProtocol) {
@@ -179,12 +277,15 @@ extension UIWindow {
179
277
  }
180
278
  }
181
279
 
182
- private func showChannelSelector(plugin: CapacitorUpdaterPlugin, bridge: CAPBridgeProtocol) {
280
+ @discardableResult
281
+ private func showChannelSelector(plugin: CapacitorUpdaterPlugin, bridge: CAPBridgeProtocol) -> Bool {
183
282
  // Prevent multiple alerts from showing
184
- if let topVC = UIApplication.topViewController(),
185
- topVC.isKind(of: UIAlertController.self) {
283
+ guard let topVC = UIApplication.topViewController() else {
284
+ return false
285
+ }
286
+ if topVC.isKind(of: UIAlertController.self) {
186
287
  plugin.logger.info("UIAlertController is already presented")
187
- return
288
+ return false
188
289
  }
189
290
 
190
291
  let updater = plugin.implementation
@@ -208,28 +309,27 @@ extension UIWindow {
208
309
  })
209
310
 
210
311
  DispatchQueue.main.async {
211
- if let topVC = UIApplication.topViewController() {
212
- topVC.present(loadingAlert, animated: true) {
213
- // Fetch channels in background
214
- DispatchQueue.global(qos: .userInitiated).async {
215
- let result = updater.listChannels()
312
+ topVC.present(loadingAlert, animated: true) {
313
+ // Fetch channels in background
314
+ DispatchQueue.global(qos: .userInitiated).async {
315
+ let result = updater.listChannels()
216
316
 
217
- DispatchQueue.main.async {
218
- loadingAlert.dismiss(animated: true) {
219
- guard !didCancel else { return }
220
- if !result.error.isEmpty {
221
- self.showError(message: "Failed to load channels: \(result.error)", plugin: plugin)
222
- } else if result.channels.isEmpty {
223
- self.showError(message: "No channels available for self-assignment", plugin: plugin)
224
- } else {
225
- self.presentChannelPicker(channels: result.channels, plugin: plugin, bridge: bridge)
226
- }
317
+ DispatchQueue.main.async {
318
+ loadingAlert.dismiss(animated: true) {
319
+ guard !didCancel else { return }
320
+ if !result.error.isEmpty {
321
+ self.showError(message: "Failed to load channels: \(result.error)", plugin: plugin)
322
+ } else if result.channels.isEmpty {
323
+ self.showError(message: "No channels available for self-assignment", plugin: plugin)
324
+ } else {
325
+ self.presentChannelPicker(channels: result.channels, plugin: plugin, bridge: bridge)
227
326
  }
228
327
  }
229
328
  }
230
329
  }
231
330
  }
232
331
  }
332
+ return true
233
333
  }
234
334
 
235
335
  private func presentChannelPicker(channels: [[String: Any]], plugin: CapacitorUpdaterPlugin, bridge: CAPBridgeProtocol) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-updater",
3
- "version": "8.47.9",
3
+ "version": "8.47.11",
4
4
  "license": "MPL-2.0",
5
5
  "description": "Live update for capacitor apps",
6
6
  "main": "dist/plugin.cjs.js",