Video Chat Improvements

This commit is contained in:
Ilya Laktyushin 2021-05-21 20:21:41 +04:00
parent 659ea466c9
commit 43dd5c7b12
9 changed files with 4568 additions and 4348 deletions

View File

@ -6472,5 +6472,6 @@ Sorry for the inconvenience.";
"VoiceChat.ShareScreen" = "Share Screen";
"VoiceChat.StopScreenSharing" = "Stop Screen Sharing";
"VoiceChat.ParticipantIsSpeaking" = "%1$@ is speaking";
"WallpaperPreview.WallpaperColors" = "Colors";

View File

@ -45,6 +45,8 @@ swift_library(
"//submodules/PeerInfoAvatarListNode:PeerInfoAvatarListNode",
"//submodules/WebSearchUI:WebSearchUI",
"//submodules/MapResourceToAvatarSizes:MapResourceToAvatarSizes",
"//submodules/TextFormat:TextFormat",
"//submodules/Markdown:Markdown",
],
visibility = [
"//visibility:public",

View File

@ -206,7 +206,6 @@ public final class VoiceChatController: ViewController {
private final class Interaction {
let updateIsMuted: (PeerId, Bool) -> Void
let switchToPeer: (PeerId, String?, Bool) -> Void
let togglePeerVideo: (PeerId) -> Void
let openInvite: () -> Void
let peerContextAction: (VoiceChatPeerEntry, ASDisplayNode, ContextGesture?) -> Void
let getPeerVideo: (String, GroupVideoNode.Position) -> GroupVideoNode?
@ -219,14 +218,12 @@ public final class VoiceChatController: ViewController {
init(
updateIsMuted: @escaping (PeerId, Bool) -> Void,
switchToPeer: @escaping (PeerId, String?, Bool) -> Void,
togglePeerVideo: @escaping (PeerId) -> Void,
openInvite: @escaping () -> Void,
peerContextAction: @escaping (VoiceChatPeerEntry, ASDisplayNode, ContextGesture?) -> Void,
getPeerVideo: @escaping (String, GroupVideoNode.Position) -> GroupVideoNode?
) {
self.updateIsMuted = updateIsMuted
self.switchToPeer = switchToPeer
self.togglePeerVideo = togglePeerVideo
self.openInvite = openInvite
self.peerContextAction = peerContextAction
self.getPeerVideo = getPeerVideo
@ -803,6 +800,7 @@ public final class VoiceChatController: ViewController {
private var endpointToPeerId: [String: PeerId] = [:]
private var peerIdToEndpoint: [PeerId: String] = [:]
private var currentSpeakers: [PeerId] = []
private var currentDominantSpeaker: (PeerId, Double)?
private var currentForcedSpeaker: PeerId?
private var effectiveSpeaker: (PeerId, String?)?
@ -1000,6 +998,7 @@ public final class VoiceChatController: ViewController {
self.transitionContainerNode.clipsToBounds = true
self.transitionContainerNode.isUserInteractionEnabled = false
self.transitionContainerNode.view.mask = self.transitionMaskView
// self.transitionContainerNode.view.addSubview(self.transitionMaskView)
self.scheduleTextNode = ImmediateTextNode()
self.scheduleTextNode.isHidden = !self.isScheduling
@ -1064,13 +1063,6 @@ public final class VoiceChatController: ViewController {
strongSelf.updateMainVideo(waitForFullSize: false, updateMembers: true, force: true)
}
}
}, togglePeerVideo: { [weak self] peerId in
guard let strongSelf = self else {
return
}
if let strongSelf = self {
}
}, openInvite: { [weak self] in
guard let strongSelf = self else {
return
@ -1882,6 +1874,20 @@ public final class VoiceChatController: ViewController {
}
})
self.fullscreenListNode.updateFloatingHeaderOffset = { [weak self] _, _ in
guard let strongSelf = self else {
return
}
var visiblePeerIds = Set<PeerId>()
strongSelf.fullscreenListNode.forEachVisibleItemNode { itemNode in
if let itemNode = itemNode as? VoiceChatFullscreenParticipantItemNode, let item = itemNode.item {
visiblePeerIds.insert(item.peer.id)
}
}
strongSelf.mainStageNode.update(visiblePeerIds: visiblePeerIds)
}
self.listNode.updateFloatingHeaderOffset = { [weak self] offset, transition in
if let strongSelf = self {
strongSelf.currentContentOffset = offset
@ -1996,6 +2002,24 @@ public final class VoiceChatController: ViewController {
}
}
self.mainStageNode.switchTo = { [weak self] peerId in
if let strongSelf = self, let interaction = strongSelf.itemInteraction {
interaction.switchToPeer(peerId, nil, false)
// let position: ListViewScrollPosition
// var index: Int = 0
// if index > strongSelf.currentFullscreenEntries.count - 3 {
// index = strongSelf.currentFullscreenEntries.count - 1
// position = .bottom(0.0)
// } else {
// position = .center(.bottom)
// }
// strongSelf.fullscreenListNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: index, position: position, animated: true, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in
// completion()
// })
}
}
self.mainStageNode.getAudioLevel = { [weak self] peerId in
return self?.itemInteraction?.getAudioLevel(peerId) ?? .single(0.0)
}
@ -3172,18 +3196,14 @@ public final class VoiceChatController: ViewController {
let bottomPanelCoverHeight = bottomAreaHeight + layout.intrinsicInsets.bottom
let bottomGradientFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - bottomPanelCoverHeight), size: CGSize(width: size.width, height: bottomGradientHeight))
let additionalMaskHeight: CGFloat = 62.0
let topEdgeY = isLandscape ? 0.0 : layoutTopInset
let bottomEdgeY = (self.isLandscape ? layout.size.height + bottomGradientHeight : bottomGradientFrame.maxY) + additionalMaskHeight
let transitionContainerFrame = CGRect(x: 0.0, y: topEdgeY, width: layout.size.width, height: max(0.0, bottomEdgeY - topEdgeY))
let transitionContainerFrame = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height)
transition.updateFrame(node: self.transitionContainerNode, frame: transitionContainerFrame)
transition.updateFrame(view: self.transitionMaskView, frame: CGRect(x: 0.0, y: 0.0, width: transitionContainerFrame.width, height: transitionContainerFrame.height))
let updateMaskLayers = {
transition.updateFrame(layer: self.transitionMaskTopFillLayer, frame: CGRect(x: 0.0, y: 0.0, width: transitionContainerFrame.width, height: topPanelFrame.height))
transition.updateFrame(layer: self.transitionMaskFillLayer, frame: CGRect(x: 0.0, y: topPanelFrame.height, width: transitionContainerFrame.width, height: transitionContainerFrame.height - bottomGradientHeight - additionalMaskHeight - topPanelFrame.height))
transition.updateFrame(layer: self.transitionMaskGradientLayer, frame: CGRect(x: 0.0, y: transitionContainerFrame.height - bottomGradientHeight - additionalMaskHeight, width: transitionContainerFrame.width, height: bottomGradientHeight))
transition.updateFrame(layer: self.transitionMaskBottomFillLayer, frame: CGRect(x: 0.0, y: transitionContainerFrame.height - bottomGradientHeight - additionalMaskHeight, width: transitionContainerFrame.width, height: bottomGradientHeight + additionalMaskHeight))
transition.updateFrame(layer: self.transitionMaskTopFillLayer, frame: CGRect(x: 0.0, y: 0.0, width: transitionContainerFrame.width, height: topPanelFrame.maxY))
transition.updateFrame(layer: self.transitionMaskFillLayer, frame: CGRect(x: 0.0, y: topPanelFrame.maxY, width: transitionContainerFrame.width, height: bottomGradientFrame.minY - topPanelFrame.maxY))
transition.updateFrame(layer: self.transitionMaskGradientLayer, frame: CGRect(x: 0.0, y: bottomGradientFrame.minY, width: transitionContainerFrame.width, height: bottomGradientFrame.height))
transition.updateFrame(layer: self.transitionMaskBottomFillLayer, frame: CGRect(x: 0.0, y: bottomGradientFrame.minY, width: transitionContainerFrame.width, height: transitionContainerFrame.height - bottomGradientFrame.minY))
}
if transition.isAnimated {
updateMaskLayers()
@ -3204,15 +3224,15 @@ public final class VoiceChatController: ViewController {
}
bottomEdgeInset = 154.0
}
transition.updateAlpha(node: self.bottomGradientNode, alpha: isFullscreen || self.isLandscape ? 0.0 : 1.0)
transition.updateAlpha(node: self.bottomGradientNode, alpha: self.isLandscape ? 0.0 : 1.0)
let videoTopEdgeY = isLandscape ? 0.0 : layoutTopInset
let videoBottomEdgeY = self.isLandscape ? layout.size.height : layout.size.height - layout.intrinsicInsets.bottom - 84.0
let videoFrame = CGRect(x: 0.0, y: videoTopEdgeY, width: isLandscape ? layout.size.width - layout.safeInsets.right - 84.0 : layout.size.width, height: videoBottomEdgeY - videoTopEdgeY)
transition.updateFrame(node: self.mainStageContainerNode, frame: videoFrame)
transition.updateFrame(node: self.mainStageBackgroundNode, frame: CGRect(origin: CGPoint(), size: videoFrame.size))
transition.updateFrame(node: self.mainStageContainerNode, frame: CGRect(origin: CGPoint(), size: layout.size))
transition.updateFrame(node: self.mainStageBackgroundNode, frame: videoFrame)
if !self.mainStageNode.animating {
transition.updateFrame(node: self.mainStageNode, frame: CGRect(origin: CGPoint(), size: videoFrame.size))
transition.updateFrame(node: self.mainStageNode, frame: videoFrame)
}
self.mainStageNode.update(size: videoFrame.size, sideInset: layout.safeInsets.left, bottomInset: bottomInset, isLandscape: self.isLandscape, transition: transition)
@ -4030,7 +4050,7 @@ public final class VoiceChatController: ViewController {
size.width = floor(min(size.width, size.height) * 0.5)
}
let bottomPanelHeight = self.isLandscape ? layout.intrinsicInsets.bottom : self.effectiveBottomAreaHeight + layout.intrinsicInsets.bottom
let bottomPanelHeight = self.isLandscape ? layout.intrinsicInsets.bottom : bottomAreaHeight + layout.intrinsicInsets.bottom
let layoutTopInset: CGFloat = max(layout.statusBarHeight ?? 0.0, layout.safeInsets.top)
let listTopInset = layoutTopInset + topPanelHeight
let listSize = CGSize(width: size.width, height: layout.size.height - listTopInset - bottomPanelHeight + bottomGradientHeight)
@ -4085,6 +4105,7 @@ public final class VoiceChatController: ViewController {
disableAnimation = true
}
let speakingPeersUpdated = self.currentSpeakingPeers != speakingPeers
self.currentCallMembers = callMembers
self.currentSpeakingPeers = speakingPeers
self.currentInvitedPeers = invitedPeers
@ -4253,6 +4274,9 @@ public final class VoiceChatController: ViewController {
if let tileItem = tileByVideoEndpoint[tileVideoEndpoint] {
tileItems.append(tileItem)
if let fullscreenEntry = entryByPeerId[tileItem.peer.id] {
if processedFullscreenPeerIds.contains(tileItem.peer.id) {
continue
}
fullscreenEntries.append(.peer(fullscreenEntry, fullscreenIndex))
processedFullscreenPeerIds.insert(fullscreenEntry.peer.id)
fullscreenIndex += 1
@ -4360,6 +4384,26 @@ public final class VoiceChatController: ViewController {
let fullscreenTransition = self.preparedFullscreenTransition(from: previousFullscreenEntries, to: fullscreenEntries, isLoading: false, isEmpty: false, canInvite: canInvite, crossFade: false, animated: true, context: self.context, presentationData: presentationData, interaction: self.itemInteraction!)
self.enqueueFullscreenTransition(fullscreenTransition)
if case .fullscreen = self.displayMode, !self.mainStageNode.animating {
if speakingPeersUpdated {
var speakingPeers = speakingPeers
var updatedSpeakers: [PeerId] = []
for peerId in self.currentSpeakers {
if speakingPeers.contains(peerId) {
updatedSpeakers.append(peerId)
speakingPeers.remove(peerId)
}
}
for peerId in Array(speakingPeers) {
updatedSpeakers.append(peerId)
}
self.currentSpeakers = updatedSpeakers
self.mainStageNode.update(speakingPeerId: updatedSpeakers.first)
}
} else {
self.mainStageNode.update(speakingPeerId: nil)
}
}
private func callStateDidReset() {
@ -4549,6 +4593,19 @@ public final class VoiceChatController: ViewController {
self.panGestureArguments = (topInset, 0.0)
self.controller?.dismissAllTooltips()
if case .fullscreen = self.effectiveDisplayMode {
self.mainStageBackgroundNode.alpha = 0.0
self.mainStageBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.4)
self.mainStageNode.setControlsHidden(true, animated: true)
self.fullscreenListNode.alpha = 0.0
self.fullscreenListNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, completion: { [weak self] _ in
self?.attachTileVideos()
})
self.contentContainer.insertSubnode(self.mainStageContainerNode, aboveSubnode: self.bottomPanelNode)
}
case .changed:
var translation = recognizer.translation(in: self.contentContainer.view).y
if self.isScheduled && translation < 0.0 {
@ -4591,9 +4648,14 @@ public final class VoiceChatController: ViewController {
self.listNode.scroller.panGestureRecognizer.setTranslation(CGPoint(), in: self.listNode.scroller)
}
}
case let .fullscreen(controlsHidden):
break
case .fullscreen:
var bounds = self.mainStageContainerNode.bounds
bounds.origin.y = -translation
self.mainStageContainerNode.bounds = bounds
var backgroundFrame = self.mainStageNode.frame
backgroundFrame.origin.y += -translation
self.mainStageBackgroundNode.frame = backgroundFrame
}
if let (layout, navigationHeight) = self.validLayout {
@ -4636,7 +4698,33 @@ public final class VoiceChatController: ViewController {
topInset = self.listNode.frame.height
}
if case .modal(true, _) = self.effectiveDisplayMode {
if case .fullscreen = self.effectiveDisplayMode {
self.panGestureArguments = nil
if abs(translation.y) > 100.0 || abs(velocity.y) > 300.0 {
self.currentForcedSpeaker = nil
self.updateDisplayMode(.modal(isExpanded: true, isFilled: true), fromPan: true)
self.effectiveSpeaker = nil
} else {
self.mainStageBackgroundNode.alpha = 1.0
self.mainStageBackgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, completion: { [weak self] _ in
self?.attachFullscreenVideos()
})
self.mainStageNode.setControlsHidden(false, animated: true)
self.fullscreenListNode.alpha = 1.0
self.fullscreenListNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, delay: 0.15)
var bounds = self.mainStageContainerNode.bounds
let previousBounds = bounds
bounds.origin.y = 0.0
self.mainStageContainerNode.bounds = bounds
self.mainStageContainerNode.layer.animateBounds(from: previousBounds, to: self.mainStageContainerNode.bounds, duration: 0.2, timingFunction: kCAMediaTimingFunctionSpring, completion: { [weak self] _ in
if let strongSelf = self {
strongSelf.contentContainer.insertSubnode(strongSelf.mainStageContainerNode, belowSubnode: strongSelf.transitionContainerNode)
}
})
}
} else if case .modal(true, _) = self.effectiveDisplayMode {
self.panGestureArguments = nil
if velocity.y > 300.0 || offset > topInset / 2.0 {
self.displayMode = .modal(isExpanded: false, isFilled: false)
@ -5070,7 +5158,52 @@ public final class VoiceChatController: ViewController {
return self.isScheduling || self.callState?.scheduleTimestamp != nil
}
private func updateDisplayMode(_ displayMode: DisplayMode) {
private func attachFullscreenVideos() {
var verticalItemNodes: [PeerId: ASDisplayNode] = [:]
self.listNode.forEachItemNode { itemNode in
if let itemNode = itemNode as? VoiceChatTilesGridItemNode {
for tileNode in itemNode.tileNodes {
if let item = tileNode.item {
verticalItemNodes[item.peer.id] = tileNode
}
if tileNode.item?.peer.id == self.effectiveSpeaker?.0 {
tileNode.isHidden = false
}
}
}
}
self.fullscreenListNode.forEachItemNode { itemNode in
if let itemNode = itemNode as? VoiceChatFullscreenParticipantItemNode, let item = itemNode.item, let otherItemNode = verticalItemNodes[item.peer.id] {
itemNode.animateTransitionIn(from: otherItemNode, containerNode: self.transitionContainerNode, transition: .immediate, animate: false)
}
}
}
private func attachTileVideos() {
var fullscreenItemNodes: [PeerId: VoiceChatFullscreenParticipantItemNode] = [:]
self.fullscreenListNode.forEachItemNode { itemNode in
if let itemNode = itemNode as? VoiceChatFullscreenParticipantItemNode, let item = itemNode.item {
fullscreenItemNodes[item.peer.id] = itemNode
}
}
self.listNode.forEachItemNode { itemNode in
if let itemNode = itemNode as? VoiceChatTilesGridItemNode {
for tileNode in itemNode.tileNodes {
if let item = tileNode.item, let otherItemNode = fullscreenItemNodes[item.peer.id] {
tileNode.animateTransitionIn(from: otherItemNode, containerNode: self.transitionContainerNode, transition: .immediate, animate: false)
if tileNode.item?.peer.id == self.effectiveSpeaker?.0 {
tileNode.isHidden = true
}
}
}
}
}
}
private func updateDisplayMode(_ displayMode: DisplayMode, fromPan: Bool = false) {
guard !self.animatingExpansion else {
return
}
@ -5093,6 +5226,7 @@ public final class VoiceChatController: ViewController {
if case .modal = previousDisplayMode, case .fullscreen = self.displayMode {
self.fullscreenListNode.isHidden = false
self.fullscreenListNode.alpha = 1.0
self.updateDecorationsLayout(transition: .immediate)
@ -5158,7 +5292,6 @@ public final class VoiceChatController: ViewController {
self.updateDecorationsLayout(transition: transition)
}
}
// if let (peerId, _) = minimalVisiblePeerid {
let effectiveSpeakerPeerId = self.effectiveSpeaker?.0
var index = 0
for item in self.currentFullscreenEntries {
@ -5178,9 +5311,6 @@ public final class VoiceChatController: ViewController {
self.fullscreenListNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: index, position: position, animated: false, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in
completion()
})
// } else {
// completion()
// }
} else if case .fullscreen = previousDisplayMode, case .modal = self.displayMode {
var minimalVisiblePeerid: (PeerId, CGFloat)?
var fullscreenItemNodes: [PeerId: VoiceChatFullscreenParticipantItemNode] = [:]
@ -5210,7 +5340,9 @@ public final class VoiceChatController: ViewController {
if let itemNode = itemNode as? VoiceChatTilesGridItemNode {
for tileNode in itemNode.tileNodes {
if let item = tileNode.item, let otherItemNode = fullscreenItemNodes[item.peer.id] {
if !fromPan || item.peer.id == effectiveSpeakerPeerId {
tileNode.animateTransitionIn(from: otherItemNode, containerNode: self.transitionContainerNode, transition: transition, animate: item.peer.id != effectiveSpeakerPeerId)
}
if item.peer.id == effectiveSpeakerPeerId {
targetTileNode = tileNode
@ -5218,29 +5350,36 @@ public final class VoiceChatController: ViewController {
}
}
} else if let itemNode = itemNode as? VoiceChatParticipantItemNode, let item = itemNode.item, let otherItemNode = fullscreenItemNodes[item.peer.id] {
if !fromPan {
itemNode.animateTransitionIn(from: otherItemNode, containerNode: self.transitionContainerNode, transition: transition)
}
}
}
self.mainStageNode.animateTransitionOut(to: targetTileNode, transition: transition, completion: { [weak self] in
let transitionOffset = -self.mainStageContainerNode.bounds.minY
if transitionOffset.isZero {
self.mainStageBackgroundNode.alpha = 0.0
self.mainStageBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2)
}
self.mainStageNode.animateTransitionOut(to: targetTileNode, offset: transitionOffset, transition: transition, completion: { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.effectiveSpeaker = nil
strongSelf.mainStageNode.update(peer: nil, waitForFullSize: false)
strongSelf.mainStageNode.setControlsHidden(false, animated: false)
strongSelf.fullscreenListNode.isHidden = true
strongSelf.mainStageContainerNode.isHidden = true
strongSelf.mainStageContainerNode.addSubnode(strongSelf.mainStageNode)
var bounds = strongSelf.mainStageContainerNode.bounds
bounds.origin.y = 0.0
strongSelf.mainStageContainerNode.bounds = bounds
strongSelf.contentContainer.insertSubnode(strongSelf.mainStageContainerNode, belowSubnode: strongSelf.transitionContainerNode)
})
self.mainStageBackgroundNode.alpha = 0.0
self.mainStageBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2)
self.transitionMaskTopFillLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak self] _ in
Queue.mainQueue().after(0.2) {
self?.transitionMaskTopFillLayer.removeAllAnimations()
}
})
self.transitionMaskTopFillLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15)
if let (layout, navigationHeight) = self.validLayout {
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: transition)

View File

@ -372,9 +372,11 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode {
self.contentWrapperNode.layer.animateScale(from: 0.001, to: 1.0, duration: duration, timingFunction: timingFunction)
self.contentWrapperNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration, timingFunction: timingFunction)
} else if !initialAnimate {
if case .animated = transition {
self.contextSourceNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration, timingFunction: timingFunction)
self.contextSourceNode.layer.animateScale(from: 0.0, to: 1.0, duration: duration, timingFunction: timingFunction)
}
}
} else if let sourceNode = sourceNode as? VoiceChatParticipantItemNode, let _ = sourceNode.item {
var startContainerPosition = sourceNode.avatarNode.view.convert(sourceNode.avatarNode.bounds, to: containerNode.view).center
var animate = true

View File

@ -16,6 +16,8 @@ import AppBundle
import PresentationDataUtils
import AvatarNode
import AudioBlob
import TextFormat
import Markdown
private let backArrowImage = NavigationBarTheme.generateBackArrowImage(color: .white)
private let backgroundCornerRadius: CGFloat = 11.0
@ -63,6 +65,7 @@ final class VoiceChatMainStageNode: ASDisplayNode {
var tapped: (() -> Void)?
var back: (() -> Void)?
var togglePin: (() -> Void)?
var switchTo: ((PeerId) -> Void)?
var getAudioLevel: ((PeerId) -> Signal<Float, NoError>)?
private let videoReadyDisposable = MetaDisposable()
@ -138,7 +141,9 @@ final class VoiceChatMainStageNode: ASDisplayNode {
self.microphoneNode.alpha = 0.0
self.speakingContainerNode = ASDisplayNode()
self.speakingContainerNode.alpha = 0.0
self.speakingContainerNode.cornerRadius = 19.0
self.speakingContainerNode.clipsToBounds = true
self.speakingAvatarNode = AvatarNode(font: avatarPlaceholderFont(size: 14.0))
self.speakingTitleNode = ImmediateTextNode()
@ -166,6 +171,11 @@ final class VoiceChatMainStageNode: ASDisplayNode {
self.headerNode.addSubnode(self.pinButtonTitleNode)
self.headerNode.addSubnode(self.pinButtonNode)
self.addSubnode(self.speakingContainerNode)
self.speakingContainerNode.addSubnode(self.speakingAvatarNode)
self.speakingContainerNode.addSubnode(self.speakingTitleNode)
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.backButtonNode.setTitle(presentationData.strings.Common_Back, with: Font.regular(17.0), with: .white, for: [])
self.backButtonNode.hitTestSlop = UIEdgeInsets(top: -8.0, left: -20.0, bottom: -8.0, right: -8.0)
@ -217,16 +227,25 @@ final class VoiceChatMainStageNode: ASDisplayNode {
super.didLoad()
let speakingEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
self.speakingContainerNode.view.addSubview(speakingEffectView)
self.speakingContainerNode.view.insertSubview(speakingEffectView, at: 0)
self.speakingEffectView = speakingEffectView
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tap)))
self.speakingContainerNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.speakingTap)))
}
@objc private func tap() {
self.tapped?()
}
@objc private func speakingTap() {
if let peerId = self.effectiveSpeakingPeerId {
self.switchTo?(peerId)
self.update(speakingPeerId: nil)
}
}
@objc private func backPressed() {
self.back?()
}
@ -278,7 +297,7 @@ final class VoiceChatMainStageNode: ASDisplayNode {
})
}
func animateTransitionOut(to targetNode: ASDisplayNode?, transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) {
func animateTransitionOut(to targetNode: ASDisplayNode?, offset: CGFloat, transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) {
guard let (_, sideInset, bottomInset, isLandscape) = self.validLayout else {
return
}
@ -289,6 +308,7 @@ final class VoiceChatMainStageNode: ASDisplayNode {
alphaTransition.updateAlpha(node: self.titleNode, alpha: 0.0)
alphaTransition.updateAlpha(node: self.microphoneNode, alpha: 0.0)
alphaTransition.updateAlpha(node: self.headerNode, alpha: 0.0)
alphaTransition.updateAlpha(node: self.bottomFadeNode, alpha: 1.0)
guard let targetNode = targetNode as? VoiceChatTileItemNode, let _ = targetNode.item else {
completion()
@ -296,10 +316,13 @@ final class VoiceChatMainStageNode: ASDisplayNode {
}
targetNode.isHidden = false
if offset.isZero {
targetNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1)
}
self.animatingOut = true
let initialFrame = self.frame
let originalFrame = self.frame
let initialFrame = originalFrame.offsetBy(dx: 0.0, dy: offset)
let targetFrame = targetNode.view.convert(targetNode.bounds, to: self.supernode?.view)
self.currentVideoNode?.keepBackdropSize = true
@ -317,6 +340,7 @@ final class VoiceChatMainStageNode: ASDisplayNode {
targetNode.alpha = 0.0
self.frame = initialFrame
self.update(size: targetFrame.size, sideInset: sideInset, bottomInset: bottomInset, isLandscape: isLandscape, force: true, transition: transition)
transition.updateFrame(node: self, frame: targetFrame, completion: { [weak self] _ in
if let strongSelf = self {
@ -325,20 +349,25 @@ final class VoiceChatMainStageNode: ASDisplayNode {
infoView?.removeFromSuperview()
targetNode.alpha = 1.0
strongSelf.animatingOut = false
strongSelf.frame = initialFrame
strongSelf.frame = originalFrame
strongSelf.update(size: initialFrame.size, sideInset: sideInset, bottomInset: bottomInset, isLandscape: isLandscape, transition: .immediate)
}
})
self.update(speakingPeerId: nil)
}
private var speakingPeerId: PeerId?
func update(speakingPeerId: PeerId?) {
guard self.speakingPeerId != speakingPeerId else {
private var effectiveSpeakingPeerId: PeerId?
private func updateSpeakingPeer() {
var effectiveSpeakingPeerId = self.speakingPeerId
if let peerId = effectiveSpeakingPeerId, self.visiblePeerIds.contains(peerId) || self.currentPeer?.0 == peerId || self.callState?.myPeerId == peerId {
effectiveSpeakingPeerId = nil
}
guard self.effectiveSpeakingPeerId != effectiveSpeakingPeerId else {
return
}
if let getAudioLevel = self.getAudioLevel, let peerId = speakingPeerId {
self.effectiveSpeakingPeerId = effectiveSpeakingPeerId
if let getAudioLevel = self.getAudioLevel, let peerId = effectiveSpeakingPeerId {
let wavesColor = UIColor(rgb: 0x34c759)
if let speakingAudioLevelView = self.speakingAudioLevelView {
speakingAudioLevelView.removeFromSuperview()
@ -353,7 +382,11 @@ final class VoiceChatMainStageNode: ASDisplayNode {
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
strongSelf.speakingAvatarNode.setPeer(context: strongSelf.context, theme: presentationData.theme, peer: peer)
strongSelf.speakingTitleNode.attributedText = NSAttributedString(string: peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), font: Font.regular(14.0), textColor: .white)
let bodyAttributes = MarkdownAttributeSet(font: Font.regular(15.0), textColor: .white, additionalAttributes: [:])
let boldAttributes = MarkdownAttributeSet(font: Font.semibold(15.0), textColor: .white, additionalAttributes: [:])
let attributedText = addAttributesToStringWithRanges(presentationData.strings.VoiceChat_ParticipantIsSpeaking(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)), body: bodyAttributes, argumentAttributes: [0: boldAttributes])
strongSelf.speakingTitleNode.attributedText = attributedText
strongSelf.speakingContainerNode.alpha = 0.0
@ -364,10 +397,9 @@ final class VoiceChatMainStageNode: ASDisplayNode {
strongSelf.speakingContainerNode.alpha = 1.0
strongSelf.speakingContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
strongSelf.speakingContainerNode.layer.animateScale(from: 0.01, to: 1.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
}))
let blobFrame = self.speakingAvatarNode.frame.insetBy(dx: -14.0, dy: -14.0)
self.speakingAudioLevelDisposable.set((getAudioLevel(peerId)
let blobFrame = strongSelf.speakingAvatarNode.frame.insetBy(dx: -7.0, dy: -7.0)
strongSelf.speakingAudioLevelDisposable.set((getAudioLevel(peerId)
|> deliverOnMainQueue).start(next: { [weak self] value in
guard let strongSelf = self else {
return
@ -381,8 +413,6 @@ final class VoiceChatMainStageNode: ASDisplayNode {
mediumBlobRange: (0.69, 0.87),
bigBlobRange: (0.71, 1.0)
)
audioLevelView.isHidden = strongSelf.currentPeer?.1 != nil
audioLevelView.setColor(wavesColor)
audioLevelView.alpha = 1.0
@ -404,9 +434,10 @@ final class VoiceChatMainStageNode: ASDisplayNode {
}
let transition: ContainedViewLayoutTransition = .animated(duration: 0.15, curve: .easeInOut)
transition.updateTransformScale(node: strongSelf.avatarNode, scale: avatarScale, beginWithCurrentState: true)
transition.updateTransformScale(node: strongSelf.speakingAvatarNode, scale: avatarScale, beginWithCurrentState: true)
}
}))
}))
} else {
self.speakingPeerDisposable.set(nil)
self.speakingAudioLevelDisposable.set(nil)
@ -426,6 +457,18 @@ final class VoiceChatMainStageNode: ASDisplayNode {
}
}
private var visiblePeerIds = Set<PeerId>()
func update(visiblePeerIds: Set<PeerId>) {
self.visiblePeerIds = visiblePeerIds
self.updateSpeakingPeer()
}
private var speakingPeerId: PeerId?
func update(speakingPeerId: PeerId?) {
self.speakingPeerId = speakingPeerId
self.updateSpeakingPeer()
}
func update(peerEntry: VoiceChatPeerEntry, pinned: Bool) {
let previousPeerEntry = self.currentPeerEntry
self.currentPeerEntry = peerEntry
@ -543,6 +586,8 @@ final class VoiceChatMainStageNode: ASDisplayNode {
}
self.currentPeer = peer
self.updateSpeakingPeer()
if let (_, endpointId) = peer {
if endpointId != previousPeer?.1 {
if let endpointId = endpointId {
@ -612,6 +657,17 @@ final class VoiceChatMainStageNode: ASDisplayNode {
}
}
func setControlsHidden(_ hidden: Bool, animated: Bool) {
let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.2, curve: .easeInOut) : .immediate
transition.updateAlpha(node: self.headerNode, alpha: hidden ? 0.0 : 1.0)
transition.updateAlpha(node: self.topFadeNode, alpha: hidden ? 0.0 : 1.0)
transition.updateAlpha(node: self.titleNode, alpha: hidden ? 0.0 : 1.0)
transition.updateAlpha(node: self.microphoneNode, alpha: hidden ? 0.0 : 1.0)
transition.updateAlpha(node: self.bottomFadeNode, alpha: hidden ? 0.0 : 1.0)
transition.updateAlpha(node: self.bottomFillNode, alpha: hidden ? 0.0 : 1.0)
}
func update(size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, isLandscape: Bool, force: Bool = false, transition: ContainedViewLayoutTransition) {
self.validLayout = (size, sideInset, bottomInset, isLandscape)
@ -679,6 +735,15 @@ final class VoiceChatMainStageNode: ASDisplayNode {
}
transition.updateFrame(node: self.headerNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: 64.0)))
let speakingInset: CGFloat = 16.0
let speakingAvatarSize = CGSize(width: 30.0, height: 30.0)
let speakingTitleSize = self.speakingTitleNode.updateLayout(CGSize(width: 220.0, height: CGFloat.greatestFiniteMagnitude))
let speakingContainerSize = CGSize(width: speakingTitleSize.width + speakingInset * 2.0 + speakingAvatarSize.width, height: 38.0)
self.speakingEffectView?.frame = CGRect(origin: CGPoint(), size: speakingContainerSize)
self.speakingAvatarNode.frame = CGRect(origin: CGPoint(x: 4.0, y: 4.0), size: speakingAvatarSize)
self.speakingTitleNode.frame = CGRect(origin: CGPoint(x: 4.0 + speakingAvatarSize.width + 14.0, y: floorToScreenPixels((38.0 - speakingTitleSize.height) / 2.0)), size: speakingTitleSize)
transition.updateFrame(node: self.speakingContainerNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - speakingContainerSize.width) / 2.0), y: size.height - bottomInset - speakingContainerSize.height - 44.0), size: speakingContainerSize))
}
func flipVideoIfNeeded() {

View File

@ -682,9 +682,15 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
let startContainerAvatarPosition = sourceNode.avatarNode.view.convert(sourceNode.avatarNode.bounds, to: containerNode.view).center
var animate = true
if containerNode.frame.width > containerNode.frame.height {
if startContainerAvatarPosition.y < -tileSize.height || startContainerAvatarPosition.y > containerNode.frame.height + tileSize.height {
animate = false
}
} else {
if startContainerAvatarPosition.x < -tileSize.width || startContainerAvatarPosition.x > containerNode.frame.width + tileSize.width {
animate = false
}
}
if animate {
sourceNode.avatarNode.alpha = 0.0
sourceNode.audioLevelView?.alpha = 0.0

View File

@ -231,6 +231,7 @@ final class VoiceChatPeerProfileNode: ASDisplayNode {
if let snapshotView = sourceNode.infoNode.view.snapshotView(afterScreenUpdates: false) {
self.videoFadeNode.image = tileFadeImage
self.videoFadeNode.transform = CATransform3DMakeScale(1.0, -1.0, 1.0)
self.videoFadeNode.frame = CGRect(x: 0.0, y: sourceRect.height - sourceNode.fadeNode.frame.height, width: sourceRect.width, height: sourceNode.fadeNode.frame.height)
self.insertSubnode(self.videoFadeNode, aboveSubnode: sourceNode.videoContainerNode)