@capgo/capacitor-stream-call 0.0.96 → 7.0.2

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.
@@ -13,7 +13,7 @@ Pod::Spec.new do |s|
13
13
  s.source_files = 'ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}'
14
14
  s.ios.deployment_target = '14.0'
15
15
  s.dependency 'Capacitor'
16
- s.dependency 'StreamVideo', '1.29.0'
17
- s.dependency 'StreamVideoSwiftUI', '1.29.0'
16
+ s.dependency 'StreamVideo', '1.29.1'
17
+ s.dependency 'StreamVideoSwiftUI', '1.29.1'
18
18
  s.swift_version = '5.1'
19
19
  end
package/Package.swift CHANGED
@@ -11,7 +11,7 @@ let package = Package(
11
11
  ],
12
12
  dependencies: [
13
13
  .package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "7.0.0"),
14
- .package(url: "https://github.com/GetStream/stream-video-swift.git", exact: "1.29.0")
14
+ .package(url: "https://github.com/GetStream/stream-video-swift.git", exact: "1.29.1")
15
15
  ],
16
16
  targets: [
17
17
  .target(
@@ -77,8 +77,8 @@ dependencies {
77
77
 
78
78
 
79
79
  // Stream dependencies
80
- implementation("io.getstream:stream-video-android-ui-compose:1.9.2")
81
- implementation("io.getstream:stream-video-android-core:1.9.2")
80
+ implementation("io.getstream:stream-video-android-ui-compose:1.10.0")
81
+ implementation("io.getstream:stream-video-android-core:1.10.0")
82
82
  implementation("io.getstream:stream-android-push:1.3.2")
83
83
  implementation("io.getstream:stream-android-push-firebase:1.3.2")
84
84
 
@@ -370,19 +370,6 @@ class StreamCallPlugin : Plugin() {
370
370
  val context = context
371
371
  val originalParent = bridge?.webView?.parent as? ViewGroup ?: return
372
372
 
373
- // Wrap original parent with TouchInterceptWrapper to allow touch passthrough
374
- val rootParent = originalParent.parent as? ViewGroup
375
- val indexInRoot = rootParent?.indexOfChild(originalParent) ?: -1
376
- if (rootParent != null && indexInRoot >= 0) {
377
- rootParent.removeViewAt(indexInRoot)
378
- touchInterceptWrapper = TouchInterceptWrapper(originalParent).apply {
379
- setBackgroundColor(Color.TRANSPARENT)
380
- }
381
- rootParent.addView(touchInterceptWrapper, indexInRoot)
382
- }
383
-
384
- val parent: ViewGroup = touchInterceptWrapper ?: originalParent
385
-
386
373
  // Make WebView initially visible and opaque
387
374
  bridge?.webView?.setBackgroundColor(Color.WHITE) // Or whatever background color suits your app
388
375
 
@@ -394,7 +381,7 @@ class StreamCallPlugin : Plugin() {
394
381
  ViewGroup.LayoutParams.MATCH_PARENT
395
382
  )
396
383
  }
397
- parent.addView(overlayView, 0) // Add at index 0 to ensure it's below WebView
384
+ originalParent.addView(overlayView, 0) // Add at index 0 to ensure it's below WebView
398
385
 
399
386
  // Initialize with active call content
400
387
  setOverlayContent()
@@ -408,7 +395,79 @@ class StreamCallPlugin : Plugin() {
408
395
  )
409
396
  setBackgroundColor("#1a242c".toColorInt())
410
397
  }
411
- parent.addView(barrierView, parent.indexOfChild(bridge?.webView) + 1) // Add above WebView
398
+ originalParent.addView(barrierView, originalParent.indexOfChild(bridge?.webView) + 1) // Add above WebView
399
+ }
400
+
401
+ private fun addTouchInterceptor() {
402
+ val originalParent = bridge?.webView?.parent as? ViewGroup ?: return
403
+
404
+ // Check if touch interceptor already exists
405
+ if (touchInterceptWrapper != null) {
406
+ Log.d("StreamCallPlugin", "Touch interceptor already exists, skipping creation")
407
+ return
408
+ }
409
+
410
+ // Wrap original parent with TouchInterceptWrapper to allow touch passthrough
411
+ val rootParent = originalParent.parent as? ViewGroup
412
+ val indexInRoot = rootParent?.indexOfChild(originalParent) ?: -1
413
+ if (rootParent != null && indexInRoot >= 0) {
414
+ rootParent.removeViewAt(indexInRoot)
415
+ touchInterceptWrapper = TouchInterceptWrapper(originalParent).apply {
416
+ setBackgroundColor(Color.TRANSPARENT)
417
+ }
418
+ rootParent.addView(touchInterceptWrapper, indexInRoot)
419
+
420
+ // Move views to touch interceptor
421
+ val parent = touchInterceptWrapper!!
422
+ if (overlayView?.parent != parent) {
423
+ (overlayView?.parent as? ViewGroup)?.removeView(overlayView)
424
+ parent.addView(overlayView, 0)
425
+ }
426
+ if (barrierView?.parent != parent) {
427
+ (barrierView?.parent as? ViewGroup)?.removeView(barrierView)
428
+ parent.addView(barrierView, parent.indexOfChild(bridge?.webView) + 1)
429
+ }
430
+
431
+ Log.d("StreamCallPlugin", "Touch interceptor added for active call")
432
+ }
433
+ }
434
+
435
+ private fun removeTouchInterceptor() {
436
+ val touchWrapper = touchInterceptWrapper ?: return
437
+ val rootParent = touchWrapper.parent as? ViewGroup ?: return
438
+ val indexInRoot = rootParent.indexOfChild(touchWrapper)
439
+
440
+ // Get the original parent (should be the only child of touchWrapper)
441
+ val originalParent = touchWrapper.getChildAt(0) as? ViewGroup
442
+ if (originalParent != null && originalParent !is ComposeView) {
443
+ // Move views back to original parent (only if it's not a ComposeView)
444
+ if (overlayView?.parent == touchWrapper) {
445
+ touchWrapper.removeView(overlayView)
446
+ originalParent.addView(overlayView, 0)
447
+ }
448
+ if (barrierView?.parent == touchWrapper) {
449
+ touchWrapper.removeView(barrierView)
450
+ originalParent.addView(barrierView, originalParent.indexOfChild(bridge?.webView) + 1)
451
+ }
452
+
453
+ // Remove touch wrapper and restore original parent
454
+ touchWrapper.removeView(originalParent)
455
+ rootParent.removeView(touchWrapper)
456
+ rootParent.addView(originalParent, indexInRoot)
457
+
458
+ touchInterceptWrapper = null
459
+ Log.d("StreamCallPlugin", "Touch interceptor removed after call ended")
460
+ } else {
461
+ // If original parent is a ComposeView or null, just remove the touch wrapper
462
+ // The views will stay where they are
463
+ if (originalParent != null) {
464
+ touchWrapper.removeView(originalParent)
465
+ rootParent.removeView(touchWrapper)
466
+ rootParent.addView(originalParent, indexInRoot)
467
+ }
468
+ touchInterceptWrapper = null
469
+ Log.d("StreamCallPlugin", "Touch interceptor removed (ComposeView parent case)")
470
+ }
412
471
  }
413
472
 
414
473
  /**
@@ -1101,6 +1160,10 @@ class StreamCallPlugin : Plugin() {
1101
1160
  // Show overlay view with the active call and make webview transparent
1102
1161
  runOnMainThread {
1103
1162
  Log.d("StreamCallPlugin", "internalAcceptCall: Updating UI for active call ${call.id} - setting overlay visible.")
1163
+
1164
+ // Add touch interceptor for the call
1165
+ addTouchInterceptor()
1166
+
1104
1167
  bridge?.webView?.setBackgroundColor(Color.TRANSPARENT) // Make webview transparent
1105
1168
  Log.d("StreamCallPlugin", "internalAcceptCall: WebView background set to transparent for call ${call.id}")
1106
1169
  bridge?.webView?.bringToFront() // Ensure WebView is on top and transparent
@@ -1474,6 +1537,9 @@ class StreamCallPlugin : Plugin() {
1474
1537
  streamCall?.microphone?.setEnabled(true)
1475
1538
  streamCall?.camera?.setEnabled(!isAudioOnly)
1476
1539
 
1540
+ // Add touch interceptor for the call
1541
+ addTouchInterceptor()
1542
+
1477
1543
  bridge?.webView?.setBackgroundColor(Color.TRANSPARENT) // Make webview transparent
1478
1544
  bridge?.webView?.bringToFront() // Ensure WebView is on top and transparent
1479
1545
  setOverlayContent(streamCall)
@@ -1912,6 +1978,9 @@ class StreamCallPlugin : Plugin() {
1912
1978
  setOverlayContent(call)
1913
1979
  overlayView?.isVisible = false
1914
1980
  bridge?.webView?.setBackgroundColor(Color.WHITE) // Restore webview opacity
1981
+
1982
+ // Remove touch interceptor
1983
+ removeTouchInterceptor()
1915
1984
 
1916
1985
  // Also hide incoming call view if visible
1917
1986
  Log.d("StreamCallPlugin", "Hiding incoming call view for call $callId")
@@ -245,9 +245,6 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
245
245
  print("- In call state detected")
246
246
  print("- All participants: \(String(describing: viewModel.participants))")
247
247
 
248
- // Enable touch interceptor when call becomes active
249
- self.touchInterceptView?.setCallActive(true)
250
-
251
248
  // Create/update overlay and make visible when there's an active call
252
249
  self.createCallOverlayView()
253
250
 
@@ -303,9 +300,6 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
303
300
  } else if newState == .idle {
304
301
  print("Call state changed to idle. CurrentCallId: \(self.currentCallId), ActiveCall: \(String(describing: self.streamVideo?.state.activeCall?.cId))")
305
302
 
306
- // Disable touch interceptor when call becomes inactive
307
- self.touchInterceptView?.setCallActive(false)
308
-
309
303
  // Only notify about call ending if we have a valid stored call ID and there's truly no active call
310
304
  // This prevents false "left" events during normal state transitions
311
305
  if !self.currentCallId.isEmpty && self.streamVideo?.state.activeCall == nil {
@@ -433,9 +427,11 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
433
427
  Task { @MainActor in
434
428
  // self.overlayViewModel?.updateCall(nil)
435
429
  // self.overlayViewModel?.updateStreamVideo(nil)
436
- self.touchInterceptView?.setCallActive(false)
437
430
  self.overlayView?.isHidden = true
438
431
  self.webView?.isOpaque = true
432
+
433
+ // Remove touch interceptor if it exists
434
+ self.removeTouchInterceptor()
439
435
  }
440
436
 
441
437
  call.resolve([
@@ -604,6 +600,9 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
604
600
 
605
601
  // Update UI on main thread
606
602
  await MainActor.run {
603
+ // Add touch interceptor for the call
604
+ self.addTouchInterceptor()
605
+
607
606
  // self.overlayViewModel?.updateCall(streamCall)
608
607
  self.overlayView?.isHidden = false
609
608
  self.webView?.isOpaque = false
@@ -671,9 +670,11 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
671
670
  }
672
671
 
673
672
  await MainActor.run {
674
- self.touchInterceptView?.setCallActive(false)
675
673
  self.overlayView?.isHidden = true
676
674
  self.webView?.isOpaque = true
675
+
676
+ // Remove touch interceptor
677
+ self.removeTouchInterceptor()
677
678
  }
678
679
 
679
680
  call.resolve([
@@ -700,9 +701,11 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
700
701
  await callViewModel?.hangUp()
701
702
 
702
703
  await MainActor.run {
703
- self.touchInterceptView?.setCallActive(false)
704
704
  self.overlayView?.isHidden = true
705
705
  self.webView?.isOpaque = true
706
+
707
+ // Remove touch interceptor
708
+ self.removeTouchInterceptor()
706
709
  }
707
710
 
708
711
  call.resolve([
@@ -809,6 +812,9 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
809
812
 
810
813
  // Update the CallOverlayView with the active call
811
814
  await MainActor.run {
815
+ // Add touch interceptor for the call
816
+ self.addTouchInterceptor()
817
+
812
818
  // self.overlayViewModel?.updateCall(streamCall)
813
819
  self.overlayView?.isHidden = false
814
820
  self.webView?.isOpaque = false
@@ -900,12 +906,6 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
900
906
  private func setupViews() {
901
907
  guard let webView = self.webView, let parent = webView.superview else { return }
902
908
 
903
- // Create the touch intercept view as an overlay for touch passthrough
904
- let touchInterceptView = TouchInterceptView(frame: parent.bounds)
905
- touchInterceptView.translatesAutoresizingMaskIntoConstraints = false
906
- touchInterceptView.backgroundColor = .clear
907
- touchInterceptView.isOpaque = false
908
-
909
909
  // Create SwiftUI view with view model if not already created
910
910
  if self.overlayView == nil, let callViewModel = self.callViewModel {
911
911
  let hostingController = UIHostingController(rootView: CallOverlayView(viewModel: callViewModel))
@@ -930,16 +930,34 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
930
930
  ])
931
931
  }
932
932
  }
933
+ }
934
+
935
+ private func addTouchInterceptor() {
936
+ guard let webView = self.webView, let parent = webView.superview else { return }
937
+
938
+ // Check if touch interceptor already exists
939
+ if self.touchInterceptView != nil {
940
+ print("Touch interceptor already exists, skipping creation")
941
+ return
942
+ }
943
+
944
+ // Create the touch intercept view as an overlay for touch passthrough
945
+ let touchInterceptView = TouchInterceptView(frame: parent.bounds)
946
+ touchInterceptView.translatesAutoresizingMaskIntoConstraints = false
947
+ touchInterceptView.backgroundColor = .clear
948
+ touchInterceptView.isOpaque = false
933
949
 
934
950
  // Setup touch intercept view with references to webview and overlay
935
951
  if let overlayView = self.overlayView {
936
952
  touchInterceptView.setupWithWebView(webView, overlayView: overlayView)
937
- // Insert touchInterceptView above webView
938
- parent.insertSubview(touchInterceptView, aboveSubview: webView)
953
+ // Add touchInterceptView as the topmost view
954
+ parent.addSubview(touchInterceptView)
955
+ parent.bringSubviewToFront(touchInterceptView)
939
956
  } else {
940
957
  // If overlayView is not present, just add on top of webView
941
958
  touchInterceptView.setupWithWebView(webView, overlayView: webView)
942
- parent.insertSubview(touchInterceptView, aboveSubview: webView)
959
+ parent.addSubview(touchInterceptView)
960
+ parent.bringSubviewToFront(touchInterceptView)
943
961
  }
944
962
 
945
963
  // Set up active call check function
@@ -957,6 +975,18 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
957
975
  touchInterceptView.leadingAnchor.constraint(equalTo: parent.leadingAnchor),
958
976
  touchInterceptView.trailingAnchor.constraint(equalTo: parent.trailingAnchor)
959
977
  ])
978
+
979
+ print("Touch interceptor added for active call - view hierarchy: \(parent.subviews.map { type(of: $0) })")
980
+ }
981
+
982
+ private func removeTouchInterceptor() {
983
+ guard let touchInterceptView = self.touchInterceptView else { return }
984
+
985
+ // Remove touch interceptor from view hierarchy
986
+ touchInterceptView.removeFromSuperview()
987
+ self.touchInterceptView = nil
988
+
989
+ print("Touch interceptor removed after call ended")
960
990
  }
961
991
 
962
992
  private func createCallOverlayView() {
@@ -1004,43 +1034,8 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
1004
1034
  self.hostingController = overlayView
1005
1035
  self.overlayView = overlayView.view
1006
1036
 
1007
- // Ensure touch intercept view is on top
1008
- if let touchInterceptView = parent.subviews.first(where: { $0 is TouchInterceptView }) {
1009
- parent.bringSubviewToFront(touchInterceptView)
1010
- // Update reference and set call active
1011
- self.touchInterceptView = touchInterceptView as? TouchInterceptView
1012
-
1013
- // Set up active call check function
1014
- self.touchInterceptView?.setActiveCallCheck { [weak self] in
1015
- return self?.streamVideo?.state.activeCall != nil
1016
- }
1017
-
1018
- self.touchInterceptView?.setCallActive(true)
1019
- } else {
1020
- // Create touch intercept view if not already created
1021
- let touchInterceptView = TouchInterceptView(frame: parent.bounds)
1022
- touchInterceptView.translatesAutoresizingMaskIntoConstraints = false
1023
- touchInterceptView.backgroundColor = .clear
1024
- touchInterceptView.isOpaque = false
1025
- touchInterceptView.setupWithWebView(webView, overlayView: overlayView.view)
1026
- parent.addSubview(touchInterceptView)
1027
-
1028
- // Set up active call check function
1029
- touchInterceptView.setActiveCallCheck { [weak self] in
1030
- return self?.streamVideo?.state.activeCall != nil
1031
- }
1032
-
1033
- // Store reference and set call active
1034
- self.touchInterceptView = touchInterceptView
1035
- self.touchInterceptView?.setCallActive(true)
1036
-
1037
- NSLayoutConstraint.activate([
1038
- touchInterceptView.topAnchor.constraint(equalTo: parent.topAnchor),
1039
- touchInterceptView.bottomAnchor.constraint(equalTo: parent.bottomAnchor),
1040
- touchInterceptView.leadingAnchor.constraint(equalTo: parent.leadingAnchor),
1041
- touchInterceptView.trailingAnchor.constraint(equalTo: parent.trailingAnchor)
1042
- ])
1043
- }
1037
+ // Add touch interceptor if not already present
1038
+ self.addTouchInterceptor()
1044
1039
  }
1045
1040
 
1046
1041
  // MARK: - Dynamic API Key Management
@@ -1066,9 +1061,6 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
1066
1061
  }
1067
1062
 
1068
1063
  func ensureViewRemoved() {
1069
- // Disable touch interceptor when overlay is removed
1070
- self.touchInterceptView?.setCallActive(false)
1071
-
1072
1064
  // Check if we have an overlay view
1073
1065
  if let existingOverlayView = self.overlayView {
1074
1066
  print("Hiding call overlay view")
@@ -1083,6 +1075,9 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
1083
1075
  } else {
1084
1076
  print("No call overlay view to hide")
1085
1077
  }
1078
+
1079
+ // Remove touch interceptor
1080
+ self.removeTouchInterceptor()
1086
1081
  }
1087
1082
 
1088
1083
  @objc func getCallStatus(_ call: CAPPluginCall) {
@@ -9,7 +9,6 @@ class TouchInterceptView: UIView {
9
9
  private var lastTouchPoint: CGPoint?
10
10
  private let touchThreshold: CGFloat = 5.0 // pixels
11
11
  private let timerDelay: TimeInterval = 0.1 // seconds
12
- private var isCallActive: Bool = false
13
12
  private var hasActiveCallCheck: (() -> Bool)?
14
13
 
15
14
  func setupWithWebView(_ webView: UIView, overlayView: UIView) {
@@ -26,28 +25,10 @@ class TouchInterceptView: UIView {
26
25
  self.hasActiveCallCheck = check
27
26
  }
28
27
 
29
- func setCallActive(_ active: Bool) {
30
- self.isCallActive = active
31
- // os_log(.debug, "TouchInterceptView: setCallActive - %{public}s", String(describing: active))
32
-
33
- // Cancel any pending timer when call becomes inactive
34
- if !active {
35
- forwardTimer?.invalidate()
36
- forwardTimer = nil
37
- lastTouchPoint = nil
38
- }
39
- }
40
-
41
28
  private func shouldInterceptTouches() -> Bool {
42
- // Check both our flag and actual call state
29
+ // Check if there's an active call
43
30
  let hasActiveCall = hasActiveCallCheck?() ?? false
44
- let shouldIntercept = isCallActive && hasActiveCall
45
-
46
- if isCallActive != hasActiveCall {
47
- // os_log(.debug, "TouchInterceptView: State mismatch - isCallActive: %{public}s, hasActiveCall: %{public}s", String(describing: isCallActive), String(describing: hasActiveCall))
48
- }
49
-
50
- return shouldIntercept
31
+ return hasActiveCall
51
32
  }
52
33
 
53
34
  private func isInteractive(_ view: UIView) -> Bool {
@@ -130,16 +111,21 @@ class TouchInterceptView: UIView {
130
111
 
131
112
  // Check if we should intercept touches
132
113
  if !shouldInterceptTouches() {
114
+ // Cancel any pending timer when not in a call
115
+ forwardTimer?.invalidate()
116
+ forwardTimer = nil
117
+ lastTouchPoint = nil
118
+
133
119
  if let webView = self.webView {
134
120
  let webPoint = self.convert(point, to: webView)
135
121
  let result = webView.hitTest(webPoint, with: event)
136
- // os_log(.debug, "TouchInterceptView: hitTest - Not intercepting, direct WebView result %{public}s at %{public}s", String(describing: result), String(describing: webPoint))
122
+ print("TouchInterceptView: hitTest - Not intercepting (no active call), direct WebView result \(String(describing: result)) at \(webPoint)")
137
123
  return result
138
124
  }
139
125
  return nil
140
126
  }
141
127
 
142
- // os_log(.debug, "TouchInterceptView: hitTest entry at %{public}s, callActive: %{public}s", String(describing: point), String(describing: isCallActive))
128
+ print("TouchInterceptView: hitTest intercepting touch at \(point)")
143
129
 
144
130
  // Check if this is same touch location continuing
145
131
  if let lastPoint = lastTouchPoint {
@@ -167,7 +153,7 @@ class TouchInterceptView: UIView {
167
153
  if let overlayView = self.overlayView, !overlayView.isHidden {
168
154
  let overlayPoint = self.convert(point, to: overlayView)
169
155
  if let overlayHit = nonGreedyInteractiveHitTest(in: overlayView, point: overlayPoint, with: event) {
170
- // os_log(.debug, "TouchInterceptView: hitTest - Overlay view %{public}s at %{public}s", String(describing: overlayHit), String(describing: overlayPoint))
156
+ print("TouchInterceptView: hitTest - Hit overlay view \(String(describing: overlayHit)) at \(overlayPoint)")
171
157
  return overlayHit
172
158
  }
173
159
  }
@@ -175,10 +161,10 @@ class TouchInterceptView: UIView {
175
161
  if let webView = self.webView {
176
162
  let webPoint = self.convert(point, to: webView)
177
163
  let result = webView.hitTest(webPoint, with: event)
178
- // os_log(.debug, "TouchInterceptView: hitTest - WebView result %{public}s at %{public}s", String(describing: result), String(describing: webPoint))
164
+ print("TouchInterceptView: hitTest - WebView fallback result \(String(describing: result)) at \(webPoint)")
179
165
  return result
180
166
  }
181
- // os_log(.debug, "TouchInterceptView: hitTest - No view found for %{public}s", String(describing: point))
167
+ print("TouchInterceptView: hitTest - No view found for \(point)")
182
168
  return nil
183
169
  }
184
170
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-stream-call",
3
- "version": "0.0.96",
3
+ "version": "7.0.2",
4
4
  "description": "Uses the https://getstream.io/ SDK to implement calling in Capacitor",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",