@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.
- package/README.md +33 -15
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +66 -5
- package/android/src/main/java/ee/forgr/capacitor_updater/ShakeDetector.java +3 -3
- 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 +71 -10
- package/ios/Sources/CapacitorUpdaterPlugin/ShakeMenu.swift +141 -41
- package/package.json +1 -1
|
@@ -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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
150
|
+
@discardableResult
|
|
151
|
+
private func showDefaultMenu(plugin: CapacitorUpdaterPlugin, bridge: CAPBridgeProtocol) -> Bool {
|
|
55
152
|
// Prevent multiple alerts from showing
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
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
|
-
|
|
280
|
+
@discardableResult
|
|
281
|
+
private func showChannelSelector(plugin: CapacitorUpdaterPlugin, bridge: CAPBridgeProtocol) -> Bool {
|
|
183
282
|
// Prevent multiple alerts from showing
|
|
184
|
-
|
|
185
|
-
|
|
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
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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) {
|