@capgo/capacitor-stream-call 0.0.19 → 0.0.20
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/ios/Sources/StreamCallPlugin/CallOverlayView.swift +10 -70
- package/ios/Sources/StreamCallPlugin/ParticipantsView.swift +0 -26
- package/ios/Sources/StreamCallPlugin/StreamCallPlugin.swift +256 -282
- package/package.json +1 -1
- package/ios/Sources/StreamCallPlugin/TouchInterceptView.swift +0 -177
|
@@ -3,53 +3,6 @@ import StreamVideo
|
|
|
3
3
|
import StreamVideoSwiftUI
|
|
4
4
|
import Combine
|
|
5
5
|
|
|
6
|
-
class CallOverlayViewModel: ObservableObject {
|
|
7
|
-
@Published var streamVideo: StreamVideo?
|
|
8
|
-
@Published var call: Call?
|
|
9
|
-
@Published var callState: CallState?
|
|
10
|
-
@Published var viewModel: CallViewModel?
|
|
11
|
-
@Published var participants: [CallParticipant] = []
|
|
12
|
-
|
|
13
|
-
private var participantsSubscription: AnyCancellable?
|
|
14
|
-
|
|
15
|
-
init(streamVideo: StreamVideo?) {
|
|
16
|
-
self.streamVideo = streamVideo
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
@MainActor
|
|
20
|
-
func updateCall(_ call: Call?) {
|
|
21
|
-
self.call = call
|
|
22
|
-
// Clean up previous subscription if any
|
|
23
|
-
participantsSubscription?.cancel()
|
|
24
|
-
|
|
25
|
-
if let call = call {
|
|
26
|
-
participantsSubscription = call.state.$participants.sink { [weak self] participants in
|
|
27
|
-
print("Participants update \(participants.map { $0.name })")
|
|
28
|
-
self?.participants = participants
|
|
29
|
-
}
|
|
30
|
-
self.callState = call.state
|
|
31
|
-
participantsSubscription = call.state.$callSettings.sink { [weak self] callSettings in
|
|
32
|
-
print("Call settings update")
|
|
33
|
-
self?.viewModel = CallViewModel(callSettings: callSettings)
|
|
34
|
-
self?.viewModel?.setActiveCall(call)
|
|
35
|
-
}
|
|
36
|
-
} else {
|
|
37
|
-
// Clear participants when call ends
|
|
38
|
-
self.participants = []
|
|
39
|
-
self.callState = nil
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
@MainActor
|
|
44
|
-
func updateStreamVideo(_ streamVideo: StreamVideo?) {
|
|
45
|
-
self.streamVideo = streamVideo
|
|
46
|
-
if streamVideo == nil {
|
|
47
|
-
self.call = nil
|
|
48
|
-
self.callState = nil
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
6
|
class CallOverlayViewFactory: ViewFactory {
|
|
54
7
|
// ... existing ViewFactory methods ...
|
|
55
8
|
func makeVideoParticipantView(
|
|
@@ -73,26 +26,22 @@ class CallOverlayViewFactory: ViewFactory {
|
|
|
73
26
|
}
|
|
74
27
|
|
|
75
28
|
struct CallOverlayView: View {
|
|
76
|
-
@ObservedObject var viewModel:
|
|
29
|
+
@ObservedObject var viewModel: CallViewModel
|
|
77
30
|
@State private var safeAreaInsets: EdgeInsets = .init()
|
|
78
31
|
private let viewFactory: CallOverlayViewFactory
|
|
79
32
|
|
|
80
|
-
init(viewModel:
|
|
33
|
+
init(viewModel: CallViewModel) {
|
|
81
34
|
self.viewModel = viewModel
|
|
82
35
|
self.viewFactory = CallOverlayViewFactory()
|
|
83
36
|
}
|
|
84
37
|
|
|
85
38
|
var body: some View {
|
|
86
39
|
VStack(spacing: 0) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
CustomCallView(viewFactory: viewFactory, viewModel: viewModelStandard)
|
|
90
|
-
}
|
|
91
|
-
.padding(.top, safeAreaInsets.top)
|
|
92
|
-
.padding(.bottom, safeAreaInsets.bottom)
|
|
93
|
-
} else {
|
|
94
|
-
Color.white
|
|
40
|
+
ZStack {
|
|
41
|
+
CustomCallView(viewFactory: viewFactory, viewModel: viewModel)
|
|
95
42
|
}
|
|
43
|
+
.padding(.top, safeAreaInsets.top)
|
|
44
|
+
.padding(.bottom, safeAreaInsets.bottom)
|
|
96
45
|
}
|
|
97
46
|
.edgesIgnoringSafeArea(.all)
|
|
98
47
|
.overlay(
|
|
@@ -108,7 +57,7 @@ struct CallOverlayView: View {
|
|
|
108
57
|
}
|
|
109
58
|
|
|
110
59
|
private func changeTrackVisibility(_ participant: CallParticipant?, isVisible: Bool) {
|
|
111
|
-
print("changeTrackVisibility for \(participant?.userId), visible: \(isVisible)")
|
|
60
|
+
print("changeTrackVisibility for \(String(describing: participant?.userId)), visible: \(isVisible)")
|
|
112
61
|
guard let participant = participant,
|
|
113
62
|
let call = viewModel.call else { return }
|
|
114
63
|
Task {
|
|
@@ -118,26 +67,17 @@ struct CallOverlayView: View {
|
|
|
118
67
|
}
|
|
119
68
|
|
|
120
69
|
extension CallOverlayView {
|
|
121
|
-
static func create(
|
|
122
|
-
let
|
|
123
|
-
let view = CallOverlayView(viewModel: viewModel)
|
|
70
|
+
static func create(callViewModel: CallViewModel) -> UIHostingController<CallOverlayView> {
|
|
71
|
+
let view = CallOverlayView(viewModel: callViewModel)
|
|
124
72
|
let hostingController = UIHostingController(rootView: view)
|
|
125
73
|
hostingController.view.backgroundColor = .clear
|
|
126
74
|
|
|
127
75
|
// Make sure we respect safe areas
|
|
128
76
|
hostingController.view.insetsLayoutMarginsFromSafeArea = true
|
|
129
77
|
|
|
130
|
-
return (hostingController
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
#if DEBUG
|
|
135
|
-
struct CallOverlayView_Previews: PreviewProvider {
|
|
136
|
-
static var previews: some View {
|
|
137
|
-
CallOverlayView(viewModel: CallOverlayViewModel(streamVideo: nil))
|
|
78
|
+
return (hostingController)
|
|
138
79
|
}
|
|
139
80
|
}
|
|
140
|
-
#endif
|
|
141
81
|
|
|
142
82
|
struct SafeAreaInsetsKey: PreferenceKey {
|
|
143
83
|
static var defaultValue: EdgeInsets = .init()
|
|
@@ -50,18 +50,6 @@ struct ParticipantsView: View {
|
|
|
50
50
|
var localParticipant: CallParticipant
|
|
51
51
|
@State private var labeledFrames: [ViewFramePreferenceData] = []
|
|
52
52
|
|
|
53
|
-
private func findTouchInterceptView() -> TouchInterceptView? {
|
|
54
|
-
// Find the TouchInterceptView by traversing up the view hierarchy
|
|
55
|
-
var currentView = UIApplication.shared.windows.first?.rootViewController?.view
|
|
56
|
-
while let view = currentView {
|
|
57
|
-
if let touchInterceptView = view as? TouchInterceptView {
|
|
58
|
-
return touchInterceptView
|
|
59
|
-
}
|
|
60
|
-
currentView = view.superview
|
|
61
|
-
}
|
|
62
|
-
return nil
|
|
63
|
-
}
|
|
64
|
-
|
|
65
53
|
var body: some View {
|
|
66
54
|
GeometryReader { proxy in
|
|
67
55
|
if !participants.isEmpty {
|
|
@@ -170,20 +158,6 @@ struct ParticipantsView: View {
|
|
|
170
158
|
}
|
|
171
159
|
}
|
|
172
160
|
}
|
|
173
|
-
.onPreferenceChange(ViewFramePreferenceKey.self) { frames in
|
|
174
|
-
print("ParticipantsView - Received frame updates:")
|
|
175
|
-
print("Number of frames: \(frames.count)")
|
|
176
|
-
frames.forEach { frame in
|
|
177
|
-
print("Label: \(frame.label), Frame: \(frame.frame)")
|
|
178
|
-
}
|
|
179
|
-
self.labeledFrames = frames
|
|
180
|
-
if let touchInterceptView = findTouchInterceptView() {
|
|
181
|
-
print("ParticipantsView - Found TouchInterceptView, updating frames")
|
|
182
|
-
touchInterceptView.updateLabeledFrames(frames)
|
|
183
|
-
} else {
|
|
184
|
-
print("ParticipantsView - Failed to find TouchInterceptView!")
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
161
|
} else {
|
|
188
162
|
Color.gray
|
|
189
163
|
}
|
|
@@ -41,20 +41,16 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
41
41
|
|
|
42
42
|
private var overlayView: UIView?
|
|
43
43
|
private var hostingController: UIHostingController<CallOverlayView>?
|
|
44
|
-
private var overlayViewModel: CallOverlayViewModel?
|
|
45
44
|
private var tokenSubscription: AnyCancellable?
|
|
46
45
|
private var activeCallSubscription: AnyCancellable?
|
|
47
46
|
private var lastVoIPToken: String?
|
|
48
|
-
private var touchInterceptView: TouchInterceptView?
|
|
49
47
|
|
|
50
48
|
private var streamVideo: StreamVideo?
|
|
51
49
|
|
|
52
|
-
// Track the current active call ID
|
|
53
|
-
private var currentActiveCallId: String?
|
|
54
|
-
|
|
55
50
|
// Store current call info for getCallStatus
|
|
56
51
|
private var currentCallId: String = ""
|
|
57
52
|
private var currentCallState: String = ""
|
|
53
|
+
private var hasNotifiedCallJoined: Bool = false
|
|
58
54
|
|
|
59
55
|
@Injected(\.callKitAdapter) var callKitAdapter
|
|
60
56
|
@Injected(\.callKitPushNotificationAdapter) var callKitPushNotificationAdapter
|
|
@@ -63,6 +59,9 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
63
59
|
// Add class property to store call states
|
|
64
60
|
private var callStates: [String: (members: [MemberResponse], participantResponses: [String: String], createdAt: Date, timer: Timer?)] = [:]
|
|
65
61
|
|
|
62
|
+
// Declare as optional and initialize in load() method
|
|
63
|
+
private var callViewModel: CallViewModel?
|
|
64
|
+
|
|
66
65
|
// Helper method to update call status and notify listeners
|
|
67
66
|
private func updateCallStatusAndNotify(callId: String, state: String, userId: String? = nil, reason: String? = nil) {
|
|
68
67
|
// Update stored call info
|
|
@@ -95,7 +94,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
95
94
|
if self.apiKey == nil {
|
|
96
95
|
fatalError("Cannot get apikey")
|
|
97
96
|
}
|
|
98
|
-
|
|
97
|
+
|
|
99
98
|
// Check if we have a logged in user for handling incoming calls
|
|
100
99
|
if let credentials = SecureUserRepository.shared.loadCurrentUser() {
|
|
101
100
|
print("Loading user for StreamCallPlugin: \(credentials.user.name)")
|
|
@@ -118,32 +117,6 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
118
117
|
self.webView?.navigationDelegate = self.webviewDelegate
|
|
119
118
|
}
|
|
120
119
|
|
|
121
|
-
// private func cleanupStreamVideo() {
|
|
122
|
-
// // Cancel subscriptions
|
|
123
|
-
// tokenSubscription?.cancel()
|
|
124
|
-
// tokenSubscription = nil
|
|
125
|
-
// activeCallSubscription?.cancel()
|
|
126
|
-
// activeCallSubscription = nil
|
|
127
|
-
// lastVoIPToken = nil
|
|
128
|
-
//
|
|
129
|
-
// // Cleanup UI
|
|
130
|
-
// Task { @MainActor in
|
|
131
|
-
// self.overlayViewModel?.updateCall(nil)
|
|
132
|
-
// self.overlayViewModel?.updateStreamVideo(nil)
|
|
133
|
-
// self.overlayView?.removeFromSuperview()
|
|
134
|
-
// self.overlayView = nil
|
|
135
|
-
// self.hostingController = nil
|
|
136
|
-
// self.overlayViewModel = nil
|
|
137
|
-
//
|
|
138
|
-
// // Reset webview
|
|
139
|
-
// self.webView?.isOpaque = true
|
|
140
|
-
// self.webView?.backgroundColor = .white
|
|
141
|
-
// self.webView?.scrollView.backgroundColor = .white
|
|
142
|
-
// }
|
|
143
|
-
//
|
|
144
|
-
// state = .notInitialized
|
|
145
|
-
// }
|
|
146
|
-
|
|
147
120
|
private func requireInitialized() throws {
|
|
148
121
|
guard state == .initialized else {
|
|
149
122
|
throw NSError(domain: "StreamCallPlugin", code: -1, userInfo: [NSLocalizedDescriptionKey: "StreamVideo not initialized"])
|
|
@@ -192,204 +165,132 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
192
165
|
}
|
|
193
166
|
|
|
194
167
|
private func setupActiveCallSubscription() {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
if let rejectedEvent = event.rawValue as? CallRejectedEvent {
|
|
232
|
-
let userId = rejectedEvent.user.id
|
|
233
|
-
let callCid = rejectedEvent.callCid
|
|
234
|
-
|
|
235
|
-
// Operate on callStates on the main thread
|
|
236
|
-
await MainActor.run {
|
|
237
|
-
// Update the combined callStates map
|
|
238
|
-
if var callState = self.callStates[callCid] {
|
|
239
|
-
callState.participantResponses[userId] = "rejected"
|
|
240
|
-
self.callStates[callCid] = callState
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
print("CallRejectedEvent \(userId)")
|
|
245
|
-
updateCallStatusAndNotify(callId: callCid, state: "rejected", userId: userId)
|
|
246
|
-
|
|
247
|
-
await checkAllParticipantsResponded(callCid: callCid)
|
|
248
|
-
continue
|
|
168
|
+
// Ensure this method is called on the main thread and properly establishes the subscription
|
|
169
|
+
DispatchQueue.main.async { [weak self] in
|
|
170
|
+
guard let self = self else { return }
|
|
171
|
+
|
|
172
|
+
// Cancel existing subscription if any
|
|
173
|
+
self.activeCallSubscription?.cancel()
|
|
174
|
+
self.activeCallSubscription = nil
|
|
175
|
+
|
|
176
|
+
// Verify callViewModel exists
|
|
177
|
+
guard let callViewModel = self.callViewModel, let streamVideo = self.streamVideo else {
|
|
178
|
+
print("Warning: setupActiveCallSubscription called but callViewModel or streamVideo is nil")
|
|
179
|
+
// Schedule a retry after a short delay if callViewModel is nil
|
|
180
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { [weak self] in
|
|
181
|
+
self?.setupActiveCallSubscription()
|
|
182
|
+
}
|
|
183
|
+
return
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
print("Setting up active call subscription")
|
|
187
|
+
|
|
188
|
+
// Create a strong reference to callViewModel to ensure it's not deallocated
|
|
189
|
+
// while the subscription is active
|
|
190
|
+
let viewModel = callViewModel
|
|
191
|
+
|
|
192
|
+
// Subscribe to streamVideo.state.$activeCall to handle CallKit integration
|
|
193
|
+
let callPublisher = streamVideo.state.$activeCall
|
|
194
|
+
.receive(on: DispatchQueue.main)
|
|
195
|
+
.sink { [weak self, weak viewModel] activeCall in
|
|
196
|
+
guard let self = self, let viewModel = viewModel else { return }
|
|
197
|
+
|
|
198
|
+
print("Active call update from streamVideo: \(String(describing: activeCall?.cId))")
|
|
199
|
+
|
|
200
|
+
if let activeCall = activeCall {
|
|
201
|
+
// Sync callViewModel with activeCall from streamVideo state
|
|
202
|
+
// This ensures CallKit integration works properly
|
|
203
|
+
viewModel.setActiveCall(activeCall)
|
|
249
204
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
print("CallMissedEvent \(userId)")
|
|
265
|
-
updateCallStatusAndNotify(callId: callCid, state: "missed", userId: userId)
|
|
266
|
-
|
|
267
|
-
await checkAllParticipantsResponded(callCid: callCid)
|
|
268
|
-
continue
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Store the subscription for activeCall updates
|
|
208
|
+
self.activeCallSubscription = callPublisher
|
|
209
|
+
|
|
210
|
+
// Additionally, subscribe to callingState for other call state changes
|
|
211
|
+
let statePublisher = viewModel.$callingState
|
|
212
|
+
.receive(on: DispatchQueue.main)
|
|
213
|
+
.sink { [weak self, weak viewModel] newState in
|
|
214
|
+
guard let self = self, let viewModel = viewModel else {
|
|
215
|
+
print("Warning: Call state update received but self or viewModel is nil")
|
|
216
|
+
return
|
|
269
217
|
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
if call.
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
}
|
|
288
|
-
}) else {
|
|
289
|
-
print("CallSessionParticipantLeftEvent no participantsCount")
|
|
290
|
-
continue
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
if participantsCount - 1 <= 1 {
|
|
294
|
-
|
|
295
|
-
print("We are left solo in a call. Ending. cID: \(participantLeftEvent.callCid). participantsCount: \(participantsCount)")
|
|
296
|
-
|
|
297
|
-
Task {
|
|
298
|
-
if let activeCall = streamVideo.state.activeCall {
|
|
299
|
-
activeCall.leave()
|
|
300
|
-
} else {
|
|
301
|
-
print("Active call isn't the one?")
|
|
302
|
-
}
|
|
218
|
+
|
|
219
|
+
do {
|
|
220
|
+
try self.requireInitialized()
|
|
221
|
+
print("Call State Update: \(newState)")
|
|
222
|
+
|
|
223
|
+
if newState == .inCall {
|
|
224
|
+
print("- In call state detected")
|
|
225
|
+
print("- All participants: \(String(describing: viewModel.participants))")
|
|
226
|
+
|
|
227
|
+
// Create/update overlay and make visible when there's an active call
|
|
228
|
+
self.createCallOverlayView()
|
|
229
|
+
|
|
230
|
+
// Notify that a call has started - but only if we haven't notified for this call yet
|
|
231
|
+
if let callId = viewModel.call?.cId, !self.hasNotifiedCallJoined || callId != self.currentCallId {
|
|
232
|
+
print("Notifying call joined: \(callId)")
|
|
233
|
+
self.updateCallStatusAndNotify(callId: callId, state: "joined")
|
|
234
|
+
self.hasNotifiedCallJoined = true
|
|
303
235
|
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
236
|
+
} else if case .incoming(let incomingCall) = newState {
|
|
237
|
+
self.updateCallStatusAndNotify(callId: incomingCall.id, state: "ringing")
|
|
238
|
+
} else if newState == .idle && self.streamVideo?.state.activeCall == nil {
|
|
239
|
+
// Get the call ID that was active before the state changed
|
|
240
|
+
let endingCallId = viewModel.call?.cId
|
|
241
|
+
print("Call ending: \(String(describing: endingCallId))")
|
|
242
|
+
|
|
243
|
+
// Notify that call has ended - use the properly tracked call ID
|
|
244
|
+
self.updateCallStatusAndNotify(callId: endingCallId ?? "", state: "left")
|
|
245
|
+
|
|
246
|
+
// Reset notification flag when call ends
|
|
247
|
+
self.hasNotifiedCallJoined = false
|
|
248
|
+
|
|
249
|
+
// Clean up any resources for this call
|
|
250
|
+
if let callCid = endingCallId {
|
|
251
|
+
// Invalidate and remove the timer
|
|
252
|
+
self.callStates[callCid]?.timer?.invalidate()
|
|
253
|
+
|
|
254
|
+
// Remove call from callStates
|
|
255
|
+
self.callStates.removeValue(forKey: callCid)
|
|
256
|
+
|
|
257
|
+
print("Cleaned up resources for ended call: \(callCid)")
|
|
322
258
|
}
|
|
259
|
+
|
|
260
|
+
// Remove the call overlay view when not in a call
|
|
261
|
+
self.ensureViewRemoved()
|
|
323
262
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
updateCallStatusAndNotify(callId: callCid, state: "accepted", userId: userId)
|
|
327
|
-
continue
|
|
263
|
+
} catch {
|
|
264
|
+
log.error("Error handling call state update: \(String(describing: error))")
|
|
328
265
|
}
|
|
329
|
-
|
|
330
|
-
updateCallStatusAndNotify(callId: streamVideo.state.activeCall?.callId ?? "", state: event.type)
|
|
331
266
|
}
|
|
267
|
+
|
|
268
|
+
// Combine both publishers
|
|
269
|
+
self.activeCallSubscription = AnyCancellable {
|
|
270
|
+
callPublisher.cancel()
|
|
271
|
+
statePublisher.cancel()
|
|
332
272
|
}
|
|
273
|
+
|
|
274
|
+
print("Active call subscription setup completed")
|
|
275
|
+
|
|
276
|
+
// Schedule a periodic check to ensure subscription is active
|
|
277
|
+
self.scheduleSubscriptionCheck()
|
|
333
278
|
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Add a new method to periodically check and restore the subscription if needed
|
|
282
|
+
private func scheduleSubscriptionCheck() {
|
|
283
|
+
// Create a timer that checks the subscription every 5 seconds
|
|
284
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) { [weak self] in
|
|
338
285
|
guard let self = self else { return }
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
print("- state: \(state)")
|
|
348
|
-
print("- Session ID: \(state.sessionId)")
|
|
349
|
-
print("- All participants: \(String(describing: state.participants))")
|
|
350
|
-
print("- Remote participants: \(String(describing: state.remoteParticipants))")
|
|
351
|
-
|
|
352
|
-
// Store the active call ID when a call becomes active
|
|
353
|
-
self.currentActiveCallId = newState?.cId
|
|
354
|
-
print("Updated current active call ID: \(String(describing: self.currentActiveCallId))")
|
|
355
|
-
|
|
356
|
-
// Update overlay and make visible when there's an active call
|
|
357
|
-
self.overlayViewModel?.updateCall(newState)
|
|
358
|
-
self.overlayView?.isHidden = false
|
|
359
|
-
self.webView?.isOpaque = false
|
|
360
|
-
|
|
361
|
-
// Notify that a call has started
|
|
362
|
-
self.updateCallStatusAndNotify(callId: newState?.cId ?? "", state: "joined")
|
|
363
|
-
} else {
|
|
364
|
-
// Get the call ID that was active before the state changed to nil
|
|
365
|
-
let endingCallId = self.currentActiveCallId
|
|
366
|
-
print("Call ending: \(String(describing: endingCallId))")
|
|
367
|
-
|
|
368
|
-
// If newState is nil, hide overlay and clear call
|
|
369
|
-
self.overlayViewModel?.updateCall(nil)
|
|
370
|
-
self.overlayView?.isHidden = true
|
|
371
|
-
self.webView?.isOpaque = true
|
|
372
|
-
|
|
373
|
-
// Notify that call has ended - use the properly tracked call ID
|
|
374
|
-
self.updateCallStatusAndNotify(callId: endingCallId ?? "", state: "left")
|
|
375
|
-
|
|
376
|
-
// Clean up any resources for this call
|
|
377
|
-
if let callCid = endingCallId {
|
|
378
|
-
// Invalidate and remove the timer
|
|
379
|
-
self.callStates[callCid]?.timer?.invalidate()
|
|
380
|
-
|
|
381
|
-
// Remove call from callStates
|
|
382
|
-
self.callStates.removeValue(forKey: callCid)
|
|
383
|
-
|
|
384
|
-
print("Cleaned up resources for ended call: \(callCid)")
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
// Clear the active call ID
|
|
388
|
-
self.currentActiveCallId = nil
|
|
389
|
-
}
|
|
390
|
-
} catch {
|
|
391
|
-
log.error("Error handling call state update: \(String(describing: error))")
|
|
392
|
-
}
|
|
286
|
+
|
|
287
|
+
// Check if we're in a state where we need the subscription but it's not active
|
|
288
|
+
if self.state == .initialized && self.activeCallSubscription == nil && self.callViewModel != nil {
|
|
289
|
+
print("Subscription check: Restoring lost activeCallSubscription")
|
|
290
|
+
self.setupActiveCallSubscription()
|
|
291
|
+
} else {
|
|
292
|
+
// Schedule the next check
|
|
293
|
+
self.scheduleSubscriptionCheck()
|
|
393
294
|
}
|
|
394
295
|
}
|
|
395
296
|
}
|
|
@@ -469,7 +370,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
469
370
|
|
|
470
371
|
// Update UI
|
|
471
372
|
await MainActor.run {
|
|
472
|
-
self.overlayViewModel?.updateCall(nil)
|
|
373
|
+
// self.overlayViewModel?.updateCall(nil)
|
|
473
374
|
self.overlayView?.isHidden = true
|
|
474
375
|
self.webView?.isOpaque = true
|
|
475
376
|
self.updateCallStatusAndNotify(callId: callCid, state: "ended", reason: "timeout")
|
|
@@ -514,7 +415,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
514
415
|
// Remove from callStates
|
|
515
416
|
self.callStates.removeValue(forKey: callCid)
|
|
516
417
|
|
|
517
|
-
self.overlayViewModel?.updateCall(nil)
|
|
418
|
+
// self.overlayViewModel?.updateCall(nil)
|
|
518
419
|
self.overlayView?.isHidden = true
|
|
519
420
|
self.webView?.isOpaque = true
|
|
520
421
|
self.updateCallStatusAndNotify(callId: callCid, state: "ended", reason: "all_rejected_or_missed")
|
|
@@ -550,7 +451,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
550
451
|
|
|
551
452
|
// Update the CallOverlayView with new StreamVideo instance
|
|
552
453
|
Task { @MainActor in
|
|
553
|
-
self.overlayViewModel?.updateStreamVideo(self.streamVideo)
|
|
454
|
+
// self.overlayViewModel?.updateStreamVideo(self.streamVideo)
|
|
554
455
|
}
|
|
555
456
|
|
|
556
457
|
call.resolve([
|
|
@@ -584,8 +485,8 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
584
485
|
|
|
585
486
|
// Update the CallOverlayView with nil StreamVideo instance
|
|
586
487
|
Task { @MainActor in
|
|
587
|
-
self.overlayViewModel?.updateCall(nil)
|
|
588
|
-
self.overlayViewModel?.updateStreamVideo(nil)
|
|
488
|
+
// self.overlayViewModel?.updateCall(nil)
|
|
489
|
+
// self.overlayViewModel?.updateStreamVideo(nil)
|
|
589
490
|
self.overlayView?.isHidden = true
|
|
590
491
|
self.webView?.isOpaque = true
|
|
591
492
|
}
|
|
@@ -668,7 +569,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
668
569
|
|
|
669
570
|
// Update the CallOverlayView with the active call
|
|
670
571
|
await MainActor.run {
|
|
671
|
-
self.overlayViewModel?.updateCall(streamCall)
|
|
572
|
+
// self.overlayViewModel?.updateCall(streamCall)
|
|
672
573
|
self.overlayView?.isHidden = false
|
|
673
574
|
self.webView?.isOpaque = false
|
|
674
575
|
}
|
|
@@ -696,7 +597,7 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
696
597
|
|
|
697
598
|
// Update view state instead of cleaning up
|
|
698
599
|
await MainActor.run {
|
|
699
|
-
self.overlayViewModel?.updateCall(nil)
|
|
600
|
+
// self.overlayViewModel?.updateCall(nil)
|
|
700
601
|
self.overlayView?.isHidden = true
|
|
701
602
|
self.webView?.isOpaque = true
|
|
702
603
|
}
|
|
@@ -795,14 +696,17 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
795
696
|
|
|
796
697
|
// Join the call
|
|
797
698
|
print("Accepting and joining call \(streamCall!.cId)...")
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
699
|
+
guard case .incoming(let incomingCall) = await self.callViewModel?.callingState else {
|
|
700
|
+
call.reject("Failed to accept call as there is no call ID")
|
|
701
|
+
return
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
await self.callViewModel?.acceptCall(callType: incomingCall.type, callId: incomingCall.id)
|
|
801
705
|
print("Successfully joined call")
|
|
802
706
|
|
|
803
707
|
// Update the CallOverlayView with the active call
|
|
804
708
|
await MainActor.run {
|
|
805
|
-
self.overlayViewModel?.updateCall(streamCall)
|
|
709
|
+
// self.overlayViewModel?.updateCall(streamCall)
|
|
806
710
|
self.overlayView?.isHidden = false
|
|
807
711
|
self.webView?.isOpaque = false
|
|
808
712
|
}
|
|
@@ -821,6 +725,21 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
821
725
|
}
|
|
822
726
|
|
|
823
727
|
private func initializeStreamVideo() {
|
|
728
|
+
if (state == .initialized) {
|
|
729
|
+
print("initializeStreamVideo already initialized")
|
|
730
|
+
// Try to get user credentials from repository
|
|
731
|
+
guard let savedCredentials = SecureUserRepository.shared.loadCurrentUser() else {
|
|
732
|
+
print("Save credentials not found, skipping initialization")
|
|
733
|
+
return
|
|
734
|
+
}
|
|
735
|
+
if (savedCredentials.user.id == streamVideo?.user.id) {
|
|
736
|
+
print("Skipping initializeStreamVideo as user is already logged in")
|
|
737
|
+
return
|
|
738
|
+
}
|
|
739
|
+
} else if (state == .initializing) {
|
|
740
|
+
print("initializeStreamVideo rejected - already initializing")
|
|
741
|
+
return
|
|
742
|
+
}
|
|
824
743
|
state = .initializing
|
|
825
744
|
|
|
826
745
|
// Try to get user credentials from repository
|
|
@@ -832,11 +751,31 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
832
751
|
}
|
|
833
752
|
print("Initializing with saved credentials for user: \(savedCredentials.user.name)")
|
|
834
753
|
|
|
754
|
+
LogConfig.level = .debug
|
|
835
755
|
self.streamVideo = StreamVideo(
|
|
836
756
|
apiKey: apiKey,
|
|
837
757
|
user: savedCredentials.user,
|
|
838
|
-
token: UserToken(stringLiteral: savedCredentials.tokenValue)
|
|
758
|
+
token: UserToken(stringLiteral: savedCredentials.tokenValue),
|
|
759
|
+
tokenProvider: {completion in
|
|
760
|
+
guard let savedCredentials = SecureUserRepository.shared.loadCurrentUser() else {
|
|
761
|
+
print("No saved credentials or API key found, cannot refresh token")
|
|
762
|
+
|
|
763
|
+
completion(.failure(NSError(domain: "No saved credentials or API key found, cannot refresh token", code: 0, userInfo: nil)))
|
|
764
|
+
return
|
|
765
|
+
}
|
|
766
|
+
completion(.success(UserToken(stringLiteral: savedCredentials.tokenValue)))
|
|
767
|
+
}
|
|
839
768
|
)
|
|
769
|
+
|
|
770
|
+
if (self.callViewModel == nil) {
|
|
771
|
+
// Initialize on main thread with proper MainActor isolation
|
|
772
|
+
DispatchQueue.main.async {
|
|
773
|
+
Task { @MainActor in
|
|
774
|
+
self.callViewModel = CallViewModel(participantsLayout: .grid)
|
|
775
|
+
self.callViewModel?.participantAutoLeavePolicy = LastParticipantAutoLeavePolicy()
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
}
|
|
840
779
|
|
|
841
780
|
state = .initialized
|
|
842
781
|
callKitAdapter.streamVideo = self.streamVideo
|
|
@@ -851,63 +790,98 @@ public class StreamCallPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
851
790
|
}
|
|
852
791
|
|
|
853
792
|
private func setupViews() {
|
|
854
|
-
guard let webView = self.webView,
|
|
855
|
-
let parent = webView.superview else { return }
|
|
856
|
-
|
|
857
|
-
// Create TouchInterceptView
|
|
858
|
-
let touchInterceptView = TouchInterceptView(frame: parent.bounds)
|
|
859
|
-
touchInterceptView.translatesAutoresizingMaskIntoConstraints = false
|
|
860
|
-
self.touchInterceptView = touchInterceptView
|
|
861
793
|
|
|
862
|
-
// Remove webView from its parent
|
|
863
|
-
webView.removeFromSuperview()
|
|
864
794
|
|
|
865
|
-
//
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
795
|
+
// // Create SwiftUI view with view model
|
|
796
|
+
// let (hostingController, viewModel) = CallOverlayView.create(streamVideo: self.streamVideo)
|
|
797
|
+
// hostingController.view.backgroundColor = .clear
|
|
798
|
+
// hostingController.view.translatesAutoresizingMaskIntoConstraints = false
|
|
799
|
+
//
|
|
800
|
+
// self.hostingController = hostingController
|
|
801
|
+
// self.overlayViewModel = viewModel
|
|
802
|
+
// self.overlayView = hostingController.view
|
|
803
|
+
//
|
|
804
|
+
// if let overlayView = self.overlayView {
|
|
805
|
+
// // Setup the views in TouchInterceptView
|
|
806
|
+
// touchInterceptView.setupWithWebView(webView, overlayView: overlayView)
|
|
807
|
+
//
|
|
808
|
+
// // Setup constraints for webView
|
|
809
|
+
// NSLayoutConstraint.activate([
|
|
810
|
+
// webView.topAnchor.constraint(equalTo: touchInterceptView.topAnchor),
|
|
811
|
+
// webView.bottomAnchor.constraint(equalTo: touchInterceptView.bottomAnchor),
|
|
812
|
+
// webView.leadingAnchor.constraint(equalTo: touchInterceptView.leadingAnchor),
|
|
813
|
+
// webView.trailingAnchor.constraint(equalTo: touchInterceptView.trailingAnchor)
|
|
814
|
+
// ])
|
|
815
|
+
//
|
|
816
|
+
// // Setup constraints for overlayView
|
|
817
|
+
// let safeGuide = touchInterceptView.safeAreaLayoutGuide
|
|
818
|
+
// NSLayoutConstraint.activate([
|
|
819
|
+
// overlayView.topAnchor.constraint(equalTo: safeGuide.topAnchor),
|
|
820
|
+
// overlayView.bottomAnchor.constraint(equalTo: safeGuide.bottomAnchor),
|
|
821
|
+
// overlayView.leadingAnchor.constraint(equalTo: safeGuide.leadingAnchor),
|
|
822
|
+
// overlayView.trailingAnchor.constraint(equalTo: safeGuide.trailingAnchor)
|
|
823
|
+
// ])
|
|
824
|
+
// }
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
private func createCallOverlayView() {
|
|
828
|
+
guard let webView = self.webView,
|
|
829
|
+
let parent = webView.superview,
|
|
830
|
+
let callOverlayView = self.callViewModel else { return }
|
|
831
|
+
|
|
832
|
+
// Check if we already have an overlay view - do nothing if it exists
|
|
833
|
+
if let existingOverlayView = self.overlayView, existingOverlayView.superview != nil {
|
|
834
|
+
print("Call overlay view already exists, doing nothing")
|
|
835
|
+
return
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
print("Creating new call overlay view")
|
|
839
|
+
|
|
840
|
+
// First, create the overlay view
|
|
841
|
+
let overlayView = CallOverlayView.create(callViewModel: callOverlayView)
|
|
842
|
+
overlayView.view.translatesAutoresizingMaskIntoConstraints = false
|
|
843
|
+
|
|
844
|
+
// Important: Insert the overlay view BELOW the webView in the view hierarchy
|
|
845
|
+
parent.insertSubview(overlayView.view, belowSubview: webView)
|
|
846
|
+
|
|
847
|
+
// Set constraints to fill the parent's safe area
|
|
848
|
+
let safeGuide = parent.safeAreaLayoutGuide
|
|
849
|
+
|
|
869
850
|
NSLayoutConstraint.activate([
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
851
|
+
overlayView.view.topAnchor.constraint(equalTo: safeGuide.topAnchor),
|
|
852
|
+
overlayView.view.bottomAnchor.constraint(equalTo: safeGuide.bottomAnchor),
|
|
853
|
+
overlayView.view.leadingAnchor.constraint(equalTo: safeGuide.leadingAnchor),
|
|
854
|
+
overlayView.view.trailingAnchor.constraint(equalTo: safeGuide.trailingAnchor)
|
|
874
855
|
])
|
|
875
|
-
|
|
876
|
-
//
|
|
877
|
-
webView.isOpaque =
|
|
856
|
+
|
|
857
|
+
// Set opacity for visual effect - make webView transparent to see overlay
|
|
858
|
+
webView.isOpaque = false
|
|
878
859
|
webView.backgroundColor = .clear
|
|
879
860
|
webView.scrollView.backgroundColor = .clear
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
hostingController.view.translatesAutoresizingMaskIntoConstraints = false
|
|
886
|
-
|
|
887
|
-
self.hostingController = hostingController
|
|
888
|
-
self.overlayViewModel = viewModel
|
|
889
|
-
self.overlayView = hostingController.view
|
|
890
|
-
|
|
891
|
-
if let overlayView = self.overlayView {
|
|
892
|
-
// Setup the views in TouchInterceptView
|
|
893
|
-
touchInterceptView.setupWithWebView(webView, overlayView: overlayView)
|
|
894
|
-
|
|
895
|
-
// Setup constraints for webView
|
|
896
|
-
NSLayoutConstraint.activate([
|
|
897
|
-
webView.topAnchor.constraint(equalTo: touchInterceptView.topAnchor),
|
|
898
|
-
webView.bottomAnchor.constraint(equalTo: touchInterceptView.bottomAnchor),
|
|
899
|
-
webView.leadingAnchor.constraint(equalTo: touchInterceptView.leadingAnchor),
|
|
900
|
-
webView.trailingAnchor.constraint(equalTo: touchInterceptView.trailingAnchor)
|
|
901
|
-
])
|
|
861
|
+
|
|
862
|
+
// Store reference to the hosting controller
|
|
863
|
+
self.hostingController = overlayView
|
|
864
|
+
self.overlayView = overlayView.view
|
|
865
|
+
}
|
|
902
866
|
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
867
|
+
private func ensureViewRemoved() {
|
|
868
|
+
// Check if we have an overlay view
|
|
869
|
+
if let existingOverlayView = self.overlayView {
|
|
870
|
+
print("Removing call overlay view")
|
|
871
|
+
|
|
872
|
+
// Remove the view from its superview
|
|
873
|
+
existingOverlayView.removeFromSuperview()
|
|
874
|
+
|
|
875
|
+
// Reset opacity for webView
|
|
876
|
+
self.webView?.isOpaque = true
|
|
877
|
+
self.webView?.backgroundColor = nil
|
|
878
|
+
self.webView?.scrollView.backgroundColor = nil
|
|
879
|
+
|
|
880
|
+
// Clear references
|
|
881
|
+
self.overlayView = nil
|
|
882
|
+
self.hostingController = nil
|
|
883
|
+
} else {
|
|
884
|
+
print("No call overlay view to remove")
|
|
911
885
|
}
|
|
912
886
|
}
|
|
913
887
|
|
package/package.json
CHANGED
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
import UIKit
|
|
2
|
-
|
|
3
|
-
class TouchInterceptView: UIView {
|
|
4
|
-
private var webView: UIView?
|
|
5
|
-
private var overlayView: UIView?
|
|
6
|
-
private var labeledFrames: [ViewFramePreferenceData] = []
|
|
7
|
-
|
|
8
|
-
override init(frame: CGRect) {
|
|
9
|
-
super.init(frame: frame)
|
|
10
|
-
isUserInteractionEnabled = true
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
required init?(coder: NSCoder) {
|
|
14
|
-
super.init(coder: coder)
|
|
15
|
-
isUserInteractionEnabled = true
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
func setupWithWebView(_ webView: UIView, overlayView: UIView) {
|
|
19
|
-
self.webView = webView
|
|
20
|
-
self.overlayView = overlayView
|
|
21
|
-
|
|
22
|
-
// Add both views as subviews
|
|
23
|
-
addSubview(overlayView)
|
|
24
|
-
addSubview(webView)
|
|
25
|
-
|
|
26
|
-
// Ensure both views can receive touches
|
|
27
|
-
webView.isUserInteractionEnabled = true
|
|
28
|
-
overlayView.isUserInteractionEnabled = true
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
|
|
32
|
-
guard let webView = webView,
|
|
33
|
-
let overlayView = overlayView else {
|
|
34
|
-
super.touchesBegan(touches, with: event)
|
|
35
|
-
return
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// Convert touch locations and check labeled frames
|
|
39
|
-
if let touch = touches.first {
|
|
40
|
-
let point = touch.location(in: self)
|
|
41
|
-
let globalPoint = convert(point, to: nil)
|
|
42
|
-
|
|
43
|
-
// If touch is in a labeled frame, only send to overlay
|
|
44
|
-
if labeledFrames.contains(where: { $0.frame.contains(globalPoint) }) {
|
|
45
|
-
overlayView.touchesBegan(touches, with: event)
|
|
46
|
-
} else {
|
|
47
|
-
// Otherwise broadcast to both views
|
|
48
|
-
webView.touchesBegan(touches, with: event)
|
|
49
|
-
overlayView.touchesBegan(touches, with: event)
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
super.touchesBegan(touches, with: event)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
|
|
57
|
-
guard let webView = webView,
|
|
58
|
-
let overlayView = overlayView else {
|
|
59
|
-
super.touchesMoved(touches, with: event)
|
|
60
|
-
return
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if let touch = touches.first {
|
|
64
|
-
let point = touch.location(in: self)
|
|
65
|
-
let globalPoint = convert(point, to: nil)
|
|
66
|
-
|
|
67
|
-
if labeledFrames.contains(where: { $0.frame.contains(globalPoint) }) {
|
|
68
|
-
overlayView.touchesMoved(touches, with: event)
|
|
69
|
-
} else {
|
|
70
|
-
webView.touchesMoved(touches, with: event)
|
|
71
|
-
overlayView.touchesMoved(touches, with: event)
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
super.touchesMoved(touches, with: event)
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
|
|
79
|
-
guard let webView = webView,
|
|
80
|
-
let overlayView = overlayView else {
|
|
81
|
-
super.touchesEnded(touches, with: event)
|
|
82
|
-
return
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if let touch = touches.first {
|
|
86
|
-
let point = touch.location(in: self)
|
|
87
|
-
let globalPoint = convert(point, to: nil)
|
|
88
|
-
|
|
89
|
-
if labeledFrames.contains(where: { $0.frame.contains(globalPoint) }) {
|
|
90
|
-
overlayView.touchesEnded(touches, with: event)
|
|
91
|
-
} else {
|
|
92
|
-
webView.touchesEnded(touches, with: event)
|
|
93
|
-
overlayView.touchesEnded(touches, with: event)
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
super.touchesEnded(touches, with: event)
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
|
|
101
|
-
guard let webView = webView,
|
|
102
|
-
let overlayView = overlayView else {
|
|
103
|
-
super.touchesCancelled(touches, with: event)
|
|
104
|
-
return
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
if let touch = touches.first {
|
|
108
|
-
let point = touch.location(in: self)
|
|
109
|
-
let globalPoint = convert(point, to: nil)
|
|
110
|
-
|
|
111
|
-
if labeledFrames.contains(where: { $0.frame.contains(globalPoint) }) {
|
|
112
|
-
overlayView.touchesCancelled(touches, with: event)
|
|
113
|
-
} else {
|
|
114
|
-
webView.touchesCancelled(touches, with: event)
|
|
115
|
-
overlayView.touchesCancelled(touches, with: event)
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
super.touchesCancelled(touches, with: event)
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
|
|
123
|
-
return true
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
|
127
|
-
guard let webView = webView,
|
|
128
|
-
let overlayView = overlayView else {
|
|
129
|
-
return super.hitTest(point, with: event)
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Convert point to global coordinates for labeled frame checking
|
|
133
|
-
let globalPoint = convert(point, to: nil)
|
|
134
|
-
|
|
135
|
-
// print("TouchInterceptView - Hit test at global point: \(globalPoint)")
|
|
136
|
-
// print("Current labeled frames: \(labeledFrames.map { "\($0.label): \($0.frame)" }.joined(separator: ", "))")
|
|
137
|
-
|
|
138
|
-
// Convert point for both views
|
|
139
|
-
let webViewPoint = convert(point, to: webView)
|
|
140
|
-
let overlayPoint = convert(point, to: overlayView)
|
|
141
|
-
|
|
142
|
-
// First check if the point is inside any labeled frame
|
|
143
|
-
for labeledFrame in labeledFrames {
|
|
144
|
-
if labeledFrame.frame.contains(globalPoint) {
|
|
145
|
-
// print("Hit labeled frame: \(labeledFrame.label)")
|
|
146
|
-
// If it's in a labeled frame, let the overlay handle it
|
|
147
|
-
if overlayView.point(inside: overlayPoint, with: event),
|
|
148
|
-
let overlayHitView = overlayView.hitTest(overlayPoint, with: event) {
|
|
149
|
-
return overlayHitView
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// If not in a labeled frame, let webview try first
|
|
155
|
-
if webView.point(inside: webViewPoint, with: event),
|
|
156
|
-
let webViewHitView = webView.hitTest(webViewPoint, with: event) {
|
|
157
|
-
return webViewHitView
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// Finally check if overlay wants to handle the touch
|
|
161
|
-
if overlayView.point(inside: overlayPoint, with: event),
|
|
162
|
-
let overlayHitView = overlayView.hitTest(overlayPoint, with: event) {
|
|
163
|
-
return overlayHitView
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
return super.hitTest(point, with: event)
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
func updateLabeledFrames(_ frames: [ViewFramePreferenceData]) {
|
|
170
|
-
print("TouchInterceptView - Updating labeled frames:")
|
|
171
|
-
print("Number of frames: \(frames.count)")
|
|
172
|
-
frames.forEach { frame in
|
|
173
|
-
print("Label: \(frame.label), Frame: \(frame.frame)")
|
|
174
|
-
}
|
|
175
|
-
self.labeledFrames = frames
|
|
176
|
-
}
|
|
177
|
-
}
|