mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
[WIP] Call UI
This commit is contained in:
parent
99d442cebc
commit
f702380082
@ -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 {
|
||||
|
@ -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))
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "locksettings (1).pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
@ -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
|
@ -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
|
||||
|
@ -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))
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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))
|
||||
|
@ -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"))
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user