Skip to content

Commit

Permalink
Update structure and add docs
Browse files Browse the repository at this point in the history
  • Loading branch information
ipavlidakis committed May 8, 2024
1 parent e6c604d commit be9caef
Show file tree
Hide file tree
Showing 10 changed files with 412 additions and 133 deletions.
105 changes: 0 additions & 105 deletions DemoApp/Sources/Components/Feedback/CallEndedViewModifier.swift

This file was deleted.

2 changes: 1 addition & 1 deletion DemoApp/Sources/Components/Feedback/DemoFeedbackView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ struct DemoFeedbackView: View {
}
.padding(.horizontal)
}
.withModalNavigationBar(title: "", closeAction: { dismiss() })
.withModalNavigationBar(title: "", closeAction: dismiss)
}
}

Expand Down
4 changes: 2 additions & 2 deletions Sources/StreamVideo/Call.swift
Original file line number Diff line number Diff line change
Expand Up @@ -927,9 +927,9 @@ public class Call: @unchecked Sendable, WSEventsSubscriber {
@discardableResult
@MainActor
public func collectUserFeedback(
custom: [String: RawJSON]? = nil,
rating: Int? = nil,
reason: String? = nil
reason: String? = nil,
custom: [String: RawJSON]? = nil
) async throws -> CollectUserFeedbackResponse {
try await callController.collectUserFeedback(
sessionID: state.sessionId,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
//
// Copyright © 2024 Stream.io Inc. All rights reserved.
//

import Combine
import Foundation
import StreamVideo
import SwiftUI

private final class CallEndedViewModifierViewModel: ObservableObject {

@Injected(\.streamVideo) private var streamVideo

@Published var activeCall: Call?
@Published var lastCall: Call?
@Published var isPresentingSubview: Bool = false
@Published var maxParticipantsCount: Int = 0

private var observationCancellable: AnyCancellable?

init() {
observationCancellable = streamVideo
.state
.$activeCall
.receive(on: DispatchQueue.main)
.sink { [weak self] in self?.activeCall = $0 }
}
}

@available(iOS 14.0, *)
private struct CallEndedViewModifier<Subview: View>: ViewModifier {

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

@StateObject private var viewModel: CallEndedViewModifierViewModel

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

func body(content: Content) -> some View {
content
.sheet(isPresented: $viewModel.isPresentingSubview) {
subviewProvider(viewModel.lastCall) {
viewModel.lastCall = nil
viewModel.isPresentingSubview = false
}
}
.onReceive(viewModel.$activeCall) { call in
log
.debug(
"CallEnded view modifier received newValue:\(call?.cId ?? "nil") oldValue:\(viewModel.lastCall?.cId ?? "nil") isPresentingSubview:\(viewModel.isPresentingSubview) maxParticipantsCount:\(viewModel.maxParticipantsCount)."
)

switch (call, viewModel.lastCall, viewModel.isPresentingSubview) {
case (nil, let activeCall, false) where activeCall != nil && viewModel.maxParticipantsCount > 1:
/// The following presentation criteria are required:
/// - The activeCall was ended.
/// - Participants, during call's duration, grew to more than one.
viewModel.isPresentingSubview = true

case let (newActiveCall, activeCall, _) where newActiveCall != nil && activeCall != nil:
/// The activeCall was replaced with another call. We should not present the
/// subview. We will also hide any modals if any is visible.
viewModel.lastCall = newActiveCall
viewModel.isPresentingSubview = false
viewModel.maxParticipantsCount = 0

case (let newActiveCall, nil, _) where newActiveCall != nil:
/// A new call has started. We should not present the subview. We will also hide
/// any modals if any is visible.
viewModel.lastCall = newActiveCall
viewModel.isPresentingSubview = false
viewModel.maxParticipantsCount = 0

default:
/// For every other case we won't perform any action.
break
}
}
.onReceive(viewModel.activeCall?.state.$participants) {
/// Every time participants update, we store the maximum number of participants in
/// the call (during call's duration).
let newMaxParticipantsCount = max(viewModel.maxParticipantsCount, $0.count)
if newMaxParticipantsCount != viewModel.maxParticipantsCount {
log
.debug(
"CallEnded view modifier updated maxParticipantsCount:\(viewModel.maxParticipantsCount)\(newMaxParticipantsCount)"
)
viewModel.maxParticipantsCount = newMaxParticipantsCount
}
}
}
}

@available(iOS, introduced: 13, obsoleted: 14)
private struct CallEndedViewModifier_iOS13<Subview: View>: ViewModifier {

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

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

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

func body(content: Content) -> some View {
content
.sheet(isPresented: $viewModel.isPresentingSubview) {
subviewProvider(viewModel.lastCall) {
viewModel.lastCall = nil
viewModel.isPresentingSubview = false
}
}
.onReceive(viewModel.$activeCall) { call in
log
.debug(
"CallEnded view modifier received newValue:\(call?.cId ?? "nil") oldValue:\(viewModel.lastCall?.cId ?? "nil") isPresentingSubview:\(viewModel.isPresentingSubview) maxParticipantsCount:\(viewModel.maxParticipantsCount)."
)

switch (call, viewModel.lastCall, viewModel.isPresentingSubview) {
case (nil, let activeCall, false) where activeCall != nil && viewModel.maxParticipantsCount > 1:
/// The following presentation criteria are required:
/// - The activeCall was ended.
/// - Participants, during call's duration, grew to more than one.
viewModel.isPresentingSubview = true

case let (newActiveCall, activeCall, _) where newActiveCall != nil && activeCall != nil:
/// The activeCall was replaced with another call. We should not present the
/// subview. We will also hide any modals if any is visible.
viewModel.lastCall = newActiveCall
viewModel.isPresentingSubview = false
viewModel.maxParticipantsCount = 0

case (let newActiveCall, nil, _) where newActiveCall != nil:
/// The activeCall was replaced with another call. We should not present the
/// subview. We will also hide any modals if any is visible.
viewModel.lastCall = newActiveCall
viewModel.isPresentingSubview = false
viewModel.maxParticipantsCount = 0

default:
/// For every other case we won't perform any action.
break
}
}
.onReceive(viewModel.activeCall?.state.$participants) {
/// Every time participants update, we store the maximum number of participants in
/// the call (during call's duration).
let newMaxParticipantsCount = max(viewModel.maxParticipantsCount, $0.count)
if newMaxParticipantsCount != viewModel.maxParticipantsCount {
log
.debug(
"CallEnded view modifier updated maxParticipantsCount:\(viewModel.maxParticipantsCount)\(newMaxParticipantsCount)"
)
viewModel.maxParticipantsCount = newMaxParticipantsCount
}
}
}
}

extension View {

/// A viewModifier that observes callState from StreamVideo. Once the following criteria are being
/// fulfilled, presents a modal with the provided content.
/// Activation criteria:
/// - 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
/// will provide a dismiss closure that can be called from the content to close the modal.
@ViewBuilder
public func onCallEnded(
@ViewBuilder _ content: @escaping (Call?, @escaping () -> Void) -> some View
) -> some View {
if #available(iOS 14.0, *) {
modifier(
CallEndedViewModifier(
subviewProvider: content
)
)
} else {
modifier(
CallEndedViewModifier_iOS13(
subviewProvider: content
)
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import SwiftUI
extension View {

@ViewBuilder
func onReceive<P>(
public func onReceive<P>(
_ publisher: P?,
perform action: @escaping (P.Output) -> Void
) -> some View where P: Publisher, P.Failure == Never {
Expand Down
15 changes: 0 additions & 15 deletions Sources/StreamVideoSwiftUI/Utils/ViewExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,3 @@ extension Alert {
)
}
}

extension View {

@ViewBuilder
func onReceive<P>(
_ publisher: P?,
perform action: @escaping (P.Output) -> Void
) -> some View where P: Publisher, P.Failure == Never {
if let publisher = publisher {
onReceive(publisher, perform: action)
} else {
self
}
}
}
Loading

0 comments on commit be9caef

Please sign in to comment.