AzureCommunicationUI/sdk/AzureCommunicationUICalling/Sources/Presentation/SwiftUI/Calling/CallingViewComponent/DraggableLocalVideoView.swift (137 lines of code) (raw):
//
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
//
import SwiftUI
struct DraggableLocalVideoView: View {
let containerBounds: CGRect
let viewModel: CallingViewModel
let avatarManager: AvatarViewManagerProtocol
let viewManager: VideoViewManager
@State var pipPosition: CGPoint?
@GestureState var pipDragStartPosition: CGPoint?
@Binding var orientation: UIDeviceOrientation
let screenSize: ScreenSizeClassType
var body: some View {
return GeometryReader { geometry in
let size = getPipSize(parentSize: geometry.size)
localVideoPipView
.frame(width: size.width, height: size.height, alignment: .center)
.position(self.pipPosition ?? getInitialPipPosition(containerBounds: containerBounds))
.gesture(
DragGesture()
.onChanged { value in
let containerBounds = getContainerBounds(bounds: geometry.frame(in: .local))
let translatedPipPosition = getTranslatedPipPosition(
currentPipPosition: self.pipPosition!,
pipDragStartPosition: self.pipDragStartPosition,
translation: value.translation,
isRightToLeft: viewModel.isRightToLeft)
self.pipPosition = getBoundedPipPosition(
currentPipPosition: self.pipPosition!,
requestedPipPosition: translatedPipPosition,
bounds: containerBounds)
}
.updating($pipDragStartPosition) { (_, startLocation, _) in
startLocation = startLocation ?? self.pipPosition
}
)
.onAppear {
self.pipPosition = getInitialPipPosition(containerBounds: containerBounds)
}
.onChange(of: geometry.size) { _ in
self.pipPosition = getInitialPipPosition(containerBounds: geometry.frame(in: .local))
}
.onChange(of: orientation) { _ in
self.pipPosition = getInitialPipPosition(containerBounds: geometry.frame(in: .local))
}
}
}
var localVideoPipView: some View {
let shapeCornerRadius: CGFloat = 4
return Group {
LocalVideoView(viewModel: viewModel.localVideoViewModel,
viewManager: viewManager,
viewType: .localVideoPip,
avatarManager: avatarManager)
.background(Color(StyleProvider.color.backgroundColor))
.clipShape(RoundedRectangle(cornerRadius: shapeCornerRadius))
}
}
private func getInitialPipPosition(containerBounds: CGRect) -> CGPoint {
return CGPoint(
x: getContainerBounds(bounds: containerBounds).maxX,
y: getContainerBounds(bounds: containerBounds).maxY)
}
private func getContainerBounds(bounds: CGRect) -> CGRect {
let pipSize = getPipSize(parentSize: bounds.size)
let padding = viewModel.isInPip ? 0.0 : 12.0
let containerBounds = bounds.inset(by: UIEdgeInsets(
top: pipSize.height / 2.0 + padding,
left: pipSize.width / 2.0 + padding,
bottom: pipSize.height / 2.0 + padding,
right: pipSize.width / 2.0 + padding))
return containerBounds
}
private func getTranslatedPipPosition(
currentPipPosition: CGPoint,
pipDragStartPosition: CGPoint?,
translation: CGSize,
isRightToLeft: Bool) -> CGPoint {
var translatedPipPosition = pipDragStartPosition ?? currentPipPosition
translatedPipPosition.x += isRightToLeft
? -translation.width
: translation.width
translatedPipPosition.y += translation.height
return translatedPipPosition
}
private func getBoundedPipPosition(
currentPipPosition: CGPoint,
requestedPipPosition: CGPoint,
bounds: CGRect) -> CGPoint {
var boundedPipPosition = currentPipPosition
if bounds.contains(requestedPipPosition) {
boundedPipPosition = requestedPipPosition
} else if requestedPipPosition.x > bounds.minX && requestedPipPosition.x < bounds.maxX {
boundedPipPosition.x = requestedPipPosition.x
boundedPipPosition.y = getLimitedValue(
value: requestedPipPosition.y,
min: bounds.minY,
max: bounds.maxY)
} else if requestedPipPosition.y > bounds.minY && requestedPipPosition.y < bounds.maxY {
boundedPipPosition.x = getLimitedValue(
value: requestedPipPosition.x,
min: bounds.minX,
max: bounds.maxX)
boundedPipPosition.y = requestedPipPosition.y
}
return boundedPipPosition
}
/// Gets the size of the Pip view based on the parent size
/// - Parameter parentSize: size of the parent view
/// - Returns: the size of the Pip view based on the parent size
private func getPipSize(parentSize: CGSize? = nil) -> CGSize {
let isPortraitMode = screenSize != .iphoneLandscapeScreenSize
let isiPad = UIDevice.current.userInterfaceIdiom == .pad
func pipSize() -> CGSize {
return CGSize(width: 36, height: 52)
}
func defaultPipSize() -> CGSize {
let width = isPortraitMode ? 72 : 104
let height = isPortraitMode ? 104 : 72
let size = CGSize(width: width, height: height)
return size
}
func iPadPipSize() -> CGSize {
guard let parentSize = parentSize else {
return defaultPipSize()
}
let isIpadPortrait = parentSize.width < parentSize.height
let width = isIpadPortrait ? 80.0 : 152.0
return CGSize(width: width, height: 115.0)
}
return viewModel.isInPip ? pipSize() : isiPad ? iPadPipSize() : defaultPipSize()
}
private func getLimitedValue(value: CGFloat, min: CGFloat, max: CGFloat) -> CGFloat {
var limitedValue = value
if value < min {
limitedValue = min
} else if value > max {
limitedValue = max
}
return limitedValue
}
}