[WIP] Call UI

This commit is contained in:
Isaac 2023-12-19 01:11:40 +04:00
parent 99d442cebc
commit f702380082
11 changed files with 332 additions and 60 deletions

View File

@ -61,11 +61,11 @@ public final class ViewController: UIViewController {
}
switch self.callState.lifecycleState {
case .connecting:
case .requesting:
self.callState.lifecycleState = .ringing
case .ringing:
self.callState.lifecycleState = .exchangingKeys
case .exchangingKeys:
self.callState.lifecycleState = .connecting
case .connecting:
self.callState.lifecycleState = .active(PrivateCallScreen.State.ActiveState(
startTime: Date().timeIntervalSince1970,
signalInfo: PrivateCallScreen.State.SignalInfo(quality: 1.0),
@ -74,6 +74,12 @@ public final class ViewController: UIViewController {
case var .active(activeState):
activeState.signalInfo.quality = activeState.signalInfo.quality == 1.0 ? 0.1 : 1.0
self.callState.lifecycleState = .active(activeState)
case .reconnecting:
self.callState.lifecycleState = .active(PrivateCallScreen.State.ActiveState(
startTime: Date().timeIntervalSince1970,
signalInfo: PrivateCallScreen.State.SignalInfo(quality: 1.0),
emojiKey: ["😂", "😘", "😍", "😊"]
))
case .terminated:
self.callState.lifecycleState = .active(PrivateCallScreen.State.ActiveState(
startTime: Date().timeIntervalSince1970,
@ -113,8 +119,8 @@ public final class ViewController: UIViewController {
}
if let input = self.callState.localVideo as? FileVideoSource {
input.sourceId = input.sourceId == 0 ? 1 : 0
input.fixedRotationAngle = input.sourceId == 0 ? Float.pi * 0.0 : Float.pi * 0.5
input.sizeMultiplicator = input.sourceId == 0 ? CGPoint(x: 1.0, y: 1.0) : CGPoint(x: 1.5, y: 1.0)
//input.fixedRotationAngle = input.sourceId == 0 ? Float.pi * 0.5 : Float.pi * 0.5
//input.sizeMultiplicator = input.sourceId == 0 ? CGPoint(x: 1.0, y: 1.0) : CGPoint(x: 1.0, y: 0.5)
}
}
callScreenView.videoAction = { [weak self] in
@ -130,7 +136,7 @@ public final class ViewController: UIViewController {
}
callScreenView.microhoneMuteAction = {
if self.callState.remoteVideo == nil {
self.callState.remoteVideo = FileVideoSource(device: MetalEngine.shared.device, url: Bundle.main.url(forResource: "test4", withExtension: "mp4")!, fixedRotationAngle: Float.pi * 0.0)
self.callState.remoteVideo = FileVideoSource(device: MetalEngine.shared.device, url: Bundle.main.url(forResource: "test4", withExtension: "mp4")!, fixedRotationAngle: Float.pi * 1.0)
} else {
self.callState.remoteVideo = nil
}
@ -140,7 +146,7 @@ public final class ViewController: UIViewController {
guard let self else {
return
}
self.callState.lifecycleState = .terminated(PrivateCallScreen.State.TerminatedState(duration: 82.0))
self.callState.lifecycleState = .terminated(PrivateCallScreen.State.TerminatedState(duration: 82.0, reason: .hangUp))
self.callState.remoteVideo = nil
self.callState.localVideo = nil
self.callState.isLocalAudioMuted = false
@ -151,9 +157,8 @@ public final class ViewController: UIViewController {
guard let self else {
return
}
//self.callState.isLocalAudioMuted = !self.callState.isLocalAudioMuted
//self.update(transition: .spring(duration: 0.4))
self.callScreenView?.beginPictureInPicture()
self.callState.isLocalAudioMuted = !self.callState.isLocalAudioMuted
self.update(transition: .spring(duration: 0.4))
}
callScreenView.closeAction = { [weak self] in
guard let self else {

View File

@ -323,8 +323,11 @@ public final class CallController: ViewController {
}
self.controllerNode.dismissedInteractively = { [weak self] in
self?.didPlayPresentationAnimation = false
self?.presentingViewController?.dismiss(animated: false, completion: nil)
guard let self else {
return
}
self.didPlayPresentationAnimation = false
self.presentingViewController?.dismiss(animated: false, completion: nil)
}
self.peerDisposable = (combineLatest(self.account.postbox.peerView(id: self.account.peerId) |> take(1), self.account.postbox.peerView(id: self.call.peerId), self.sharedContext.activeAccountsWithInfo |> take(1))

View File

@ -17,6 +17,7 @@ import AlertUI
import PresentationDataUtils
import DeviceAccess
import ContextUI
import AppBundle
private func interpolateFrame(from fromValue: CGRect, to toValue: CGRect, t: CGFloat) -> CGRect {
return CGRect(x: floorToScreenPixels(toValue.origin.x * t + fromValue.origin.x * (1.0 - t)), y: floorToScreenPixels(toValue.origin.y * t + fromValue.origin.y * (1.0 - t)), width: floorToScreenPixels(toValue.size.width * t + fromValue.size.width * (1.0 - t)), height: floorToScreenPixels(toValue.size.height * t + fromValue.size.height * (1.0 - t)))
@ -27,6 +28,8 @@ private func interpolate(from: CGFloat, to: CGFloat, value: CGFloat) -> CGFloat
}
final class CallVideoNode: ASDisplayNode, PreviewVideoNode {
private var placeholderImageNode: ASImageNode?
private let videoTransformContainer: ASDisplayNode
private let videoView: PresentationCallVideoView
@ -52,7 +55,7 @@ final class CallVideoNode: ASDisplayNode, PreviewVideoNode {
private var previousVideoHeight: CGFloat?
init(videoView: PresentationCallVideoView, disabledText: String?, assumeReadyAfterTimeout: Bool, isReadyUpdated: @escaping () -> Void, orientationUpdated: @escaping () -> Void, isFlippedUpdated: @escaping (CallVideoNode) -> Void) {
init(videoView: PresentationCallVideoView, displayPlaceholderUntilReady: Bool = false, disabledText: String?, assumeReadyAfterTimeout: Bool, isReadyUpdated: @escaping () -> Void, orientationUpdated: @escaping () -> Void, isFlippedUpdated: @escaping (CallVideoNode) -> Void) {
self.isReadyUpdated = isReadyUpdated
self.isFlippedUpdated = isFlippedUpdated
@ -80,6 +83,13 @@ final class CallVideoNode: ASDisplayNode, PreviewVideoNode {
self.videoTransformContainer.view.addSubview(self.videoView.view)
self.addSubnode(self.videoTransformContainer)
if displayPlaceholderUntilReady {
let placeholderImageNode = ASImageNode()
placeholderImageNode.image = UIImage(bundleImageName: "Camera/SelfiePlaceholder")
self.placeholderImageNode = placeholderImageNode
self.addSubnode(placeholderImageNode)
}
if let disabledText = disabledText {
self.videoPausedNode.attributedText = NSAttributedString(string: disabledText, font: Font.regular(17.0), textColor: .white)
self.addSubnode(self.videoPausedNode)
@ -95,6 +105,13 @@ final class CallVideoNode: ASDisplayNode, PreviewVideoNode {
strongSelf.readyPromise.set(true)
strongSelf.isReadyTimer?.invalidate()
strongSelf.isReadyUpdated()
if let placeholderImageNode = strongSelf.placeholderImageNode {
strongSelf.placeholderImageNode = nil
placeholderImageNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak placeholderImageNode] _ in
placeholderImageNode?.removeFromSupernode()
})
}
}
}
}
@ -191,6 +208,11 @@ final class CallVideoNode: ASDisplayNode, PreviewVideoNode {
func updateLayout(size: CGSize, cornerRadius: CGFloat, isOutgoing: Bool, deviceOrientation: UIDeviceOrientation, isCompactLayout: Bool, transition: ContainedViewLayoutTransition) {
self.currentCornerRadius = cornerRadius
if let placeholderImageNode = self.placeholderImageNode, let image = placeholderImageNode.image {
let placeholderSize = image.size.aspectFilled(size)
transition.updateFrame(node: placeholderImageNode, frame: CGRect(origin: CGPoint(x: (size.width - placeholderSize.width) * 0.5, y: (size.height - placeholderSize.height) * 0.5), size: placeholderSize))
}
var rotationAngle: CGFloat
if false && isOutgoing && isCompactLayout {
rotationAngle = CGFloat.pi / 2.0

View File

@ -20,6 +20,14 @@ import DeviceAccess
import LibYuvBinding
final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeProtocol {
private struct PanGestureState {
var offsetFraction: CGFloat
init(offsetFraction: CGFloat) {
self.offsetFraction = offsetFraction
}
}
private let sharedContext: SharedAccountContext
private let account: Account
private let presentationData: PresentationData
@ -64,6 +72,9 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
private var localVideo: AdaptedCallVideoSource?
private var remoteVideo: AdaptedCallVideoSource?
private var panGestureState: PanGestureState?
private var notifyDismissedInteractivelyOnPanGestureApply: Bool = false
init(
sharedContext: SharedAccountContext,
account: Account,
@ -80,6 +91,7 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
self.call = call
self.containerView = UIView()
self.containerView.clipsToBounds = true
self.callScreen = PrivateCallScreen()
super.init()
@ -174,6 +186,8 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
}
self.callScreen.addIncomingAudioLevel(value: audioLevel)
})
self.view.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:))))
}
deinit {
@ -257,7 +271,7 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
var updateLayoutImpl: ((ContainerViewLayout, CGFloat) -> Void)?
let outgoingVideoNode = CallVideoNode(videoView: outgoingVideoView, disabledText: nil, assumeReadyAfterTimeout: true, isReadyUpdated: { [weak self] in
let outgoingVideoNode = CallVideoNode(videoView: outgoingVideoView, displayPlaceholderUntilReady: true, disabledText: nil, assumeReadyAfterTimeout: true, isReadyUpdated: { [weak self] in
guard let self, let (layout, navigationBarHeight) = self.validLayout else {
return
}
@ -357,7 +371,11 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
case let .ended(type):
switch type {
case .missed:
mappedReason = .missed
if self.call.isOutgoing {
mappedReason = .hangUp
} else {
mappedReason = .missed
}
case .busy:
mappedReason = .busy
case .hungUp:
@ -517,6 +535,9 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
}
func animateIn() {
self.panGestureState = nil
self.update(transition: .immediate)
if !self.containerView.alpha.isZero {
var bounds = self.bounds
bounds.origin = CGPoint()
@ -548,7 +569,34 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
}
func expandFromPipIfPossible() {
}
@objc private func panGesture(_ recognizer: UIPanGestureRecognizer) {
switch recognizer.state {
case .began, .changed:
if !self.bounds.height.isZero && !self.notifyDismissedInteractivelyOnPanGestureApply {
let translation = recognizer.translation(in: self.view)
self.panGestureState = PanGestureState(offsetFraction: translation.y / self.bounds.height)
self.update(transition: .immediate)
}
case .cancelled, .ended:
if !self.bounds.height.isZero {
let translation = recognizer.translation(in: self.view)
let panGestureState = PanGestureState(offsetFraction: translation.y / self.bounds.height)
let velocity = recognizer.velocity(in: self.view)
self.panGestureState = nil
if abs(panGestureState.offsetFraction) > 0.6 || abs(velocity.y) >= 100.0 {
self.panGestureState = PanGestureState(offsetFraction: panGestureState.offsetFraction < 0.0 ? -1.0 : 1.0)
self.notifyDismissedInteractivelyOnPanGestureApply = true
}
self.update(transition: .animated(duration: 0.4, curve: .spring))
}
default:
break
}
}
private func update(transition: ContainedViewLayoutTransition) {
@ -561,7 +609,24 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
self.validLayout = (layout, navigationBarHeight)
transition.updateFrame(view: self.containerView, frame: CGRect(origin: CGPoint(), size: layout.size))
var containerOffset: CGFloat = 0.0
if let panGestureState = self.panGestureState {
containerOffset = panGestureState.offsetFraction * layout.size.height
self.containerView.layer.cornerRadius = layout.deviceMetrics.screenCornerRadius
}
transition.updateFrame(view: self.containerView, frame: CGRect(origin: CGPoint(x: 0.0, y: containerOffset), size: layout.size), completion: { [weak self] completed in
guard let self, completed else {
return
}
if self.panGestureState == nil {
self.containerView.layer.cornerRadius = 0.0
}
if self.notifyDismissedInteractivelyOnPanGestureApply {
self.notifyDismissedInteractivelyOnPanGestureApply = false
self.dismissedInteractively?()
}
})
transition.updateFrame(view: self.callScreen, frame: CGRect(origin: CGPoint(), size: layout.size))
if var callScreenState = self.callScreenState {

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "locksettings (1).pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,123 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm
0.000000 0.000000 0.000000 scn
0.000000 3.800000 m
0.000000 4.920105 0.000000 5.480157 0.217987 5.907981 c
0.409734 6.284305 0.715695 6.590266 1.092019 6.782013 c
1.519843 7.000000 2.079895 7.000000 3.200000 7.000000 c
5.800000 7.000000 l
6.920105 7.000000 7.480157 7.000000 7.907981 6.782013 c
8.284306 6.590266 8.590266 6.284305 8.782013 5.907981 c
9.000000 5.480157 9.000000 4.920105 9.000000 3.800000 c
9.000000 3.200000 l
9.000000 2.079895 9.000000 1.519843 8.782013 1.092019 c
8.590266 0.715695 8.284306 0.409734 7.907981 0.217987 c
7.480157 0.000000 6.920105 0.000000 5.800000 0.000000 c
3.200000 0.000000 l
2.079895 0.000000 1.519843 0.000000 1.092019 0.217987 c
0.715695 0.409734 0.409734 0.715695 0.217987 1.092019 c
0.000000 1.519843 0.000000 2.079895 0.000000 3.200000 c
0.000000 3.800000 l
h
f
n
Q
q
1.000000 0.000000 -0.000000 1.000000 2.000000 2.400391 cm
0.000000 0.000000 0.000000 scn
4.200000 1.599609 m
4.200000 1.157782 4.558172 0.799609 5.000000 0.799609 c
5.441828 0.799609 5.800000 1.157782 5.800000 1.599609 c
4.200000 1.599609 l
h
-0.800000 1.599609 m
-0.800000 1.157782 -0.441828 0.799609 -0.000000 0.799609 c
0.441828 0.799609 0.800000 1.157782 0.800000 1.599609 c
-0.800000 1.599609 l
h
4.200000 6.099609 m
4.200000 1.599609 l
5.800000 1.599609 l
5.800000 6.099609 l
4.200000 6.099609 l
h
0.800000 1.599609 m
0.800000 6.099609 l
-0.800000 6.099609 l
-0.800000 1.599609 l
0.800000 1.599609 l
h
2.500000 7.799609 m
3.438884 7.799609 4.200000 7.038493 4.200000 6.099609 c
5.800000 6.099609 l
5.800000 7.922149 4.322540 9.399610 2.500000 9.399610 c
2.500000 7.799609 l
h
2.500000 9.399610 m
0.677460 9.399610 -0.800000 7.922149 -0.800000 6.099609 c
0.800000 6.099609 l
0.800000 7.038493 1.561116 7.799609 2.500000 7.799609 c
2.500000 9.399610 l
h
f
n
Q
endstream
endobj
3 0 obj
1865
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 9.000000 12.000000 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Pages 5 0 R
/Type /Catalog
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000001955 00000 n
0000001978 00000 n
0000002150 00000 n
0000002224 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
2283
%%EOF

View File

@ -50,10 +50,12 @@ final class ButtonGroupView: OverlayMaskContainerView {
final class Notice {
let id: AnyHashable
let icon: String
let text: String
init(id: AnyHashable, text: String) {
init(id: AnyHashable, icon: String, text: String) {
self.id = id
self.icon = icon
self.text = text
}
}
@ -126,7 +128,7 @@ final class ButtonGroupView: OverlayMaskContainerView {
noticesHeight += buttonNoticeSpacing
}
}
let noticeSize = noticeView.update(text: notice.text, constrainedWidth: size.width - insets.left * 2.0 - 16.0 * 2.0, transition: noticeTransition)
let noticeSize = noticeView.update(icon: notice.icon, text: notice.text, constrainedWidth: size.width - insets.left * 2.0 - 16.0 * 2.0, transition: noticeTransition)
let noticeFrame = CGRect(origin: CGPoint(x: floor((size.width - noticeSize.width) * 0.5), y: nextNoticeY - noticeSize.height), size: noticeSize)
noticesHeight += noticeSize.height
nextNoticeY -= noticeSize.height + noticeSpacing

View File

@ -1,6 +1,7 @@
import Foundation
import UIKit
import Display
import AppBundle
private func addRoundedRectPath(context: CGContext, rect: CGRect, radius: CGFloat) {
context.saveGState()
@ -41,6 +42,7 @@ final class EmojiTooltipView: OverlayMaskContainerView {
private let text: String
private let backgroundView: UIImageView
private let iconView: UIImageView
private let textView: TextView
private var currentLayout: Layout?
@ -50,18 +52,18 @@ final class EmojiTooltipView: OverlayMaskContainerView {
self.backgroundView = UIImageView()
self.iconView = UIImageView()
self.textView = TextView()
super.init(frame: CGRect())
self.maskContents.addSubview(self.backgroundView)
self.addSubview(self.iconView)
self.addSubview(self.textView)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func animateIn() {
@ -92,9 +94,16 @@ final class EmojiTooltipView: OverlayMaskContainerView {
}
private func update(params: Params) -> CGSize {
let horizontalInset: CGFloat = 12.0
let horizontalInset: CGFloat = 13.0
let verticalInset: CGFloat = 10.0
let arrowHeight: CGFloat = 8.0
let iconSpacing: CGFloat = 5.0
if self.iconView.image == nil {
self.iconView.image = UIImage(bundleImageName: "Call/EmojiTooltipLock")?.withRenderingMode(.alwaysTemplate)
self.iconView.tintColor = .white
}
let iconSize = self.iconView.image?.size ?? CGSize(width: 12.0, height: 12.0)
let textSize = self.textView.update(
string: self.text,
@ -105,9 +114,11 @@ final class EmojiTooltipView: OverlayMaskContainerView {
transition: .immediate
)
let size = CGSize(width: textSize.width + horizontalInset * 2.0, height: arrowHeight + textSize.height + verticalInset * 2.0)
let size = CGSize(width: iconSize.width + iconSpacing + textSize.width + horizontalInset * 2.0, height: arrowHeight + textSize.height + verticalInset * 2.0)
self.textView.frame = CGRect(origin: CGPoint(x: horizontalInset, y: arrowHeight + verticalInset), size: textSize)
self.iconView.frame = CGRect(origin: CGPoint(x: horizontalInset, y: arrowHeight + verticalInset + floorToScreenPixels((textSize.height - iconSize.height) * 0.5)), size: iconSize)
self.textView.frame = CGRect(origin: CGPoint(x: horizontalInset + iconSize.width + iconSpacing, y: arrowHeight + verticalInset), size: textSize)
self.backgroundView.image = generateImage(size, rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))

View File

@ -2,16 +2,19 @@ import Foundation
import UIKit
import Display
import ComponentFlow
import AppBundle
final class NoticeView: OverlayMaskContainerView {
private let backgroundView: RoundedCornersView
private let textContainer: UIView
private let iconView: UIImageView
private let textView: TextView
override init(frame: CGRect) {
self.backgroundView = RoundedCornersView(color: .white)
self.textContainer = UIView()
self.textContainer.clipsToBounds = true
self.iconView = UIImageView()
self.textView = TextView()
super.init(frame: frame)
@ -20,6 +23,7 @@ final class NoticeView: OverlayMaskContainerView {
self.maskContents.addSubview(self.backgroundView)
self.textContainer.addSubview(self.iconView)
self.textContainer.addSubview(self.textView)
self.addSubview(self.textContainer)
}
@ -38,7 +42,9 @@ final class NoticeView: OverlayMaskContainerView {
self.backgroundView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
self.backgroundView.layer.animateFrame(from: CGRect(origin: CGPoint(x: (self.bounds.width - self.bounds.height) * 0.5, y: 0.0), size: CGSize(width: self.bounds.height, height: self.bounds.height)), to: self.backgroundView.frame, duration: 0.5, delay: delay, timingFunction: kCAMediaTimingFunctionSpring)
self.textContainer.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, delay: delay)
self.iconView.layer.animatePosition(from: CGPoint(x: -4.0, y: 0.0), to: CGPoint(), duration: 0.15, delay: delay, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
//self.textContainer.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, delay: delay)
self.textContainer.layer.cornerRadius = self.bounds.height * 0.5
self.textContainer.layer.animateFrame(from: CGRect(origin: CGPoint(x: (self.bounds.width - self.bounds.height) * 0.5, y: 0.0), size: CGSize(width: self.bounds.height, height: self.bounds.height)), to: self.textContainer.frame, duration: 0.5, delay: delay, timingFunction: kCAMediaTimingFunctionSpring, completion: { [weak self] completed in
guard let self, completed else {
@ -55,18 +61,28 @@ final class NoticeView: OverlayMaskContainerView {
self.layer.animateScale(from: 1.0, to: 0.001, duration: 0.2, removeOnCompletion: false)
}
func update(text: String, constrainedWidth: CGFloat, transition: Transition) -> CGSize {
func update(icon: String, text: String, constrainedWidth: CGFloat, transition: Transition) -> CGSize {
let sideInset: CGFloat = 12.0
let verticalInset: CGFloat = 6.0
let iconSpacing: CGFloat = -3.0
if self.iconView.image == nil {
self.iconView.image = UIImage(bundleImageName: icon)?.withRenderingMode(.alwaysTemplate)
self.iconView.tintColor = .white
}
let iconSize = self.iconView.image?.size ?? CGSize(width: 12.0, height: 12.0)
let textSize = self.textView.update(string: text, fontSize: 15.0, fontWeight: 0.0, color: .white, constrainedWidth: constrainedWidth - sideInset * 2.0, transition: .immediate)
let size = CGSize(width: textSize.width + sideInset * 2.0, height: textSize.height + verticalInset * 2.0)
let size = CGSize(width: iconSize.width + iconSpacing + textSize.width + sideInset * 2.0, height: textSize.height + verticalInset * 2.0)
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: size))
self.backgroundView.update(cornerRadius: floor(size.height * 0.5), transition: transition)
transition.setFrame(view: self.textContainer, frame: CGRect(origin: CGPoint(), size: size))
transition.setFrame(view: self.textView, frame: CGRect(origin: CGPoint(x: sideInset, y: verticalInset), size: textSize))
transition.setFrame(view: self.iconView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.height - iconSize.height) * 0.5) + 4.0, y: verticalInset + floorToScreenPixels((textSize.height - iconSize.height) * 0.5)), size: iconSize))
transition.setFrame(view: self.textView, frame: CGRect(origin: CGPoint(x: sideInset + iconSize.width + iconSpacing, y: verticalInset), size: textSize))
return size
}

View File

@ -488,6 +488,7 @@ final class VideoContainerView: HighlightTrackingButton {
private struct MinimizedLayout {
var videoIsRotated: Bool
var videoSize: CGSize
var rotatedVideoSize: CGSize
var rotatedVideoResolution: CGSize
var rotatedVideoFrame: CGRect
@ -538,6 +539,7 @@ final class VideoContainerView: HighlightTrackingButton {
return MinimizedLayout(
videoIsRotated: videoIsRotated,
videoSize: videoSize,
rotatedVideoSize: rotatedVideoSize,
rotatedVideoResolution: rotatedVideoResolution,
rotatedVideoFrame: rotatedVideoFrame,
@ -563,20 +565,20 @@ final class VideoContainerView: HighlightTrackingButton {
let videoLayout = self.calculateMinimizedLayout(params: params, videoMetrics: videoMetrics, resolvedRotationAngle: resolvedRotationAngle, applyDragPosition: true)
transition.setPosition(layer: self.videoContainerLayer, position: videoLayout.rotatedVideoFrame.center)
transition.setPosition(layer: self.videoContainerLayer, position: videoLayout.effectiveVideoFrame.center)
self.videoContainerLayer.contentsLayer.masksToBounds = true
if self.disappearingVideoLayer != nil {
self.videoContainerLayer.contentsLayer.backgroundColor = UIColor.black.cgColor
}
transition.setBounds(layer: self.videoContainerLayer, bounds: CGRect(origin: CGPoint(), size: videoLayout.rotatedVideoSize), completion: { [weak self] completed in
transition.setBounds(layer: self.videoContainerLayer, bounds: CGRect(origin: CGPoint(), size: videoLayout.effectiveVideoFrame.size), completion: { [weak self] completed in
guard let self, completed else {
return
}
self.videoContainerLayer.contentsLayer.masksToBounds = false
self.videoContainerLayer.contentsLayer.backgroundColor = nil
})
self.videoContainerLayer.update(size: videoLayout.rotatedVideoSize, transition: transition)
self.videoContainerLayer.update(size: videoLayout.effectiveVideoFrame.size, transition: transition)
var videoTransition = transition
if self.videoLayer.bounds.isEmpty {
@ -587,30 +589,18 @@ final class VideoContainerView: HighlightTrackingButton {
self.disappearingVideoLayer = nil
let disappearingVideoLayout = self.calculateMinimizedLayout(params: params, videoMetrics: disappearingVideoLayer.videoMetrics, resolvedRotationAngle: resolveVideoRotationAngle(angle: disappearingVideoLayer.videoMetrics.rotationAngle, followsDeviceOrientation: disappearingVideoLayer.videoMetrics.followsDeviceOrientation, interfaceOrientation: params.interfaceOrientation), applyDragPosition: true)
let initialDisapparingVideoSize = disappearingVideoLayout.rotatedVideoSize
let initialDisappearingVideoSize = disappearingVideoLayout.effectiveVideoFrame.size
if !disappearingVideoLayer.isAlphaAnimationInitiated {
disappearingVideoLayer.isAlphaAnimationInitiated = true
if let flipAnimationInfo = disappearingVideoLayer.flipAnimationInfo {
let resolvedPreviousRotationAngle = resolveVideoRotationAngle(angle: flipAnimationInfo.previousRotationAngle, followsDeviceOrientation: flipAnimationInfo.followsDeviceOrientation, interfaceOrientation: params.interfaceOrientation)
var videoTransform = self.videoContainerLayer.transform
var axis: (x: CGFloat, y: CGFloat, z: CGFloat) = (0.0, 0.0, 0.0)
let previousVideoScale: CGPoint
if resolvedPreviousRotationAngle == Float.pi * 0.5 {
axis.x = -1.0
previousVideoScale = CGPoint(x: 1.0, y: -1.0)
} else if resolvedPreviousRotationAngle == Float.pi {
axis.y = -1.0
previousVideoScale = CGPoint(x: -1.0, y: -1.0)
} else if resolvedPreviousRotationAngle == Float.pi * 3.0 / 2.0 {
axis.x = 1.0
previousVideoScale = CGPoint(x: 1.0, y: 1.0)
} else {
axis.y = 1.0
previousVideoScale = CGPoint(x: -1.0, y: 1.0)
}
axis.y = 1.0
previousVideoScale = CGPoint(x: -1.0, y: 1.0)
videoTransform = CATransform3DRotate(videoTransform, (flipAnimationInfo.isForward ? 1.0 : -1.0) * CGFloat.pi * 0.9999, axis.x, axis.y, axis.z)
self.videoContainerLayer.transform = videoTransform
@ -618,7 +608,7 @@ final class VideoContainerView: HighlightTrackingButton {
disappearingVideoLayer.videoLayer.zPosition = 1.0
transition.setZPosition(layer: disappearingVideoLayer.videoLayer, zPosition: -1.0)
disappearingVideoLayer.videoLayer.transform = CATransform3DMakeScale(previousVideoScale.x, previousVideoScale.y, 1.0)
disappearingVideoLayer.videoLayer.transform = CATransform3DConcat(disappearingVideoLayout.videoTransform, CATransform3DMakeScale(previousVideoScale.x, previousVideoScale.y, 1.0))
animateFlipDisappearingVideo = disappearingVideoLayer
disappearingVideoLayer.videoLayer.blurredLayer.removeFromSuperlayer()
@ -640,13 +630,27 @@ final class VideoContainerView: HighlightTrackingButton {
self.videoLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
let mappedDisappearingSize: CGSize
if videoLayout.videoIsRotated {
mappedDisappearingSize = CGSize(width: initialDisappearingVideoSize.height, height: initialDisappearingVideoSize.width)
} else {
mappedDisappearingSize = initialDisappearingVideoSize
}
self.videoLayer.position = disappearingVideoLayer.videoLayer.position
self.videoLayer.bounds = CGRect(origin: CGPoint(), size: videoLayout.rotatedVideoSize.aspectFilled(initialDisapparingVideoSize))
self.videoLayer.bounds = CGRect(origin: CGPoint(), size: videoLayout.rotatedVideoSize.aspectFilled(mappedDisappearingSize))
self.videoLayer.blurredLayer.position = disappearingVideoLayer.videoLayer.blurredLayer.position
self.videoLayer.blurredLayer.bounds = CGRect(origin: CGPoint(), size: videoLayout.rotatedVideoSize.aspectFilled(initialDisapparingVideoSize))
self.videoLayer.blurredLayer.bounds = CGRect(origin: CGPoint(), size: videoLayout.rotatedVideoSize.aspectFilled(mappedDisappearingSize))
}
let disappearingVideoSize = initialDisapparingVideoSize.aspectFilled(videoLayout.rotatedVideoSize)
let disappearingFitVideoSize: CGSize
if disappearingVideoLayout.videoIsRotated {
disappearingFitVideoSize = CGSize(width: videoLayout.effectiveVideoFrame.size.height, height: videoLayout.effectiveVideoFrame.size.width)
} else {
disappearingFitVideoSize = videoLayout.effectiveVideoFrame.size
}
let disappearingVideoSize = initialDisappearingVideoSize.aspectFilled(disappearingFitVideoSize)
transition.setPosition(layer: disappearingVideoLayer.videoLayer, position: CGPoint(x: videoLayout.rotatedVideoSize.width * 0.5, y: videoLayout.rotatedVideoSize.height * 0.5))
transition.setBounds(layer: disappearingVideoLayer.videoLayer, bounds: CGRect(origin: CGPoint(), size: disappearingVideoSize))
transition.setPosition(layer: disappearingVideoLayer.videoLayer.blurredLayer, position: videoLayout.rotatedVideoFrame.center)
@ -654,12 +658,13 @@ final class VideoContainerView: HighlightTrackingButton {
}
let animateFlipDisappearingVideoLayer = animateFlipDisappearingVideo?.videoLayer
transition.setTransform(layer: self.videoContainerLayer, transform: videoLayout.videoTransform, completion: { [weak animateFlipDisappearingVideoLayer] _ in
transition.setTransform(layer: self.videoContainerLayer, transform: CATransform3DIdentity, completion: { [weak animateFlipDisappearingVideoLayer] _ in
animateFlipDisappearingVideoLayer?.removeFromSuperlayer()
})
transition.setPosition(layer: self.videoLayer, position: CGPoint(x: videoLayout.rotatedVideoSize.width * 0.5, y: videoLayout.rotatedVideoSize.height * 0.5))
transition.setPosition(layer: self.videoLayer, position: CGPoint(x: videoLayout.videoSize.width * 0.5, y: videoLayout.videoSize.height * 0.5))
transition.setBounds(layer: self.videoLayer, bounds: CGRect(origin: CGPoint(), size: videoLayout.rotatedVideoSize))
videoTransition.setTransform(layer: self.videoLayer, transform: videoLayout.videoTransform)
transition.setPosition(layer: self.videoLayer.blurredLayer, position: videoLayout.rotatedVideoFrame.center)
transition.setAlpha(layer: self.videoLayer.blurredLayer, alpha: 0.0)
@ -740,12 +745,19 @@ final class VideoContainerView: HighlightTrackingButton {
}
}
let videoFrame = rotatedVideoSize.centered(around: CGPoint(x: rotatedBoundingSize.width * 0.5, y: rotatedBoundingSize.height * 0.5))
if let disappearingVideoLayer = self.disappearingVideoLayer {
self.disappearingVideoLayer = nil
if !disappearingVideoLayer.isAlphaAnimationInitiated {
disappearingVideoLayer.isAlphaAnimationInitiated = true
self.videoLayer.position = disappearingVideoLayer.videoLayer.position
transition.setPosition(layer: disappearingVideoLayer.videoLayer, position: videoFrame.center)
transition.setPosition(layer: disappearingVideoLayer.videoLayer.blurredLayer, position: videoFrame.center)
let alphaTransition: Transition = .easeInOut(duration: 0.2)
let disappearingVideoLayerValue = disappearingVideoLayer.videoLayer
alphaTransition.setAlpha(layer: disappearingVideoLayerValue, alpha: 0.0, completion: { [weak disappearingVideoLayerValue] _ in
@ -758,10 +770,11 @@ final class VideoContainerView: HighlightTrackingButton {
}
}
transition.setTransform(layer: self.videoContainerLayer, transform: CATransform3DMakeRotation(CGFloat(resolvedRotationAngle), 0.0, 0.0, 1.0))
transition.setPosition(layer: self.videoLayer, position: videoFrame.center)
videoTransition.setBounds(layer: self.videoLayer, bounds: CGRect(origin: CGPoint(), size: videoFrame.size))
videoTransition.setTransform(layer: self.videoLayer, transform: CATransform3DMakeRotation(CGFloat(resolvedRotationAngle), 0.0, 0.0, 1.0))
videoTransition.setFrame(layer: self.videoLayer, frame: rotatedVideoSize.centered(around: CGPoint(x: rotatedBoundingSize.width * 0.5, y: rotatedBoundingSize.height * 0.5)))
videoTransition.setPosition(layer: self.videoLayer.blurredLayer, position: rotatedVideoFrame.center)
transition.setPosition(layer: self.videoLayer.blurredLayer, position: rotatedVideoFrame.center)
videoTransition.setBounds(layer: self.videoLayer.blurredLayer, bounds: CGRect(origin: CGPoint(), size: rotatedVideoFrame.size))
videoTransition.setAlpha(layer: self.videoLayer.blurredLayer, alpha: 1.0)
videoTransition.setTransform(layer: self.videoLayer.blurredLayer, transform: CATransform3DMakeRotation(CGFloat(resolvedRotationAngle), 0.0, 0.0, 1.0))

View File

@ -715,16 +715,16 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
var notices: [ButtonGroupView.Notice] = []
if !isTerminated {
if params.state.isLocalAudioMuted {
notices.append(ButtonGroupView.Notice(id: AnyHashable(0 as Int), text: "Your microphone is turned off"))
notices.append(ButtonGroupView.Notice(id: AnyHashable(0 as Int), icon: "Call/CallToastMicrophone", text: "Your microphone is turned off"))
}
if params.state.isRemoteAudioMuted {
notices.append(ButtonGroupView.Notice(id: AnyHashable(1 as Int), text: "\(params.state.shortName)'s microphone is turned off"))
notices.append(ButtonGroupView.Notice(id: AnyHashable(1 as Int), icon: "Call/CallToastMicrophone", text: "\(params.state.shortName)'s microphone is turned off"))
}
if params.state.remoteVideo != nil && params.state.localVideo == nil {
notices.append(ButtonGroupView.Notice(id: AnyHashable(2 as Int), text: "Your camera is turned off"))
notices.append(ButtonGroupView.Notice(id: AnyHashable(2 as Int), icon: "Call/CallToastCamera", text: "Your camera is turned off"))
}
if params.state.isRemoteBatteryLow {
notices.append(ButtonGroupView.Notice(id: AnyHashable(3 as Int), text: "\(params.state.shortName)'s battery is low"))
notices.append(ButtonGroupView.Notice(id: AnyHashable(3 as Int), icon: "Call/CallToastBattery", text: "\(params.state.shortName)'s battery is low"))
}
}