[WIP] Call UI

This commit is contained in:
Isaac
2023-12-01 01:06:22 +04:00
parent 835278b9dc
commit 06e3841f23
22 changed files with 1204 additions and 480 deletions

View File

@@ -128,8 +128,10 @@ public final class PrivateCallScreen: OverlayMaskContainerView {
private var emojiView: KeyEmojiView?
private var localVideoContainerView: VideoContainerView?
private var remoteVideoContainerView: VideoContainerView?
private let videoContainerBackgroundView: RoundedCornersView
private let overlayContentsVideoContainerBackgroundView: RoundedCornersView
private var videoContainerViews: [VideoContainerView] = []
private var activeRemoteVideoSource: VideoSource?
private var waitingForFirstRemoteVideoFrameDisposable: Disposable?
@@ -137,6 +139,9 @@ public final class PrivateCallScreen: OverlayMaskContainerView {
private var activeLocalVideoSource: VideoSource?
private var waitingForFirstLocalVideoFrameDisposable: Disposable?
private var areControlsHidden: Bool = false
private var swapLocalAndRemoteVideo: Bool = false
private var processedInitialAudioLevelBump: Bool = false
private var audioLevelBump: Float = 0.0
@@ -161,6 +166,9 @@ public final class PrivateCallScreen: OverlayMaskContainerView {
self.blobLayer = CallBlobsLayer()
self.avatarLayer = AvatarLayer()
self.videoContainerBackgroundView = RoundedCornersView(color: .black)
self.overlayContentsVideoContainerBackgroundView = RoundedCornersView(color: UIColor(white: 0.1, alpha: 1.0))
self.titleView = TextView()
self.statusView = StatusView()
@@ -169,9 +177,13 @@ public final class PrivateCallScreen: OverlayMaskContainerView {
self.layer.addSublayer(self.backgroundLayer)
self.overlayContentsView.layer.addSublayer(self.backgroundLayer.blurredLayer)
self.overlayContentsView.addSubview(self.overlayContentsVideoContainerBackgroundView)
self.layer.addSublayer(self.blobLayer)
self.layer.addSublayer(self.avatarLayer)
self.addSubview(self.videoContainerBackgroundView)
self.overlayContentsView.mask = self.maskContents
self.addSubview(self.overlayContentsView)
@@ -201,6 +213,8 @@ public final class PrivateCallScreen: OverlayMaskContainerView {
}
self.audioLevelUpdateSubscription = nil
}
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
}
public required init?(coder: NSCoder) {
@@ -245,6 +259,15 @@ public final class PrivateCallScreen: OverlayMaskContainerView {
}
}
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
if case .ended = recognizer.state {
if self.activeRemoteVideoSource != nil || self.activeLocalVideoSource != nil {
self.areControlsHidden = !self.areControlsHidden
self.update(transition: .spring(duration: 0.4))
}
}
}
public func update(size: CGSize, insets: UIEdgeInsets, screenCornerRadius: CGFloat, state: State, transition: Transition) {
let params = Params(size: size, insets: insets, screenCornerRadius: screenCornerRadius, state: state)
if self.params == params {
@@ -259,7 +282,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView {
self.activeRemoteVideoSource = remoteVideo
} else {
let firstVideoFrameSignal = Signal<Never, NoError> { subscriber in
remoteVideo.updated = { [weak remoteVideo] in
return remoteVideo.addOnUpdated { [weak remoteVideo] in
guard let remoteVideo else {
subscriber.putCompletion()
return
@@ -268,12 +291,9 @@ public final class PrivateCallScreen: OverlayMaskContainerView {
subscriber.putCompletion()
}
}
return EmptyDisposable
}
var shouldUpdate = false
self.waitingForFirstRemoteVideoFrameDisposable = (firstVideoFrameSignal
|> timeout(4.0, queue: .mainQueue(), alternate: .complete())
|> deliverOnMainQueue).startStrict(completed: { [weak self] in
guard let self else {
return
@@ -297,7 +317,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView {
self.activeLocalVideoSource = localVideo
} else {
let firstVideoFrameSignal = Signal<Never, NoError> { subscriber in
localVideo.updated = { [weak localVideo] in
return localVideo.addOnUpdated { [weak localVideo] in
guard let localVideo else {
subscriber.putCompletion()
return
@@ -306,12 +326,9 @@ public final class PrivateCallScreen: OverlayMaskContainerView {
subscriber.putCompletion()
}
}
return EmptyDisposable
}
var shouldUpdate = false
self.waitingForFirstLocalVideoFrameDisposable = (firstVideoFrameSignal
|> timeout(4.0, queue: .mainQueue(), alternate: .complete())
|> deliverOnMainQueue).startStrict(completed: { [weak self] in
guard let self else {
return
@@ -328,6 +345,10 @@ public final class PrivateCallScreen: OverlayMaskContainerView {
}
}
if self.activeRemoteVideoSource == nil && self.activeLocalVideoSource == nil {
self.areControlsHidden = false
}
self.params = params
self.updateInternal(params: params, transition: transition)
}
@@ -340,34 +361,42 @@ public final class PrivateCallScreen: OverlayMaskContainerView {
}
private func updateInternal(params: Params, transition: Transition) {
let backgroundFrame = CGRect(origin: CGPoint(), size: params.size)
let aspect: CGFloat = params.size.width / params.size.height
let sizeNorm: CGFloat = 64.0
let renderingSize = CGSize(width: floor(sizeNorm * aspect), height: sizeNorm)
let edgeSize: Int = 2
let primaryVideoSource: VideoSource?
let secondaryVideoSource: VideoSource?
if let activeRemoteVideoSource = self.activeRemoteVideoSource, let activeLocalVideoSource = self.activeLocalVideoSource {
primaryVideoSource = activeRemoteVideoSource
secondaryVideoSource = activeLocalVideoSource
} else if let activeRemoteVideoSource = self.activeRemoteVideoSource {
primaryVideoSource = activeRemoteVideoSource
secondaryVideoSource = nil
} else if let activeLocalVideoSource = self.activeLocalVideoSource {
primaryVideoSource = activeLocalVideoSource
secondaryVideoSource = nil
} else {
primaryVideoSource = nil
secondaryVideoSource = nil
let genericAlphaTransition: Transition
switch transition.animation {
case .none:
genericAlphaTransition = .immediate
case let .curve(duration, _):
genericAlphaTransition = .easeInOut(duration: min(0.3, duration))
}
let havePrimaryVideo = self.activeRemoteVideoSource != nil || self.activeLocalVideoSource != nil
let backgroundFrame = CGRect(origin: CGPoint(), size: params.size)
let visualBackgroundFrame = backgroundFrame.insetBy(dx: -CGFloat(edgeSize) / renderingSize.width * backgroundFrame.width, dy: -CGFloat(edgeSize) / renderingSize.height * backgroundFrame.height)
var activeVideoSources: [(VideoContainerView.Key, VideoSource)] = []
if self.swapLocalAndRemoteVideo {
if let activeLocalVideoSource = self.activeLocalVideoSource {
activeVideoSources.append((.background, activeLocalVideoSource))
}
if let activeRemoteVideoSource = self.activeRemoteVideoSource {
activeVideoSources.append((.foreground, activeRemoteVideoSource))
}
} else {
if let activeRemoteVideoSource = self.activeRemoteVideoSource {
activeVideoSources.append((.background, activeRemoteVideoSource))
}
if let activeLocalVideoSource = self.activeLocalVideoSource {
activeVideoSources.append((.foreground, activeLocalVideoSource))
}
}
let havePrimaryVideo = !activeVideoSources.isEmpty
self.backgroundLayer.renderSpec = RenderLayerSpec(size: RenderSize(width: Int(renderingSize.width) + edgeSize * 2, height: Int(renderingSize.height) + edgeSize * 2))
let currentAreControlsHidden = havePrimaryVideo && self.areControlsHidden
let backgroundAspect: CGFloat = params.size.width / params.size.height
let backgroundSizeNorm: CGFloat = 64.0
let backgroundRenderingSize = CGSize(width: floor(backgroundSizeNorm * backgroundAspect), height: backgroundSizeNorm)
let backgroundEdgeSize: Int = 2
let visualBackgroundFrame = backgroundFrame.insetBy(dx: -CGFloat(backgroundEdgeSize) / backgroundRenderingSize.width * backgroundFrame.width, dy: -CGFloat(backgroundEdgeSize) / backgroundRenderingSize.height * backgroundFrame.height)
self.backgroundLayer.renderSpec = RenderLayerSpec(size: RenderSize(width: Int(backgroundRenderingSize.width) + backgroundEdgeSize * 2, height: Int(backgroundRenderingSize.height) + backgroundEdgeSize * 2))
transition.setFrame(layer: self.backgroundLayer, frame: visualBackgroundFrame)
transition.setFrame(layer: self.backgroundLayer.blurredLayer, frame: visualBackgroundFrame)
@@ -427,15 +456,17 @@ public final class PrivateCallScreen: OverlayMaskContainerView {
self.speakerAction?()
}), at: 0)
}
self.buttonGroupView.update(size: params.size, buttons: buttons, transition: transition)
let contentBottomInset = self.buttonGroupView.update(size: params.size, insets: params.insets, controlsHidden: currentAreControlsHidden, buttons: buttons, transition: transition)
if case let .active(activeState) = params.state.lifecycleState {
let emojiView: KeyEmojiView
var emojiTransition = transition
var emojiAlphaTransition = genericAlphaTransition
if let current = self.emojiView {
emojiView = current
} else {
emojiTransition = transition.withAnimation(.none)
emojiAlphaTransition = genericAlphaTransition.withAnimation(.none)
emojiView = KeyEmojiView(emoji: activeState.emojiKey)
self.emojiView = emojiView
}
@@ -445,11 +476,18 @@ public final class PrivateCallScreen: OverlayMaskContainerView {
emojiView.animateIn()
}
}
emojiTransition.setFrame(view: emojiView, frame: CGRect(origin: CGPoint(x: params.size.width - params.insets.right - 12.0 - emojiView.size.width, y: params.insets.top + 27.0), size: emojiView.size))
let emojiY: CGFloat
if currentAreControlsHidden {
emojiY = -8.0 - emojiView.size.height
} else {
emojiY = params.insets.top + 12.0
}
emojiTransition.setFrame(view: emojiView, frame: CGRect(origin: CGPoint(x: params.size.width - params.insets.right - 12.0 - emojiView.size.width, y: emojiY), size: emojiView.size))
emojiAlphaTransition.setAlpha(view: emojiView, alpha: currentAreControlsHidden ? 0.0 : 1.0)
} else {
if let emojiView = self.emojiView {
self.emojiView = nil
transition.setAlpha(view: emojiView, alpha: 0.0, completion: { [weak emojiView] _ in
genericAlphaTransition.setAlpha(view: emojiView, alpha: 0.0, completion: { [weak emojiView] _ in
emojiView?.removeFromSuperview()
})
}
@@ -464,112 +502,162 @@ public final class PrivateCallScreen: OverlayMaskContainerView {
let avatarFrame = havePrimaryVideo ? expandedAvatarFrame : collapsedAvatarFrame
let avatarCornerRadius = havePrimaryVideo ? params.screenCornerRadius : collapsedAvatarSize * 0.5
let minimizedVideoInsets = UIEdgeInsets(top: 124.0, left: 12.0, bottom: 178.0, right: 12.0)
var minimizedVideoInsets = UIEdgeInsets()
minimizedVideoInsets.top = params.insets.top + (currentAreControlsHidden ? 0.0 : 60.0)
minimizedVideoInsets.left = params.insets.left + 12.0
minimizedVideoInsets.right = params.insets.right + 12.0
minimizedVideoInsets.bottom = contentBottomInset + 12.0
if let primaryVideoSource {
let remoteVideoContainerView: VideoContainerView
if let current = self.remoteVideoContainerView {
remoteVideoContainerView = current
var validVideoContainerKeys: [VideoContainerView.Key] = []
for i in 0 ..< activeVideoSources.count {
let (videoContainerKey, videoSource) = activeVideoSources[i]
validVideoContainerKeys.append(videoContainerKey)
var animateIn = false
let videoContainerView: VideoContainerView
if let current = self.videoContainerViews.first(where: { $0.key == videoContainerKey }) {
videoContainerView = current
} else {
remoteVideoContainerView = VideoContainerView(frame: CGRect())
self.remoteVideoContainerView = remoteVideoContainerView
self.insertSubview(remoteVideoContainerView, belowSubview: self.overlayContentsView)
self.overlayContentsView.layer.addSublayer(remoteVideoContainerView.blurredContainerLayer)
animateIn = true
videoContainerView = VideoContainerView(key: videoContainerKey)
switch videoContainerKey {
case .foreground:
self.overlayContentsView.layer.addSublayer(videoContainerView.blurredContainerLayer)
self.insertSubview(videoContainerView, belowSubview: self.overlayContentsView)
self.videoContainerViews.append(videoContainerView)
case .background:
if !self.videoContainerViews.isEmpty {
self.overlayContentsView.layer.insertSublayer(videoContainerView.blurredContainerLayer, below: self.videoContainerViews[0].blurredContainerLayer)
self.insertSubview(videoContainerView, belowSubview: self.videoContainerViews[0])
self.videoContainerViews.insert(videoContainerView, at: 0)
} else {
self.overlayContentsView.layer.addSublayer(videoContainerView.blurredContainerLayer)
self.insertSubview(videoContainerView, belowSubview: self.overlayContentsView)
self.videoContainerViews.append(videoContainerView)
}
}
remoteVideoContainerView.layer.position = self.avatarLayer.position
remoteVideoContainerView.layer.bounds = self.avatarLayer.bounds
remoteVideoContainerView.alpha = 0.0
remoteVideoContainerView.blurredContainerLayer.position = self.avatarLayer.position
remoteVideoContainerView.blurredContainerLayer.bounds = self.avatarLayer.bounds
remoteVideoContainerView.blurredContainerLayer.opacity = 0.0
remoteVideoContainerView.update(size: self.avatarLayer.bounds.size, insets: minimizedVideoInsets, cornerRadius: self.avatarLayer.params?.cornerRadius ?? 0.0, isMinimized: false, isAnimatingOut: false, transition: .immediate)
videoContainerView.pressAction = { [weak self] in
guard let self else {
return
}
self.swapLocalAndRemoteVideo = !self.swapLocalAndRemoteVideo
self.update(transition: .easeInOut(duration: 0.25))
}
}
if remoteVideoContainerView.video !== primaryVideoSource {
remoteVideoContainerView.video = primaryVideoSource
if videoContainerView.video !== videoSource {
videoContainerView.video = videoSource
}
transition.setPosition(view: remoteVideoContainerView, position: expandedVideoFrame.center)
transition.setBounds(view: remoteVideoContainerView, bounds: CGRect(origin: CGPoint(), size: expandedVideoFrame.size))
transition.setAlpha(view: remoteVideoContainerView, alpha: 1.0)
transition.setPosition(layer: remoteVideoContainerView.blurredContainerLayer, position: expandedVideoFrame.center)
transition.setBounds(layer: remoteVideoContainerView.blurredContainerLayer, bounds: CGRect(origin: CGPoint(), size: expandedVideoFrame.size))
transition.setAlpha(layer: remoteVideoContainerView.blurredContainerLayer, alpha: 1.0)
remoteVideoContainerView.update(size: expandedVideoFrame.size, insets: minimizedVideoInsets, cornerRadius: params.screenCornerRadius, isMinimized: false, isAnimatingOut: false, transition: transition)
} else {
if let remoteVideoContainerView = self.remoteVideoContainerView {
remoteVideoContainerView.update(size: avatarFrame.size, insets: minimizedVideoInsets, cornerRadius: avatarCornerRadius, isMinimized: false, isAnimatingOut: true, transition: transition)
transition.setPosition(layer: remoteVideoContainerView.blurredContainerLayer, position: avatarFrame.center)
transition.setBounds(layer: remoteVideoContainerView.blurredContainerLayer, bounds: CGRect(origin: CGPoint(), size: avatarFrame.size))
transition.setAlpha(layer: remoteVideoContainerView.blurredContainerLayer, alpha: 0.0)
transition.setPosition(view: remoteVideoContainerView, position: avatarFrame.center)
transition.setBounds(view: remoteVideoContainerView, bounds: CGRect(origin: CGPoint(), size: avatarFrame.size))
if remoteVideoContainerView.alpha != 0.0 {
transition.setAlpha(view: remoteVideoContainerView, alpha: 0.0, completion: { [weak self, weak remoteVideoContainerView] completed in
guard let self, let remoteVideoContainerView, completed else {
let videoContainerTransition = transition
if animateIn {
if i == 0 && self.videoContainerViews.count == 1 {
videoContainerView.layer.position = self.avatarLayer.position
videoContainerView.layer.bounds = self.avatarLayer.bounds
videoContainerView.alpha = 0.0
videoContainerView.blurredContainerLayer.position = self.avatarLayer.position
videoContainerView.blurredContainerLayer.bounds = self.avatarLayer.bounds
videoContainerView.blurredContainerLayer.opacity = 0.0
videoContainerView.update(size: self.avatarLayer.bounds.size, insets: minimizedVideoInsets, cornerRadius: self.avatarLayer.params?.cornerRadius ?? 0.0, controlsHidden: currentAreControlsHidden, isMinimized: false, isAnimatedOut: true, transition: .immediate)
} else {
videoContainerView.layer.position = expandedVideoFrame.center
videoContainerView.layer.bounds = CGRect(origin: CGPoint(), size: expandedVideoFrame.size)
videoContainerView.alpha = 0.0
videoContainerView.blurredContainerLayer.position = expandedVideoFrame.center
videoContainerView.blurredContainerLayer.bounds = CGRect(origin: CGPoint(), size: expandedVideoFrame.size)
videoContainerView.blurredContainerLayer.opacity = 0.0
videoContainerView.update(size: self.avatarLayer.bounds.size, insets: minimizedVideoInsets, cornerRadius: params.screenCornerRadius, controlsHidden: currentAreControlsHidden, isMinimized: i != 0, isAnimatedOut: i != 0, transition: .immediate)
}
}
videoContainerTransition.setPosition(view: videoContainerView, position: expandedVideoFrame.center)
videoContainerTransition.setBounds(view: videoContainerView, bounds: CGRect(origin: CGPoint(), size: expandedVideoFrame.size))
videoContainerTransition.setPosition(layer: videoContainerView.blurredContainerLayer, position: expandedVideoFrame.center)
videoContainerTransition.setBounds(layer: videoContainerView.blurredContainerLayer, bounds: CGRect(origin: CGPoint(), size: expandedVideoFrame.size))
videoContainerView.update(size: expandedVideoFrame.size, insets: minimizedVideoInsets, cornerRadius: params.screenCornerRadius, controlsHidden: currentAreControlsHidden, isMinimized: i != 0, isAnimatedOut: false, transition: videoContainerTransition)
let alphaTransition: Transition
switch transition.animation {
case .none:
alphaTransition = .immediate
case let .curve(duration, _):
if animateIn {
if i == 0 {
if self.videoContainerViews.count > 1 && self.videoContainerViews[1].isFillingBounds {
alphaTransition = .immediate
} else {
alphaTransition = transition
}
} else {
alphaTransition = .easeInOut(duration: min(0.1, duration))
}
} else {
alphaTransition = transition
}
}
alphaTransition.setAlpha(view: videoContainerView, alpha: 1.0)
alphaTransition.setAlpha(layer: videoContainerView.blurredContainerLayer, alpha: 1.0)
}
var removedVideoContainerIndices: [Int] = []
for i in 0 ..< self.videoContainerViews.count {
let videoContainerView = self.videoContainerViews[i]
if !validVideoContainerKeys.contains(videoContainerView.key) {
removedVideoContainerIndices.append(i)
if self.videoContainerViews.count == 1 {
let alphaTransition: Transition = genericAlphaTransition
videoContainerView.update(size: avatarFrame.size, insets: minimizedVideoInsets, cornerRadius: avatarCornerRadius, controlsHidden: currentAreControlsHidden, isMinimized: false, isAnimatedOut: true, transition: transition)
transition.setPosition(layer: videoContainerView.blurredContainerLayer, position: avatarFrame.center)
transition.setBounds(layer: videoContainerView.blurredContainerLayer, bounds: CGRect(origin: CGPoint(), size: avatarFrame.size))
transition.setAlpha(layer: videoContainerView.blurredContainerLayer, alpha: 0.0)
transition.setPosition(view: videoContainerView, position: avatarFrame.center)
transition.setBounds(view: videoContainerView, bounds: CGRect(origin: CGPoint(), size: avatarFrame.size))
if videoContainerView.alpha != 0.0 {
alphaTransition.setAlpha(view: videoContainerView, alpha: 0.0, completion: { [weak videoContainerView] _ in
guard let videoContainerView else {
return
}
videoContainerView.removeFromSuperview()
videoContainerView.blurredContainerLayer.removeFromSuperlayer()
})
alphaTransition.setAlpha(layer: videoContainerView.blurredContainerLayer, alpha: 0.0)
}
} else if i == 0 {
let alphaTransition = genericAlphaTransition
alphaTransition.setAlpha(view: videoContainerView, alpha: 0.0, completion: { [weak videoContainerView] _ in
guard let videoContainerView else {
return
}
remoteVideoContainerView.removeFromSuperview()
remoteVideoContainerView.blurredContainerLayer.removeFromSuperlayer()
if self.remoteVideoContainerView === remoteVideoContainerView {
self.remoteVideoContainerView = nil
}
videoContainerView.removeFromSuperview()
videoContainerView.blurredContainerLayer.removeFromSuperlayer()
})
alphaTransition.setAlpha(layer: videoContainerView.blurredContainerLayer, alpha: 0.0)
} else {
let alphaTransition = genericAlphaTransition
alphaTransition.setAlpha(view: videoContainerView, alpha: 0.0, completion: { [weak videoContainerView] _ in
guard let videoContainerView else {
return
}
videoContainerView.removeFromSuperview()
videoContainerView.blurredContainerLayer.removeFromSuperlayer()
})
alphaTransition.setAlpha(layer: videoContainerView.blurredContainerLayer, alpha: 0.0)
videoContainerView.update(size: params.size, insets: minimizedVideoInsets, cornerRadius: params.screenCornerRadius, controlsHidden: currentAreControlsHidden, isMinimized: true, isAnimatedOut: true, transition: transition)
}
}
}
if let secondaryVideoSource {
let localVideoContainerView: VideoContainerView
if let current = self.localVideoContainerView {
localVideoContainerView = current
} else {
localVideoContainerView = VideoContainerView(frame: CGRect())
self.localVideoContainerView = localVideoContainerView
self.insertSubview(localVideoContainerView, belowSubview: self.overlayContentsView)
self.overlayContentsView.layer.addSublayer(localVideoContainerView.blurredContainerLayer)
localVideoContainerView.layer.position = self.avatarLayer.position
localVideoContainerView.layer.bounds = self.avatarLayer.bounds
localVideoContainerView.alpha = 0.0
localVideoContainerView.blurredContainerLayer.position = self.avatarLayer.position
localVideoContainerView.blurredContainerLayer.bounds = self.avatarLayer.bounds
localVideoContainerView.blurredContainerLayer.opacity = 0.0
localVideoContainerView.update(size: self.avatarLayer.bounds.size, insets: minimizedVideoInsets, cornerRadius: self.avatarLayer.params?.cornerRadius ?? 0.0, isMinimized: true, isAnimatingOut: false, transition: .immediate)
}
if localVideoContainerView.video !== secondaryVideoSource {
localVideoContainerView.video = secondaryVideoSource
}
transition.setPosition(view: localVideoContainerView, position: expandedVideoFrame.center)
transition.setBounds(view: localVideoContainerView, bounds: CGRect(origin: CGPoint(), size: expandedVideoFrame.size))
transition.setAlpha(view: localVideoContainerView, alpha: 1.0)
transition.setPosition(layer: localVideoContainerView.blurredContainerLayer, position: expandedVideoFrame.center)
transition.setBounds(layer: localVideoContainerView.blurredContainerLayer, bounds: CGRect(origin: CGPoint(), size: expandedVideoFrame.size))
transition.setAlpha(layer: localVideoContainerView.blurredContainerLayer, alpha: 1.0)
localVideoContainerView.update(size: expandedVideoFrame.size, insets: minimizedVideoInsets, cornerRadius: params.screenCornerRadius, isMinimized: true, isAnimatingOut: false, transition: transition)
} else {
if let localVideoContainerView = self.localVideoContainerView {
localVideoContainerView.update(size: avatarFrame.size, insets: minimizedVideoInsets, cornerRadius: avatarCornerRadius, isMinimized: false, isAnimatingOut: true, transition: transition)
transition.setPosition(layer: localVideoContainerView.blurredContainerLayer, position: avatarFrame.center)
transition.setBounds(layer: localVideoContainerView.blurredContainerLayer, bounds: CGRect(origin: CGPoint(), size: avatarFrame.size))
transition.setAlpha(layer: localVideoContainerView.blurredContainerLayer, alpha: 0.0)
transition.setPosition(view: localVideoContainerView, position: avatarFrame.center)
transition.setBounds(view: localVideoContainerView, bounds: CGRect(origin: CGPoint(), size: avatarFrame.size))
if localVideoContainerView.alpha != 0.0 {
transition.setAlpha(view: localVideoContainerView, alpha: 0.0, completion: { [weak self, weak localVideoContainerView] completed in
guard let self, let localVideoContainerView, completed else {
return
}
localVideoContainerView.removeFromSuperview()
localVideoContainerView.blurredContainerLayer.removeFromSuperlayer()
if self.localVideoContainerView === localVideoContainerView {
self.localVideoContainerView = nil
}
})
}
}
for index in removedVideoContainerIndices.reversed() {
self.videoContainerViews.remove(at: index)
}
if self.avatarLayer.image !== params.state.avatarImage {
@@ -577,7 +665,17 @@ public final class PrivateCallScreen: OverlayMaskContainerView {
}
transition.setPosition(layer: self.avatarLayer, position: avatarFrame.center)
transition.setBounds(layer: self.avatarLayer, bounds: CGRect(origin: CGPoint(), size: avatarFrame.size))
self.avatarLayer.update(size: collapsedAvatarFrame.size, isExpanded:havePrimaryVideo, cornerRadius: avatarCornerRadius, transition: transition)
self.avatarLayer.update(size: collapsedAvatarFrame.size, isExpanded: havePrimaryVideo, cornerRadius: avatarCornerRadius, transition: transition)
transition.setPosition(view: self.videoContainerBackgroundView, position: avatarFrame.center)
transition.setBounds(view: self.videoContainerBackgroundView, bounds: CGRect(origin: CGPoint(), size: avatarFrame.size))
transition.setAlpha(view: self.videoContainerBackgroundView, alpha: havePrimaryVideo ? 1.0 : 0.0)
self.videoContainerBackgroundView.update(cornerRadius: havePrimaryVideo ? params.screenCornerRadius : avatarCornerRadius, transition: transition)
transition.setPosition(view: self.overlayContentsVideoContainerBackgroundView, position: avatarFrame.center)
transition.setBounds(view: self.overlayContentsVideoContainerBackgroundView, bounds: CGRect(origin: CGPoint(), size: avatarFrame.size))
transition.setAlpha(view: self.overlayContentsVideoContainerBackgroundView, alpha: havePrimaryVideo ? 1.0 : 0.0)
self.overlayContentsVideoContainerBackgroundView.update(cornerRadius: havePrimaryVideo ? params.screenCornerRadius : avatarCornerRadius, transition: transition)
let blobFrame = CGRect(origin: CGPoint(x: floor(avatarFrame.midX - blobSize * 0.5), y: floor(avatarFrame.midY - blobSize * 0.5)), size: CGSize(width: blobSize, height: blobSize))
transition.setPosition(layer: self.blobLayer, position: CGPoint(x: blobFrame.midX, y: blobFrame.midY))
@@ -606,14 +704,6 @@ public final class PrivateCallScreen: OverlayMaskContainerView {
constrainedWidth: params.size.width - 16.0 * 2.0,
transition: transition
)
let titleFrame = CGRect(
origin: CGPoint(
x: (params.size.width - titleSize.width) * 0.5,
y: !havePrimaryVideo ? collapsedAvatarFrame.maxY + 39.0 : params.insets.top + 17.0
),
size: titleSize
)
transition.setFrame(view: self.titleView, frame: titleFrame)
let statusState: StatusView.State
switch params.state.lifecycleState {
@@ -661,6 +751,25 @@ public final class PrivateCallScreen: OverlayMaskContainerView {
}
let statusSize = self.statusView.update(state: statusState, transition: .immediate)
let titleY: CGFloat
if currentAreControlsHidden {
titleY = -8.0 - titleSize.height - statusSize.height
} else if havePrimaryVideo {
titleY = params.insets.top + 2.0
} else {
titleY = collapsedAvatarFrame.maxY + 39.0
}
let titleFrame = CGRect(
origin: CGPoint(
x: (params.size.width - titleSize.width) * 0.5,
y: titleY
),
size: titleSize
)
transition.setFrame(view: self.titleView, frame: titleFrame)
genericAlphaTransition.setAlpha(view: self.titleView, alpha: currentAreControlsHidden ? 0.0 : 1.0)
let statusFrame = CGRect(
origin: CGPoint(
x: (params.size.width - statusSize.width) * 0.5,
@@ -678,6 +787,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView {
}
} else {
transition.setFrame(view: self.statusView, frame: statusFrame)
genericAlphaTransition.setAlpha(view: self.statusView, alpha: currentAreControlsHidden ? 0.0 : 1.0)
}
if case let .active(activeState) = params.state.lifecycleState, activeState.signalInfo.quality <= 0.2 {
@@ -690,7 +800,13 @@ public final class PrivateCallScreen: OverlayMaskContainerView {
self.addSubview(weakSignalView)
}
let weakSignalSize = weakSignalView.update(constrainedSize: CGSize(width: params.size.width - 32.0, height: 100.0))
let weakSignalFrame = CGRect(origin: CGPoint(x: floor((params.size.width - weakSignalSize.width) * 0.5), y: statusFrame.maxY + (havePrimaryVideo ? 12.0 : 12.0)), size: weakSignalSize)
let weakSignalY: CGFloat
if currentAreControlsHidden {
weakSignalY = params.insets.top + 2.0
} else {
weakSignalY = statusFrame.maxY + (havePrimaryVideo ? 12.0 : 12.0)
}
let weakSignalFrame = CGRect(origin: CGPoint(x: floor((params.size.width - weakSignalSize.width) * 0.5), y: weakSignalY), size: weakSignalSize)
if weakSignalView.bounds.isEmpty {
weakSignalView.frame = weakSignalFrame
if !transition.animation.isImmediate {