mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-23 06:35:51 +00:00
Video Chat Improvements
This commit is contained in:
@@ -623,11 +623,13 @@ public final class AvatarNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func drawPeerAvatarLetters(context: CGContext, size: CGSize, font: UIFont, letters: [String], peerId: PeerId) {
|
public func drawPeerAvatarLetters(context: CGContext, size: CGSize, round: Bool = true, font: UIFont, letters: [String], peerId: PeerId) {
|
||||||
|
if round {
|
||||||
context.beginPath()
|
context.beginPath()
|
||||||
context.addEllipse(in: CGRect(x: 0.0, y: 0.0, width: size.width, height:
|
context.addEllipse(in: CGRect(x: 0.0, y: 0.0, width: size.width, height:
|
||||||
size.height))
|
size.height))
|
||||||
context.clip()
|
context.clip()
|
||||||
|
}
|
||||||
|
|
||||||
let colorIndex: Int
|
let colorIndex: Int
|
||||||
if peerId.namespace == .max {
|
if peerId.namespace == .max {
|
||||||
|
|||||||
@@ -87,9 +87,9 @@ public func peerAvatarImageData(account: Account, peerReference: PeerReference?,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func peerAvatarCompleteImage(account: Account, peer: Peer, size: CGSize, font: UIFont = avatarPlaceholderFont(size: 13.0), fullSize: Bool = false) -> Signal<UIImage?, NoError> {
|
public func peerAvatarCompleteImage(account: Account, peer: Peer, size: CGSize, round: Bool = true, font: UIFont = avatarPlaceholderFont(size: 13.0), drawLetters: Bool = true, fullSize: Bool = false) -> Signal<UIImage?, NoError> {
|
||||||
let iconSignal: Signal<UIImage?, NoError>
|
let iconSignal: Signal<UIImage?, NoError>
|
||||||
if let signal = peerAvatarImage(account: account, peerReference: PeerReference(peer), authorOfMessage: nil, representation: peer.profileImageRepresentations.first, displayDimensions: size, inset: 0.0, emptyColor: nil, synchronousLoad: fullSize) {
|
if let signal = peerAvatarImage(account: account, peerReference: PeerReference(peer), authorOfMessage: nil, representation: peer.profileImageRepresentations.first, displayDimensions: size, round: round, inset: 0.0, emptyColor: nil, synchronousLoad: fullSize) {
|
||||||
if fullSize, let fullSizeSignal = peerAvatarImage(account: account, peerReference: PeerReference(peer), authorOfMessage: nil, representation: peer.profileImageRepresentations.last, displayDimensions: size, emptyColor: nil, synchronousLoad: true) {
|
if fullSize, let fullSizeSignal = peerAvatarImage(account: account, peerReference: PeerReference(peer), authorOfMessage: nil, representation: peer.profileImageRepresentations.last, displayDimensions: size, emptyColor: nil, synchronousLoad: true) {
|
||||||
iconSignal = combineLatest(.single(nil) |> then(signal), .single(nil) |> then(fullSizeSignal))
|
iconSignal = combineLatest(.single(nil) |> then(signal), .single(nil) |> then(fullSizeSignal))
|
||||||
|> mapToSignal { thumbnailImage, fullSizeImage -> Signal<UIImage?, NoError> in
|
|> mapToSignal { thumbnailImage, fullSizeImage -> Signal<UIImage?, NoError> in
|
||||||
@@ -113,10 +113,13 @@ public func peerAvatarCompleteImage(account: Account, peer: Peer, size: CGSize,
|
|||||||
if displayLetters.count == 2 && displayLetters[0].isSingleEmoji && displayLetters[1].isSingleEmoji {
|
if displayLetters.count == 2 && displayLetters[0].isSingleEmoji && displayLetters[1].isSingleEmoji {
|
||||||
displayLetters = [displayLetters[0]]
|
displayLetters = [displayLetters[0]]
|
||||||
}
|
}
|
||||||
|
if !drawLetters {
|
||||||
|
displayLetters = []
|
||||||
|
}
|
||||||
iconSignal = Signal { subscriber in
|
iconSignal = Signal { subscriber in
|
||||||
let image = generateImage(size, rotatedContext: { size, context in
|
let image = generateImage(size, rotatedContext: { size, context in
|
||||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
drawPeerAvatarLetters(context: context, size: CGSize(width: size.width, height: size.height), font: font, letters: displayLetters, peerId: peerId)
|
drawPeerAvatarLetters(context: context, size: CGSize(width: size.width, height: size.height), round: round, font: font, letters: displayLetters, peerId: peerId)
|
||||||
})?.withRenderingMode(.alwaysOriginal)
|
})?.withRenderingMode(.alwaysOriginal)
|
||||||
|
|
||||||
subscriber.putNext(image)
|
subscriber.putNext(image)
|
||||||
|
|||||||
@@ -132,6 +132,14 @@ private final class CallVideoNode: ASDisplayNode {
|
|||||||
self.isReadyTimer?.invalidate()
|
self.isReadyTimer?.invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func didLoad() {
|
||||||
|
super.didLoad()
|
||||||
|
|
||||||
|
if #available(iOS 13.0, *) {
|
||||||
|
self.layer.cornerCurve = .continuous
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func animateRadialMask(from fromRect: CGRect, to toRect: CGRect) {
|
func animateRadialMask(from fromRect: CGRect, to toRect: CGRect) {
|
||||||
let maskLayer = CAShapeLayer()
|
let maskLayer = CAShapeLayer()
|
||||||
maskLayer.frame = fromRect
|
maskLayer.frame = fromRect
|
||||||
|
|||||||
@@ -222,9 +222,6 @@ private class CallControllerToastItemNode: ASDisplayNode {
|
|||||||
self.clipNode = ASDisplayNode()
|
self.clipNode = ASDisplayNode()
|
||||||
self.clipNode.clipsToBounds = true
|
self.clipNode.clipsToBounds = true
|
||||||
self.clipNode.layer.cornerRadius = 14.0
|
self.clipNode.layer.cornerRadius = 14.0
|
||||||
if #available(iOS 13.0, *) {
|
|
||||||
self.clipNode.layer.cornerCurve = .continuous
|
|
||||||
}
|
|
||||||
|
|
||||||
self.effectView = UIVisualEffectView()
|
self.effectView = UIVisualEffectView()
|
||||||
self.effectView.effect = UIBlurEffect(style: .light)
|
self.effectView.effect = UIBlurEffect(style: .light)
|
||||||
@@ -248,6 +245,14 @@ private class CallControllerToastItemNode: ASDisplayNode {
|
|||||||
self.clipNode.addSubnode(self.textNode)
|
self.clipNode.addSubnode(self.textNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func didLoad() {
|
||||||
|
super.didLoad()
|
||||||
|
|
||||||
|
if #available(iOS 13.0, *) {
|
||||||
|
self.clipNode.layer.cornerCurve = .continuous
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func update(width: CGFloat, content: Content, transition: ContainedViewLayoutTransition) -> CGFloat {
|
func update(width: CGFloat, content: Content, transition: ContainedViewLayoutTransition) -> CGFloat {
|
||||||
let inset: CGFloat = 30.0
|
let inset: CGFloat = 30.0
|
||||||
let isNarrowScreen = width <= 320.0
|
let isNarrowScreen = width <= 320.0
|
||||||
|
|||||||
@@ -688,7 +688,7 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
strongSelf.audioLevelView = audioLevelView
|
strongSelf.audioLevelView = audioLevelView
|
||||||
strongSelf.offsetContainerNode.view.insertSubview(audioLevelView, at: 0)
|
strongSelf.offsetContainerNode.view.insertSubview(audioLevelView, at: 0)
|
||||||
|
|
||||||
if let item = strongSelf.item, strongSelf.videoNode != nil || item.active {
|
if let item = strongSelf.item, strongSelf.videoNode != nil && !item.active {
|
||||||
audioLevelView.alpha = 0.0
|
audioLevelView.alpha = 0.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,12 +48,12 @@ final class VoiceChatMainStageNode: ASDisplayNode {
|
|||||||
private let audioLevelDisposable = MetaDisposable()
|
private let audioLevelDisposable = MetaDisposable()
|
||||||
private let speakingPeerDisposable = MetaDisposable()
|
private let speakingPeerDisposable = MetaDisposable()
|
||||||
private let speakingAudioLevelDisposable = MetaDisposable()
|
private let speakingAudioLevelDisposable = MetaDisposable()
|
||||||
private var avatarNode: ASImageNode
|
private var backdropAvatarNode: ImageNode
|
||||||
|
private var backdropEffectView: UIVisualEffectView?
|
||||||
|
private var avatarNode: ImageNode
|
||||||
private let titleNode: ImmediateTextNode
|
private let titleNode: ImmediateTextNode
|
||||||
private let microphoneNode: VoiceChatMicrophoneNode
|
private let microphoneNode: VoiceChatMicrophoneNode
|
||||||
|
|
||||||
private let avatarDisposable = MetaDisposable()
|
|
||||||
|
|
||||||
private let speakingContainerNode: ASDisplayNode
|
private let speakingContainerNode: ASDisplayNode
|
||||||
private var speakingEffectView: UIVisualEffectView?
|
private var speakingEffectView: UIVisualEffectView?
|
||||||
private let speakingAvatarNode: AvatarNode
|
private let speakingAvatarNode: AvatarNode
|
||||||
@@ -129,7 +129,12 @@ final class VoiceChatMainStageNode: ASDisplayNode {
|
|||||||
self.pinButtonTitleNode.attributedText = NSAttributedString(string: "Unpin", font: Font.regular(17.0), textColor: .white)
|
self.pinButtonTitleNode.attributedText = NSAttributedString(string: "Unpin", font: Font.regular(17.0), textColor: .white)
|
||||||
self.pinButtonNode = HighlightableButtonNode()
|
self.pinButtonNode = HighlightableButtonNode()
|
||||||
|
|
||||||
self.avatarNode = ASImageNode()
|
self.backdropAvatarNode = ImageNode()
|
||||||
|
self.backdropAvatarNode.contentMode = .scaleAspectFill
|
||||||
|
self.backdropAvatarNode.displaysAsynchronously = false
|
||||||
|
self.backdropAvatarNode.isHidden = true
|
||||||
|
|
||||||
|
self.avatarNode = ImageNode()
|
||||||
self.avatarNode.displaysAsynchronously = false
|
self.avatarNode.displaysAsynchronously = false
|
||||||
self.avatarNode.isHidden = true
|
self.avatarNode.isHidden = true
|
||||||
|
|
||||||
@@ -152,14 +157,12 @@ final class VoiceChatMainStageNode: ASDisplayNode {
|
|||||||
|
|
||||||
self.clipsToBounds = true
|
self.clipsToBounds = true
|
||||||
self.cornerRadius = backgroundCornerRadius
|
self.cornerRadius = backgroundCornerRadius
|
||||||
if #available(iOS 13.0, *) {
|
|
||||||
self.layer.cornerCurve = .continuous
|
|
||||||
}
|
|
||||||
|
|
||||||
self.addSubnode(self.backgroundNode)
|
self.addSubnode(self.backgroundNode)
|
||||||
self.addSubnode(self.topFadeNode)
|
self.addSubnode(self.topFadeNode)
|
||||||
self.addSubnode(self.bottomFadeNode)
|
self.addSubnode(self.bottomFadeNode)
|
||||||
self.addSubnode(self.bottomFillNode)
|
self.addSubnode(self.bottomFillNode)
|
||||||
|
self.addSubnode(self.backdropAvatarNode)
|
||||||
self.addSubnode(self.avatarNode)
|
self.addSubnode(self.avatarNode)
|
||||||
self.addSubnode(self.titleNode)
|
self.addSubnode(self.titleNode)
|
||||||
self.addSubnode(self.microphoneNode)
|
self.addSubnode(self.microphoneNode)
|
||||||
@@ -215,7 +218,6 @@ final class VoiceChatMainStageNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
self.avatarDisposable.dispose()
|
|
||||||
self.videoReadyDisposable.dispose()
|
self.videoReadyDisposable.dispose()
|
||||||
self.audioLevelDisposable.dispose()
|
self.audioLevelDisposable.dispose()
|
||||||
self.speakingPeerDisposable.dispose()
|
self.speakingPeerDisposable.dispose()
|
||||||
@@ -226,10 +228,25 @@ final class VoiceChatMainStageNode: ASDisplayNode {
|
|||||||
override func didLoad() {
|
override func didLoad() {
|
||||||
super.didLoad()
|
super.didLoad()
|
||||||
|
|
||||||
|
if #available(iOS 13.0, *) {
|
||||||
|
self.layer.cornerCurve = .continuous
|
||||||
|
}
|
||||||
|
|
||||||
let speakingEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
|
let speakingEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
|
||||||
self.speakingContainerNode.view.insertSubview(speakingEffectView, at: 0)
|
self.speakingContainerNode.view.insertSubview(speakingEffectView, at: 0)
|
||||||
self.speakingEffectView = speakingEffectView
|
self.speakingEffectView = speakingEffectView
|
||||||
|
|
||||||
|
let effect: UIVisualEffect
|
||||||
|
if #available(iOS 13.0, *) {
|
||||||
|
effect = UIBlurEffect(style: .systemMaterialDark)
|
||||||
|
} else {
|
||||||
|
effect = UIBlurEffect(style: .dark)
|
||||||
|
}
|
||||||
|
let backdropEffectView = UIVisualEffectView(effect: effect)
|
||||||
|
backdropEffectView.isHidden = true
|
||||||
|
self.view.insertSubview(backdropEffectView, aboveSubview: self.backdropAvatarNode.view)
|
||||||
|
self.backdropEffectView = backdropEffectView
|
||||||
|
|
||||||
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tap)))
|
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tap)))
|
||||||
|
|
||||||
self.speakingContainerNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.speakingTap)))
|
self.speakingContainerNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.speakingTap)))
|
||||||
@@ -476,12 +493,8 @@ final class VoiceChatMainStageNode: ASDisplayNode {
|
|||||||
if !arePeersEqual(previousPeerEntry?.peer, peerEntry.peer) {
|
if !arePeersEqual(previousPeerEntry?.peer, peerEntry.peer) {
|
||||||
let peer = peerEntry.peer
|
let peer = peerEntry.peer
|
||||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||||
self.avatarDisposable.set((peerAvatarCompleteImage(account: self.context.account, peer: peer, size: CGSize(width: 180.0, height: 180.0), font: avatarPlaceholderFont(size: 78.0), fullSize: true)
|
self.backdropAvatarNode.setSignal(peerAvatarCompleteImage(account: self.context.account, peer: peer, size: CGSize(width: 180.0, height: 180.0), round: false, font: avatarPlaceholderFont(size: 78.0), drawLetters: false))
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] image in
|
self.avatarNode.setSignal(peerAvatarCompleteImage(account: self.context.account, peer: peer, size: CGSize(width: 180.0, height: 180.0), font: avatarPlaceholderFont(size: 78.0), fullSize: true))
|
||||||
if let strongSelf = self {
|
|
||||||
strongSelf.avatarNode.image = image
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
self.titleNode.attributedText = NSAttributedString(string: peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), font: Font.semibold(15.0), textColor: .white)
|
self.titleNode.attributedText = NSAttributedString(string: peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), font: Font.semibold(15.0), textColor: .white)
|
||||||
if let (size, sideInset, bottomInset, isLandscape) = self.validLayout {
|
if let (size, sideInset, bottomInset, isLandscape) = self.validLayout {
|
||||||
self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, isLandscape: isLandscape, transition: .immediate)
|
self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, isLandscape: isLandscape, transition: .immediate)
|
||||||
@@ -581,6 +594,13 @@ final class VoiceChatMainStageNode: ASDisplayNode {
|
|||||||
self.microphoneNode.update(state: VoiceChatMicrophoneNode.State(muted: muted, filled: true, color: .white), animated: true)
|
self.microphoneNode.update(state: VoiceChatMicrophoneNode.State(muted: muted, filled: true, color: .white), animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func setAvatarHidden(_ hidden: Bool) {
|
||||||
|
self.backdropAvatarNode.isHidden = hidden
|
||||||
|
self.backdropEffectView?.isHidden = hidden
|
||||||
|
self.avatarNode.isHidden = hidden
|
||||||
|
self.audioLevelView?.isHidden = hidden
|
||||||
|
}
|
||||||
|
|
||||||
func update(peer: (peer: PeerId, endpointId: String?)?, waitForFullSize: Bool, completion: (() -> Void)? = nil) {
|
func update(peer: (peer: PeerId, endpointId: String?)?, waitForFullSize: Bool, completion: (() -> Void)? = nil) {
|
||||||
let previousPeer = self.currentPeer
|
let previousPeer = self.currentPeer
|
||||||
if previousPeer?.0 == peer?.0 && previousPeer?.1 == peer?.1 {
|
if previousPeer?.0 == peer?.0 && previousPeer?.1 == peer?.1 {
|
||||||
@@ -594,8 +614,7 @@ final class VoiceChatMainStageNode: ASDisplayNode {
|
|||||||
if let (_, endpointId) = peer {
|
if let (_, endpointId) = peer {
|
||||||
if endpointId != previousPeer?.1 {
|
if endpointId != previousPeer?.1 {
|
||||||
if let endpointId = endpointId {
|
if let endpointId = endpointId {
|
||||||
self.avatarNode.isHidden = true
|
self.setAvatarHidden(true)
|
||||||
self.audioLevelView?.isHidden = true
|
|
||||||
|
|
||||||
self.call.makeIncomingVideoView(endpointId: endpointId, completion: { [weak self] videoView in
|
self.call.makeIncomingVideoView(endpointId: endpointId, completion: { [weak self] videoView in
|
||||||
Queue.mainQueue().async {
|
Queue.mainQueue().async {
|
||||||
@@ -643,15 +662,14 @@ final class VoiceChatMainStageNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
self.avatarNode.isHidden = false
|
self.setAvatarHidden(false)
|
||||||
self.audioLevelView?.isHidden = false
|
|
||||||
if let currentVideoNode = self.currentVideoNode {
|
if let currentVideoNode = self.currentVideoNode {
|
||||||
currentVideoNode.removeFromSupernode()
|
currentVideoNode.removeFromSupernode()
|
||||||
self.currentVideoNode = nil
|
self.currentVideoNode = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.audioLevelView?.isHidden = self.currentPeer?.1 != nil
|
self.setAvatarHidden(endpointId != nil)
|
||||||
completion?()
|
completion?()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -705,6 +723,10 @@ final class VoiceChatMainStageNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: size))
|
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: size))
|
||||||
|
transition.updateFrame(node: self.backdropAvatarNode, frame: CGRect(origin: CGPoint(), size: size))
|
||||||
|
if let backdropEffectView = self.backdropEffectView {
|
||||||
|
transition.updateFrame(view: backdropEffectView, frame: CGRect(origin: CGPoint(), size: size))
|
||||||
|
}
|
||||||
|
|
||||||
let avatarSize = CGSize(width: 180.0, height: 180.0)
|
let avatarSize = CGSize(width: 180.0, height: 180.0)
|
||||||
let avatarFrame = CGRect(origin: CGPoint(x: (size.width - avatarSize.width) / 2.0, y: (size.height - avatarSize.height) / 2.0), size: avatarSize)
|
let avatarFrame = CGRect(origin: CGPoint(x: (size.width - avatarSize.width) / 2.0, y: (size.height - avatarSize.height) / 2.0), size: avatarSize)
|
||||||
|
|||||||
@@ -129,9 +129,7 @@ final class VoiceChatTileItemNode: ASDisplayNode {
|
|||||||
self.contentNode = ASDisplayNode()
|
self.contentNode = ASDisplayNode()
|
||||||
self.contentNode.clipsToBounds = true
|
self.contentNode.clipsToBounds = true
|
||||||
self.contentNode.cornerRadius = backgroundCornerRadius
|
self.contentNode.cornerRadius = backgroundCornerRadius
|
||||||
if #available(iOS 13.0, *) {
|
|
||||||
self.contentNode.layer.cornerCurve = .continuous
|
|
||||||
}
|
|
||||||
self.backgroundNode = ASDisplayNode()
|
self.backgroundNode = ASDisplayNode()
|
||||||
self.backgroundNode.backgroundColor = panelBackgroundColor
|
self.backgroundNode.backgroundColor = panelBackgroundColor
|
||||||
|
|
||||||
@@ -203,6 +201,10 @@ final class VoiceChatTileItemNode: ASDisplayNode {
|
|||||||
override func didLoad() {
|
override func didLoad() {
|
||||||
super.didLoad()
|
super.didLoad()
|
||||||
|
|
||||||
|
if #available(iOS 13.0, *) {
|
||||||
|
self.contentNode.layer.cornerCurve = .continuous
|
||||||
|
}
|
||||||
|
|
||||||
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tap)))
|
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tap)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user