import SwiftUI import StreamVideo import StreamVideoSwiftUI import Combine class CallOverlayViewFactory: ViewFactory { // ... existing ViewFactory methods ... func makeVideoParticipantView( participant: CallParticipant, id: String, availableFrame: CGRect, contentMode: UIView.ContentMode, customData: [String: RawJSON], call: Call? ) -> some View { VideoCallParticipantView( viewFactory: self, participant: participant, id: id, availableFrame: availableFrame, contentMode: .scaleAspectFit, customData: customData, call: call ) } func makeVideoCallParticipantModifier( participant: CallParticipant, call: Call?, availableFrame: CGRect, ratio: CGFloat, showAllInfo: Bool ) -> some ViewModifier { VideoCallParticipantModifier( participant: participant, call: call, availableFrame: availableFrame, ratio: ratio, showAllInfo: showAllInfo, decorations: [.speaking] // Here we only want the speaking decoration ) } func makeLocalParticipantViewModifier( localParticipant: CallParticipant, callSettings: Binding, call: Call? ) -> some ViewModifier { LocalParticipantViewModifier( localParticipant: localParticipant, call: call, callSettings: callSettings, showAllInfo: true, decorations: [.speaking] // Here we only need the speaking decoration ) } } struct CallOverlayView: View { @ObservedObject var viewModel: CallViewModel // Healthy Homies (10/21): while the face tiles show, they draw the call // (CrewTiles/CrewStageView, in its own view under the page) and Stream's // stock view steps aside. Tiles off, this is the view as it always was. @ObservedObject var crew: CrewLayoutModel @State private var safeAreaInsets: EdgeInsets = .init() private let viewFactory: CallOverlayViewFactory init(viewModel: CallViewModel, crew: CrewLayoutModel) { self.viewModel = viewModel self.crew = crew self.viewFactory = CallOverlayViewFactory() } var body: some View { VStack(spacing: 0) { ZStack { if crew.showing { Color.clear } else { CustomCallView(viewFactory: viewFactory, viewModel: viewModel) } } .padding(.top, safeAreaInsets.top) .padding(.bottom, safeAreaInsets.bottom) } .edgesIgnoringSafeArea(.all) .overlay( GeometryReader { geometry in Color.clear .preference(key: SafeAreaInsetsKey.self, value: geometry.safeAreaInsets) } ) .onPreferenceChange(SafeAreaInsetsKey.self) { value in safeAreaInsets = value } .frame(maxWidth: .infinity, maxHeight: .infinity) } private func changeTrackVisibility(_ participant: CallParticipant?, isVisible: Bool) { print("changeTrackVisibility for \(String(describing: participant?.userId)), visible: \(isVisible)") guard let participant = participant, let call = viewModel.call else { return } Task { await call.changeTrackVisibility(for: participant, isVisible: isVisible) } } } extension CallOverlayView { static func create(callViewModel: CallViewModel, crew: CrewLayoutModel) -> UIHostingController { let view = CallOverlayView(viewModel: callViewModel, crew: crew) let hostingController = UIHostingController(rootView: view) hostingController.view.backgroundColor = .clear // Make sure we respect safe areas hostingController.view.insetsLayoutMarginsFromSafeArea = true return (hostingController) } } struct SafeAreaInsetsKey: PreferenceKey { static var defaultValue: EdgeInsets = .init() static func reduce(value: inout EdgeInsets, nextValue: () -> EdgeInsets) { value = nextValue() } }