mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-23 06:35:51 +00:00
[WIP] Call UI
This commit is contained in:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user