Skip to content

Commit

Permalink
Small improvements around callEnded and runtime warnings (#385)
Browse files Browse the repository at this point in the history
  • Loading branch information
ipavlidakis authored May 10, 2024
1 parent e399e44 commit 405d0f4
Show file tree
Hide file tree
Showing 12 changed files with 191 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -168,11 +168,12 @@ fileprivate func content() {

container {
struct CallContainer: View {
@Injected(\.streamVideo) var streamVideo

var body: some View {
YourRootView()
.modifier(CallModifier(viewModel: viewModel))
.onCallEnded { call, dismiss in
.onCallEnded(presentationValidator: { $0?.state.createdBy?.id == streamVideo.user.id }) { call, dismiss in
if let call {
DemoFeedbackView(call, dismiss: dismiss)
}
Expand Down
26 changes: 20 additions & 6 deletions Sources/StreamVideo/Call.swift
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,9 @@ public class Call: @unchecked Sendable, WSEventsSubscriber {
await state.update(from: response)
let updated = await state.callSettings
updateCallSettingsManagers(with: updated)
streamVideo.state.activeCall = self
Task { @MainActor in
streamVideo.state.activeCall = self
}
return response
})
}
Expand All @@ -141,7 +143,9 @@ public class Call: @unchecked Sendable, WSEventsSubscriber {
)
await state.update(from: response)
if ring {
streamVideo.state.ringingCall = self
Task { @MainActor in
streamVideo.state.ringingCall = self
}
}
return response
}
Expand Down Expand Up @@ -199,7 +203,9 @@ public class Call: @unchecked Sendable, WSEventsSubscriber {
)
await state.update(from: response)
if ring {
streamVideo.state.ringingCall = self
Task { @MainActor in
streamVideo.state.ringingCall = self
}
}
return response.call
}
Expand All @@ -226,7 +232,9 @@ public class Call: @unchecked Sendable, WSEventsSubscriber {
public func reject() async throws -> RejectCallResponse {
let response = try await coordinatorClient.rejectCall(type: callType, id: callId)
if streamVideo.state.ringingCall?.cId == cId {
streamVideo.state.ringingCall = nil
Task { @MainActor in
streamVideo.state.ringingCall = nil
}
}
return response
}
Expand Down Expand Up @@ -361,8 +369,14 @@ public class Call: @unchecked Sendable, WSEventsSubscriber {
cancellables.removeAll()
eventHandlers.removeAll()
callController.cleanUp()
streamVideo.state.ringingCall = nil
streamVideo.state.activeCall = nil
Task { @MainActor in
if streamVideo.state.ringingCall?.cId == cId {
streamVideo.state.ringingCall = nil
}
if streamVideo.state.activeCall?.cId == cId {
streamVideo.state.activeCall = nil
}
}
}

/// Starts noise cancellation asynchronously.
Expand Down
24 changes: 16 additions & 8 deletions Sources/StreamVideo/CallKit/CallKitService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,12 @@ open class CallKitService: NSObject, CXProviderDelegate, @unchecked Sendable {
let call = streamVideo.call(callType: callType, callId: callId)
let callState = try await call.get()

if streamVideo.state.ringingCall?.cId != call.cId {
Task { @MainActor in
streamVideo.state.ringingCall = call
}
}

if !checkIfCallWasHandled(callState: callState), state == .idle {
setUpRingingTimer(for: callState)
state = .joining
Expand Down Expand Up @@ -233,8 +239,8 @@ open class CallKitService: NSObject, CXProviderDelegate, @unchecked Sendable {
do {
log.debug("Answering VoIP incoming call with callId:\(callId) callType:\(callType).")
call = streamVideo.call(callType: callType, callId: callId)
try await call?.accept()
try await call?.join()
try await call?.accept()
state = .inCall
action.fulfill()
} catch {
Expand Down Expand Up @@ -272,7 +278,7 @@ open class CallKitService: NSObject, CXProviderDelegate, @unchecked Sendable {
///
/// - Parameter transaction: The transaction to be requested.
/// - Throws: An error if the request fails.
public func requestTransaction(
open func requestTransaction(
_ action: CXAction
) async throws {
try await callController.requestTransaction(with: action)
Expand All @@ -282,7 +288,7 @@ open class CallKitService: NSObject, CXProviderDelegate, @unchecked Sendable {
///
/// - Parameter callState: The state of the call.
/// - Returns: A boolean value indicating whether the call was handled.
public func checkIfCallWasHandled(callState: GetCallResponse) -> Bool {
open func checkIfCallWasHandled(callState: GetCallResponse) -> Bool {
guard let streamVideo else {
log.warning("CallKit operation:\(#function) cannot be fulfilled because StreamVideo is nil.")
return false
Expand All @@ -300,7 +306,7 @@ open class CallKitService: NSObject, CXProviderDelegate, @unchecked Sendable {
/// Sets up a ringing timer for the call.
///
/// - Parameter callState: The state of the call.
public func setUpRingingTimer(for callState: GetCallResponse) {
open func setUpRingingTimer(for callState: GetCallResponse) {
createdBy = callState.call.createdBy.toUser
let timeout = TimeInterval(callState.call.settings.ring.autoCancelTimeoutMs / 1000)
ringingTimerCancellable = Foundation.Timer.publish(
Expand All @@ -315,6 +321,12 @@ open class CallKitService: NSObject, CXProviderDelegate, @unchecked Sendable {
}
}

/// A method that's being called every time the StreamVideo instance is getting updated.
/// - Parameter streamVideo: The new StreamVideo instance (nil if none)
open func didUpdate(_ streamVideo: StreamVideo?) {
subscribeToCallEvents()
}

// MARK: - Private helpers

private func subscribeToCallEvents() {
Expand Down Expand Up @@ -382,10 +394,6 @@ open class CallKitService: NSObject, CXProviderDelegate, @unchecked Sendable {

return update
}

private func didUpdate(_ streamVideo: StreamVideo?) {
subscribeToCallEvents()
}
}

extension CallKitService: InjectionKey {
Expand Down
6 changes: 4 additions & 2 deletions Sources/StreamVideo/StreamVideo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ public class StreamVideo: ObservableObject, @unchecked Sendable {
oldValue?.leave()
}
if ringingCall != nil {
ringingCall = nil
Task { @MainActor in
ringingCall = nil
}
}
}
}
Expand Down Expand Up @@ -654,8 +656,8 @@ extension StreamVideo: WSEventsSubscriber {
)
executeOnMain {
call.state.update(from: ringEvent)
self.state.ringingCall = call
}
self.state.ringingCall = call
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ open class StreamAudioFilterProcessingModule: RTCDefaultAudioProcessingModule, A
capturePostProcessingDelegate: AudioFilterCapturePostProcessingModule = StreamAudioFilterCapturePostProcessingModule(),
renderPreProcessingDelegate: RTCAudioCustomProcessingDelegate? = nil
) {
// #if STREAM_TESTS
// assert(
// false,
// "\(type(of: self)) should not be used in Tests as it relies on the WebRTC stack being fully setup. Consider using `VideoConfig.dummy()` when initializing StreamVideo in tests or `MockAudioProcessingModule` if you need to use an instance directly."
// )
// #endif
_capturePostProcessingDelegate = capturePostProcessingDelegate
super.init(
config: config,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,16 @@ private final class CallEndedViewModifierViewModel: ObservableObject {
@available(iOS 14.0, *)
private struct CallEndedViewModifier<Subview: View>: ViewModifier {

private var presentationValidator: (Call?) -> Bool
private var subviewProvider: (Call?, @escaping () -> Void) -> Subview

@StateObject private var viewModel: CallEndedViewModifierViewModel

init(
presentationValidator: @escaping (Call?) -> Bool,
@ViewBuilder subviewProvider: @escaping (Call?, @escaping () -> Void) -> Subview
) {
self.presentationValidator = presentationValidator
self.subviewProvider = subviewProvider
_viewModel = .init(wrappedValue: .init())
}
Expand All @@ -46,6 +49,7 @@ private struct CallEndedViewModifier<Subview: View>: ViewModifier {
.sheet(isPresented: $viewModel.isPresentingSubview) {
subviewProvider(viewModel.lastCall) {
viewModel.lastCall = nil
viewModel.maxParticipantsCount = 0
viewModel.isPresentingSubview = false
}
}
Expand All @@ -56,7 +60,9 @@ private struct CallEndedViewModifier<Subview: View>: ViewModifier {
)

switch (call, viewModel.lastCall, viewModel.isPresentingSubview) {
case (nil, let activeCall, false) where activeCall != nil && viewModel.maxParticipantsCount > 1:
case (nil, let activeCall, false)
where activeCall != nil && viewModel
.maxParticipantsCount > 1 && presentationValidator(viewModel.lastCall):
/// The following presentation criteria are required:
/// - The activeCall was ended.
/// - Participants, during call's duration, grew to more than one.
Expand Down Expand Up @@ -99,14 +105,18 @@ private struct CallEndedViewModifier<Subview: View>: ViewModifier {
@available(iOS, introduced: 13, obsoleted: 14)
private struct CallEndedViewModifier_iOS13<Subview: View>: ViewModifier {

private var presentationValidator: (Call?) -> Bool
private var subviewProvider: (Call?, @escaping () -> Void) -> Subview

@BackportStateObject private var viewModel: CallEndedViewModifierViewModel = .init()
@BackportStateObject private var viewModel: CallEndedViewModifierViewModel

init(
presentationValidator: @escaping (Call?) -> Bool,
@ViewBuilder subviewProvider: @escaping (Call?, @escaping () -> Void) -> Subview
) {
self.presentationValidator = presentationValidator
self.subviewProvider = subviewProvider
_viewModel = .init(wrappedValue: .init())
}

func body(content: Content) -> some View {
Expand All @@ -124,7 +134,9 @@ private struct CallEndedViewModifier_iOS13<Subview: View>: ViewModifier {
)

switch (call, viewModel.lastCall, viewModel.isPresentingSubview) {
case (nil, let activeCall, false) where activeCall != nil && viewModel.maxParticipantsCount > 1:
case (nil, let activeCall, false)
where activeCall != nil && viewModel
.maxParticipantsCount > 1 && presentationValidator(viewModel.lastCall):
/// The following presentation criteria are required:
/// - The activeCall was ended.
/// - Participants, during call's duration, grew to more than one.
Expand Down Expand Up @@ -172,21 +184,27 @@ extension View {
/// - Active call was ended.
/// - Participants, during call's duration, grew to more than one.
///
/// - Parameter content: A viewBuilder that returns the modal's content. The viewModifier
/// - Parameters:
/// - presentationValidator: A closure that can be used to provide additional
/// validation rules for presentation. The modifier will inject the last available call when calling.
/// - content: A viewBuilder that returns the modal's content. The viewModifier
/// will provide a dismiss closure that can be called from the content to close the modal.
@ViewBuilder
public func onCallEnded(
presentationValidator: @escaping (Call?) -> Bool = { _ in true },
@ViewBuilder _ content: @escaping (Call?, @escaping () -> Void) -> some View
) -> some View {
if #available(iOS 14.0, *) {
modifier(
CallEndedViewModifier(
presentationValidator: presentationValidator,
subviewProvider: content
)
)
} else {
modifier(
CallEndedViewModifier_iOS13(
presentationValidator: presentationValidator,
subviewProvider: content
)
)
Expand Down
24 changes: 18 additions & 6 deletions Sources/StreamVideoSwiftUI/CallingViews/CallConnectingView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,32 @@
import StreamVideo
import SwiftUI

struct CallConnectingView<CallControls: View, CallTopView: View>: View {
public struct CallConnectingView<CallControls: View, CallTopView: View>: View {
@Injected(\.streamVideo) var streamVideo

@Injected(\.colors) var colors
@Injected(\.fonts) var fonts
@Injected(\.images) var images
@Injected(\.utils) var utils

var outgoingCallMembers: [Member]
var title: String
var callControls: CallControls
var callTopView: CallTopView
public var outgoingCallMembers: [Member]
public var title: String
public var callControls: CallControls
public var callTopView: CallTopView

var body: some View {
public init(
outgoingCallMembers: [Member],
title: String,
callControls: CallControls,
callTopView: CallTopView
) {
self.outgoingCallMembers = outgoingCallMembers
self.title = title
self.callControls = callControls
self.callTopView = callTopView
}

public var body: some View {
ZStack {
VStack(spacing: 16) {
callTopView
Expand Down
Loading

0 comments on commit 405d0f4

Please sign in to comment.