diff --git a/DemoApp/Sources/Views/Controls/DemoControls.swift b/DemoApp/Sources/Views/Controls/DemoControls.swift index 8166a03e4..e989897d2 100644 --- a/DemoApp/Sources/Views/Controls/DemoControls.swift +++ b/DemoApp/Sources/Views/Controls/DemoControls.swift @@ -18,6 +18,7 @@ struct AppControlsWithChat: View { private var canOpenChat: Bool private let size: CGFloat = 50 + private let cornerRadius: CGFloat = 24 @ObservedObject var reactionsHelper = AppState.shared.reactionsHelper @ObservedObject var viewModel: CallViewModel @@ -28,41 +29,28 @@ struct AppControlsWithChat: View { } var body: some View { - VStack { - HStack(alignment: .center, spacing: 12) { - if let chatViewModel, chatViewModel.isChatEnabled { - ChatIconView(viewModel: chatViewModel) - } - VideoIconView(viewModel: viewModel) - MicrophoneIconView(viewModel: viewModel) - ToggleCameraIconView(viewModel: viewModel) - if !ProcessInfo.processInfo.isiOSAppOnMac { - BroadcastIconView( - viewModel: viewModel, - preferredExtension: "io.getstream.iOS.VideoDemoApp.ScreenSharing" - ) - } - HangUpIconView(viewModel: viewModel) + HStack(alignment: .center, spacing: 12) { + if let chatViewModel, chatViewModel.isChatEnabled { + ChatIconView(viewModel: chatViewModel) } - .frame(height: 85) - } - .frame(maxWidth: .infinity) - .background( - colors.callControlsBackground - .edgesIgnoringSafeArea(.all) - ) - .overlay( - VStack { - colors.callControlsBackground - .frame(height: 30) - .cornerRadius(24) - Spacer() + VideoIconView(viewModel: viewModel) + MicrophoneIconView(viewModel: viewModel) + ToggleCameraIconView(viewModel: viewModel) + if !ProcessInfo.processInfo.isiOSAppOnMac { + BroadcastIconView( + viewModel: viewModel, + preferredExtension: "io.getstream.iOS.VideoDemoApp.ScreenSharing" + ) } - .offset(y: -15) + HangUpIconView(viewModel: viewModel) + } + .padding() + .clipCorners( + radius: cornerRadius, + corners: [.topLeft, .topRight], + backgroundColor: colors.callControlsBackground ) - .onReceive(viewModel.$call, perform: { call in - reactionsHelper.call = call - }) + .onReceive(viewModel.$call) { reactionsHelper.call = $0 } } } diff --git a/Sources/StreamVideoSwiftUI/CallView/CallControlsView.swift b/Sources/StreamVideoSwiftUI/CallView/CallControlsView.swift index a6e048b4f..a8d1589b5 100644 --- a/Sources/StreamVideoSwiftUI/CallView/CallControlsView.swift +++ b/Sources/StreamVideoSwiftUI/CallView/CallControlsView.swift @@ -10,7 +10,8 @@ public struct CallControlsView: View { @Injected(\.streamVideo) var streamVideo private let size: CGFloat = 50 - + private let cornerRadius: CGFloat = 24 + @ObservedObject var viewModel: CallViewModel @Injected(\.images) var images @@ -23,37 +24,28 @@ public struct CallControlsView: View { public var body: some View { HStack(alignment: .top) { Spacer() - + VideoIconView(viewModel: viewModel) - + Spacer() - + MicrophoneIconView(viewModel: viewModel) - + Spacer() - + ToggleCameraIconView(viewModel: viewModel) - + Spacer() HangUpIconView(viewModel: viewModel) - + Spacer() } - .frame(maxWidth: .infinity) - .frame(height: 85) - .background( - colors.callControlsBackground - .edgesIgnoringSafeArea(.all) - ) - .overlay( - VStack { - colors.callControlsBackground - .frame(height: 30) - .cornerRadius(24) - Spacer() - } - .offset(y: -15) + .padding() + .clipCorners( + radius: cornerRadius, + corners: [.topLeft, .topRight], + backgroundColor: colors.callControlsBackground ) } } diff --git a/Sources/StreamVideoSwiftUI/CallView/CallView.swift b/Sources/StreamVideoSwiftUI/CallView/CallView.swift index 9c4215722..2f330053b 100644 --- a/Sources/StreamVideoSwiftUI/CallView/CallView.swift +++ b/Sources/StreamVideoSwiftUI/CallView/CallView.swift @@ -97,7 +97,7 @@ public struct CallView: View { } } } - .background(Color(colors.callBackground)) + .background(Color(colors.callBackground).edgesIgnoringSafeArea(.all)) .frame(maxWidth: .infinity, maxHeight: .infinity) .onAppear { UIApplication.shared.isIdleTimerDisabled = true diff --git a/Sources/StreamVideoSwiftUI/Utils/ClipCorners.swift b/Sources/StreamVideoSwiftUI/Utils/ClipCorners.swift new file mode 100644 index 000000000..ada90b2c8 --- /dev/null +++ b/Sources/StreamVideoSwiftUI/Utils/ClipCorners.swift @@ -0,0 +1,79 @@ +// +// Copyright © 2023 Stream.io Inc. All rights reserved. +// + +import Foundation +import SwiftUI + +/// `CornerClipper` is a ViewModifier that clips a SwiftUI view +/// to have rounded corners on specified sides. +public struct CornerClipper: ViewModifier { + + /// The radius of the rounded corners. + public var radius: CGFloat + + /// The corners that should be rounded. + public var corners: UIRectCorner + + /// The background color that should extend below/above safeArea. + public var backgroundColor: Color + + /// Modifies the provided content by clipping it to the shape with rounded corners. + /// - Parameter content: The content view to be modified. + public func body(content: Content) -> some View { + ZStack { + backgroundColor + .cornerRadius(radius) + .edgesIgnoringSafeArea(.all) + + content + .clipShape(RoundedCorners(radius: radius, corners: corners)) + .layoutPriority(1) + } + } +} + +/// `RoundedCorners` is a Shape used to create a rounded cornered rectangle +/// on the specified sides using a given radius. +public struct RoundedCorners: Shape { + + /// The radius of the rounded corners. + public var radius: CGFloat = .infinity + + /// The corners to be rounded. + public var corners: UIRectCorner = .allCorners + + /// Creates a path for the current shape. + /// - Parameter rect: The rectangle in which the path should be created. + public func path(in rect: CGRect) -> Path { + Path( + UIBezierPath( + roundedRect: rect, + byRoundingCorners: corners, + cornerRadii: CGSize(width: radius, height: radius) + ).cgPath + ) + } +} + +extension View { + + /// Clips the corners of the current view. + /// - Parameters: + /// - radius: The radius for the rounded corners. + /// - corners: The corners that should be rounded. + /// - backgroundColor: The background color that should extend below/above safeArea. + public func clipCorners( + radius: CGFloat, + corners: UIRectCorner, + backgroundColor: Color = .clear + ) -> some View { + modifier( + CornerClipper( + radius: radius, + corners: corners, + backgroundColor: backgroundColor + ) + ) + } +} diff --git a/StreamVideo.xcodeproj/project.pbxproj b/StreamVideo.xcodeproj/project.pbxproj index 9adc612d3..43d0c7e91 100644 --- a/StreamVideo.xcodeproj/project.pbxproj +++ b/StreamVideo.xcodeproj/project.pbxproj @@ -86,6 +86,7 @@ 4093861F2AA0A21800FF5AF4 /* MemoryLogViewer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4093861E2AA0A21800FF5AF4 /* MemoryLogViewer.swift */; }; 409BFA402A9F79D2003341EF /* View+OptionalPublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 409BFA3F2A9F79D2003341EF /* View+OptionalPublisher.swift */; }; 409BFA432A9F7BBB003341EF /* ReadableContentGuide.swift in Sources */ = {isa = PBXBuildFile; fileRef = 409BFA422A9F7BBB003341EF /* ReadableContentGuide.swift */; }; + 40AA2EE22AE0137E000DCA5C /* ClipCorners.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40AA2EE02AE00179000DCA5C /* ClipCorners.swift */; }; 40AB31262A49838000C270E1 /* EventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40AB31252A49838000C270E1 /* EventTests.swift */; }; 40B499CA2AC1A5E100A53B60 /* OSLogDestination.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40B499C92AC1A5E100A53B60 /* OSLogDestination.swift */; }; 40B499CC2AC1A90F00A53B60 /* DeeplinkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40B499CB2AC1A90F00A53B60 /* DeeplinkTests.swift */; }; @@ -920,6 +921,7 @@ 4093861E2AA0A21800FF5AF4 /* MemoryLogViewer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemoryLogViewer.swift; sourceTree = ""; }; 409BFA3F2A9F79D2003341EF /* View+OptionalPublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+OptionalPublisher.swift"; sourceTree = ""; }; 409BFA422A9F7BBB003341EF /* ReadableContentGuide.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadableContentGuide.swift; sourceTree = ""; }; + 40AA2EE02AE00179000DCA5C /* ClipCorners.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClipCorners.swift; sourceTree = ""; }; 40AB31252A49838000C270E1 /* EventTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventTests.swift; sourceTree = ""; }; 40B499C92AC1A5E100A53B60 /* OSLogDestination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSLogDestination.swift; sourceTree = ""; }; 40B499CB2AC1A90F00A53B60 /* DeeplinkTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeeplinkTests.swift; sourceTree = ""; }; @@ -2980,6 +2982,7 @@ 84EBAA91288C135700BE3176 /* Utils */ = { isa = PBXGroup; children = ( + 40AA2EE02AE00179000DCA5C /* ClipCorners.swift */, 84EBAA92288C137E00BE3176 /* Modifiers.swift */, 84F3B0E3289174F60088751D /* ContainerHelpers.swift */, 8434C52C289AA41D0001490A /* ImageExtensions.swift */, @@ -4268,6 +4271,7 @@ 843697D228C7A25F00839D99 /* ParticipantsGridView.swift in Sources */, 840042CF2A70212D00917B30 /* ScreensharingControls.swift in Sources */, 8406269A2A37A5E2004B8748 /* CallEvents.swift in Sources */, + 40AA2EE22AE0137E000DCA5C /* ClipCorners.swift in Sources */, 8415D3E3290BC882006E53CB /* Sounds.swift in Sources */, 84DCA2092A382B16000C3411 /* CallModels.swift in Sources */, 8442993C294232360037232A /* IncomingCallView_iOS13.swift in Sources */,