mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-07 17:30:12 +00:00
Video Chat Improvements
This commit is contained in:
parent
659ea466c9
commit
43dd5c7b12
@ -6472,5 +6472,6 @@ Sorry for the inconvenience.";
|
|||||||
|
|
||||||
"VoiceChat.ShareScreen" = "Share Screen";
|
"VoiceChat.ShareScreen" = "Share Screen";
|
||||||
"VoiceChat.StopScreenSharing" = "Stop Screen Sharing";
|
"VoiceChat.StopScreenSharing" = "Stop Screen Sharing";
|
||||||
|
"VoiceChat.ParticipantIsSpeaking" = "%1$@ is speaking";
|
||||||
|
|
||||||
"WallpaperPreview.WallpaperColors" = "Colors";
|
"WallpaperPreview.WallpaperColors" = "Colors";
|
||||||
|
|||||||
@ -45,6 +45,8 @@ swift_library(
|
|||||||
"//submodules/PeerInfoAvatarListNode:PeerInfoAvatarListNode",
|
"//submodules/PeerInfoAvatarListNode:PeerInfoAvatarListNode",
|
||||||
"//submodules/WebSearchUI:WebSearchUI",
|
"//submodules/WebSearchUI:WebSearchUI",
|
||||||
"//submodules/MapResourceToAvatarSizes:MapResourceToAvatarSizes",
|
"//submodules/MapResourceToAvatarSizes:MapResourceToAvatarSizes",
|
||||||
|
"//submodules/TextFormat:TextFormat",
|
||||||
|
"//submodules/Markdown:Markdown",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|||||||
@ -206,7 +206,6 @@ public final class VoiceChatController: ViewController {
|
|||||||
private final class Interaction {
|
private final class Interaction {
|
||||||
let updateIsMuted: (PeerId, Bool) -> Void
|
let updateIsMuted: (PeerId, Bool) -> Void
|
||||||
let switchToPeer: (PeerId, String?, Bool) -> Void
|
let switchToPeer: (PeerId, String?, Bool) -> Void
|
||||||
let togglePeerVideo: (PeerId) -> Void
|
|
||||||
let openInvite: () -> Void
|
let openInvite: () -> Void
|
||||||
let peerContextAction: (VoiceChatPeerEntry, ASDisplayNode, ContextGesture?) -> Void
|
let peerContextAction: (VoiceChatPeerEntry, ASDisplayNode, ContextGesture?) -> Void
|
||||||
let getPeerVideo: (String, GroupVideoNode.Position) -> GroupVideoNode?
|
let getPeerVideo: (String, GroupVideoNode.Position) -> GroupVideoNode?
|
||||||
@ -219,14 +218,12 @@ public final class VoiceChatController: ViewController {
|
|||||||
init(
|
init(
|
||||||
updateIsMuted: @escaping (PeerId, Bool) -> Void,
|
updateIsMuted: @escaping (PeerId, Bool) -> Void,
|
||||||
switchToPeer: @escaping (PeerId, String?, Bool) -> Void,
|
switchToPeer: @escaping (PeerId, String?, Bool) -> Void,
|
||||||
togglePeerVideo: @escaping (PeerId) -> Void,
|
|
||||||
openInvite: @escaping () -> Void,
|
openInvite: @escaping () -> Void,
|
||||||
peerContextAction: @escaping (VoiceChatPeerEntry, ASDisplayNode, ContextGesture?) -> Void,
|
peerContextAction: @escaping (VoiceChatPeerEntry, ASDisplayNode, ContextGesture?) -> Void,
|
||||||
getPeerVideo: @escaping (String, GroupVideoNode.Position) -> GroupVideoNode?
|
getPeerVideo: @escaping (String, GroupVideoNode.Position) -> GroupVideoNode?
|
||||||
) {
|
) {
|
||||||
self.updateIsMuted = updateIsMuted
|
self.updateIsMuted = updateIsMuted
|
||||||
self.switchToPeer = switchToPeer
|
self.switchToPeer = switchToPeer
|
||||||
self.togglePeerVideo = togglePeerVideo
|
|
||||||
self.openInvite = openInvite
|
self.openInvite = openInvite
|
||||||
self.peerContextAction = peerContextAction
|
self.peerContextAction = peerContextAction
|
||||||
self.getPeerVideo = getPeerVideo
|
self.getPeerVideo = getPeerVideo
|
||||||
@ -803,6 +800,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
private var endpointToPeerId: [String: PeerId] = [:]
|
private var endpointToPeerId: [String: PeerId] = [:]
|
||||||
private var peerIdToEndpoint: [PeerId: String] = [:]
|
private var peerIdToEndpoint: [PeerId: String] = [:]
|
||||||
|
|
||||||
|
private var currentSpeakers: [PeerId] = []
|
||||||
private var currentDominantSpeaker: (PeerId, Double)?
|
private var currentDominantSpeaker: (PeerId, Double)?
|
||||||
private var currentForcedSpeaker: PeerId?
|
private var currentForcedSpeaker: PeerId?
|
||||||
private var effectiveSpeaker: (PeerId, String?)?
|
private var effectiveSpeaker: (PeerId, String?)?
|
||||||
@ -1000,6 +998,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
self.transitionContainerNode.clipsToBounds = true
|
self.transitionContainerNode.clipsToBounds = true
|
||||||
self.transitionContainerNode.isUserInteractionEnabled = false
|
self.transitionContainerNode.isUserInteractionEnabled = false
|
||||||
self.transitionContainerNode.view.mask = self.transitionMaskView
|
self.transitionContainerNode.view.mask = self.transitionMaskView
|
||||||
|
// self.transitionContainerNode.view.addSubview(self.transitionMaskView)
|
||||||
|
|
||||||
self.scheduleTextNode = ImmediateTextNode()
|
self.scheduleTextNode = ImmediateTextNode()
|
||||||
self.scheduleTextNode.isHidden = !self.isScheduling
|
self.scheduleTextNode.isHidden = !self.isScheduling
|
||||||
@ -1064,13 +1063,6 @@ public final class VoiceChatController: ViewController {
|
|||||||
strongSelf.updateMainVideo(waitForFullSize: false, updateMembers: true, force: true)
|
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
|
}, openInvite: { [weak self] in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
@ -1835,7 +1827,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if case .fullscreen = strongSelf.displayMode, !strongSelf.mainStageNode.animating {
|
if case .fullscreen = strongSelf.displayMode, !strongSelf.mainStageNode.animating {
|
||||||
if let (peerId, _) = maxLevelWithVideo {
|
if let (peerId, _) = maxLevelWithVideo {
|
||||||
if let peer = strongSelf.currentDominantSpeaker, CACurrentMediaTime() - peer.1 < 2.5 {
|
if let peer = strongSelf.currentDominantSpeaker, CACurrentMediaTime() - peer.1 < 2.5 {
|
||||||
@ -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
|
self.listNode.updateFloatingHeaderOffset = { [weak self] offset, transition in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.currentContentOffset = offset
|
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
|
self.mainStageNode.getAudioLevel = { [weak self] peerId in
|
||||||
return self?.itemInteraction?.getAudioLevel(peerId) ?? .single(0.0)
|
return self?.itemInteraction?.getAudioLevel(peerId) ?? .single(0.0)
|
||||||
}
|
}
|
||||||
@ -3172,18 +3196,14 @@ public final class VoiceChatController: ViewController {
|
|||||||
let bottomPanelCoverHeight = bottomAreaHeight + layout.intrinsicInsets.bottom
|
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 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 transitionContainerFrame = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height)
|
||||||
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))
|
|
||||||
transition.updateFrame(node: self.transitionContainerNode, frame: transitionContainerFrame)
|
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))
|
transition.updateFrame(view: self.transitionMaskView, frame: CGRect(x: 0.0, y: 0.0, width: transitionContainerFrame.width, height: transitionContainerFrame.height))
|
||||||
let updateMaskLayers = {
|
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.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.height, width: transitionContainerFrame.width, height: transitionContainerFrame.height - bottomGradientHeight - additionalMaskHeight - topPanelFrame.height))
|
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: transitionContainerFrame.height - bottomGradientHeight - additionalMaskHeight, width: transitionContainerFrame.width, height: bottomGradientHeight))
|
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: transitionContainerFrame.height - bottomGradientHeight - additionalMaskHeight, width: transitionContainerFrame.width, height: bottomGradientHeight + additionalMaskHeight))
|
transition.updateFrame(layer: self.transitionMaskBottomFillLayer, frame: CGRect(x: 0.0, y: bottomGradientFrame.minY, width: transitionContainerFrame.width, height: transitionContainerFrame.height - bottomGradientFrame.minY))
|
||||||
}
|
}
|
||||||
if transition.isAnimated {
|
if transition.isAnimated {
|
||||||
updateMaskLayers()
|
updateMaskLayers()
|
||||||
@ -3204,15 +3224,15 @@ public final class VoiceChatController: ViewController {
|
|||||||
}
|
}
|
||||||
bottomEdgeInset = 154.0
|
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 videoTopEdgeY = isLandscape ? 0.0 : layoutTopInset
|
||||||
let videoBottomEdgeY = self.isLandscape ? layout.size.height : layout.size.height - layout.intrinsicInsets.bottom - 84.0
|
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)
|
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.mainStageContainerNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||||
transition.updateFrame(node: self.mainStageBackgroundNode, frame: CGRect(origin: CGPoint(), size: videoFrame.size))
|
transition.updateFrame(node: self.mainStageBackgroundNode, frame: videoFrame)
|
||||||
if !self.mainStageNode.animating {
|
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)
|
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)
|
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 layoutTopInset: CGFloat = max(layout.statusBarHeight ?? 0.0, layout.safeInsets.top)
|
||||||
let listTopInset = layoutTopInset + topPanelHeight
|
let listTopInset = layoutTopInset + topPanelHeight
|
||||||
let listSize = CGSize(width: size.width, height: layout.size.height - listTopInset - bottomPanelHeight + bottomGradientHeight)
|
let listSize = CGSize(width: size.width, height: layout.size.height - listTopInset - bottomPanelHeight + bottomGradientHeight)
|
||||||
@ -4085,6 +4105,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
disableAnimation = true
|
disableAnimation = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let speakingPeersUpdated = self.currentSpeakingPeers != speakingPeers
|
||||||
self.currentCallMembers = callMembers
|
self.currentCallMembers = callMembers
|
||||||
self.currentSpeakingPeers = speakingPeers
|
self.currentSpeakingPeers = speakingPeers
|
||||||
self.currentInvitedPeers = invitedPeers
|
self.currentInvitedPeers = invitedPeers
|
||||||
@ -4253,6 +4274,9 @@ public final class VoiceChatController: ViewController {
|
|||||||
if let tileItem = tileByVideoEndpoint[tileVideoEndpoint] {
|
if let tileItem = tileByVideoEndpoint[tileVideoEndpoint] {
|
||||||
tileItems.append(tileItem)
|
tileItems.append(tileItem)
|
||||||
if let fullscreenEntry = entryByPeerId[tileItem.peer.id] {
|
if let fullscreenEntry = entryByPeerId[tileItem.peer.id] {
|
||||||
|
if processedFullscreenPeerIds.contains(tileItem.peer.id) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
fullscreenEntries.append(.peer(fullscreenEntry, fullscreenIndex))
|
fullscreenEntries.append(.peer(fullscreenEntry, fullscreenIndex))
|
||||||
processedFullscreenPeerIds.insert(fullscreenEntry.peer.id)
|
processedFullscreenPeerIds.insert(fullscreenEntry.peer.id)
|
||||||
fullscreenIndex += 1
|
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!)
|
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)
|
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() {
|
private func callStateDidReset() {
|
||||||
@ -4549,6 +4593,19 @@ public final class VoiceChatController: ViewController {
|
|||||||
self.panGestureArguments = (topInset, 0.0)
|
self.panGestureArguments = (topInset, 0.0)
|
||||||
|
|
||||||
self.controller?.dismissAllTooltips()
|
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:
|
case .changed:
|
||||||
var translation = recognizer.translation(in: self.contentContainer.view).y
|
var translation = recognizer.translation(in: self.contentContainer.view).y
|
||||||
if self.isScheduled && translation < 0.0 {
|
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)
|
self.listNode.scroller.panGestureRecognizer.setTranslation(CGPoint(), in: self.listNode.scroller)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case let .fullscreen(controlsHidden):
|
case .fullscreen:
|
||||||
break
|
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 {
|
if let (layout, navigationHeight) = self.validLayout {
|
||||||
@ -4636,7 +4698,33 @@ public final class VoiceChatController: ViewController {
|
|||||||
topInset = self.listNode.frame.height
|
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
|
self.panGestureArguments = nil
|
||||||
if velocity.y > 300.0 || offset > topInset / 2.0 {
|
if velocity.y > 300.0 || offset > topInset / 2.0 {
|
||||||
self.displayMode = .modal(isExpanded: false, isFilled: false)
|
self.displayMode = .modal(isExpanded: false, isFilled: false)
|
||||||
@ -5070,7 +5158,52 @@ public final class VoiceChatController: ViewController {
|
|||||||
return self.isScheduling || self.callState?.scheduleTimestamp != nil
|
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 {
|
guard !self.animatingExpansion else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -5093,6 +5226,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
|
|
||||||
if case .modal = previousDisplayMode, case .fullscreen = self.displayMode {
|
if case .modal = previousDisplayMode, case .fullscreen = self.displayMode {
|
||||||
self.fullscreenListNode.isHidden = false
|
self.fullscreenListNode.isHidden = false
|
||||||
|
self.fullscreenListNode.alpha = 1.0
|
||||||
|
|
||||||
self.updateDecorationsLayout(transition: .immediate)
|
self.updateDecorationsLayout(transition: .immediate)
|
||||||
|
|
||||||
@ -5158,29 +5292,25 @@ public final class VoiceChatController: ViewController {
|
|||||||
self.updateDecorationsLayout(transition: transition)
|
self.updateDecorationsLayout(transition: transition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// if let (peerId, _) = minimalVisiblePeerid {
|
let effectiveSpeakerPeerId = self.effectiveSpeaker?.0
|
||||||
let effectiveSpeakerPeerId = self.effectiveSpeaker?.0
|
var index = 0
|
||||||
var index = 0
|
for item in self.currentFullscreenEntries {
|
||||||
for item in self.currentFullscreenEntries {
|
if case let .peer(entry, _) = item, entry.peer.id == effectiveSpeakerPeerId {
|
||||||
if case let .peer(entry, _) = item, entry.peer.id == effectiveSpeakerPeerId {
|
break
|
||||||
break
|
|
||||||
} else {
|
|
||||||
index += 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let position: ListViewScrollPosition
|
|
||||||
if index > self.currentFullscreenEntries.count - 3 {
|
|
||||||
index = self.currentFullscreenEntries.count - 1
|
|
||||||
position = .bottom(0.0)
|
|
||||||
} else {
|
} else {
|
||||||
position = .center(.bottom)
|
index += 1
|
||||||
}
|
}
|
||||||
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()
|
let position: ListViewScrollPosition
|
||||||
})
|
if index > self.currentFullscreenEntries.count - 3 {
|
||||||
// } else {
|
index = self.currentFullscreenEntries.count - 1
|
||||||
// completion()
|
position = .bottom(0.0)
|
||||||
// }
|
} else {
|
||||||
|
position = .center(.bottom)
|
||||||
|
}
|
||||||
|
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 if case .fullscreen = previousDisplayMode, case .modal = self.displayMode {
|
} else if case .fullscreen = previousDisplayMode, case .modal = self.displayMode {
|
||||||
var minimalVisiblePeerid: (PeerId, CGFloat)?
|
var minimalVisiblePeerid: (PeerId, CGFloat)?
|
||||||
var fullscreenItemNodes: [PeerId: VoiceChatFullscreenParticipantItemNode] = [:]
|
var fullscreenItemNodes: [PeerId: VoiceChatFullscreenParticipantItemNode] = [:]
|
||||||
@ -5210,7 +5340,9 @@ public final class VoiceChatController: ViewController {
|
|||||||
if let itemNode = itemNode as? VoiceChatTilesGridItemNode {
|
if let itemNode = itemNode as? VoiceChatTilesGridItemNode {
|
||||||
for tileNode in itemNode.tileNodes {
|
for tileNode in itemNode.tileNodes {
|
||||||
if let item = tileNode.item, let otherItemNode = fullscreenItemNodes[item.peer.id] {
|
if let item = tileNode.item, let otherItemNode = fullscreenItemNodes[item.peer.id] {
|
||||||
tileNode.animateTransitionIn(from: otherItemNode, containerNode: self.transitionContainerNode, transition: transition, animate: item.peer.id != effectiveSpeakerPeerId)
|
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 {
|
if item.peer.id == effectiveSpeakerPeerId {
|
||||||
targetTileNode = tileNode
|
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] {
|
} else if let itemNode = itemNode as? VoiceChatParticipantItemNode, let item = itemNode.item, let otherItemNode = fullscreenItemNodes[item.peer.id] {
|
||||||
itemNode.animateTransitionIn(from: otherItemNode, containerNode: self.transitionContainerNode, transition: transition)
|
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 {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
strongSelf.effectiveSpeaker = nil
|
strongSelf.effectiveSpeaker = nil
|
||||||
strongSelf.mainStageNode.update(peer: nil, waitForFullSize: false)
|
strongSelf.mainStageNode.update(peer: nil, waitForFullSize: false)
|
||||||
|
strongSelf.mainStageNode.setControlsHidden(false, animated: false)
|
||||||
strongSelf.fullscreenListNode.isHidden = true
|
strongSelf.fullscreenListNode.isHidden = true
|
||||||
strongSelf.mainStageContainerNode.isHidden = true
|
strongSelf.mainStageContainerNode.isHidden = true
|
||||||
strongSelf.mainStageContainerNode.addSubnode(strongSelf.mainStageNode)
|
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.transitionMaskTopFillLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15)
|
||||||
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()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if let (layout, navigationHeight) = self.validLayout {
|
if let (layout, navigationHeight) = self.validLayout {
|
||||||
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: transition)
|
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: transition)
|
||||||
|
|||||||
@ -372,8 +372,10 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
self.contentWrapperNode.layer.animateScale(from: 0.001, to: 1.0, duration: duration, timingFunction: timingFunction)
|
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)
|
self.contentWrapperNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration, timingFunction: timingFunction)
|
||||||
} else if !initialAnimate {
|
} else if !initialAnimate {
|
||||||
self.contextSourceNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration, timingFunction: timingFunction)
|
if case .animated = transition {
|
||||||
self.contextSourceNode.layer.animateScale(from: 0.0, to: 1.0, duration: duration, timingFunction: timingFunction)
|
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 {
|
} else if let sourceNode = sourceNode as? VoiceChatParticipantItemNode, let _ = sourceNode.item {
|
||||||
var startContainerPosition = sourceNode.avatarNode.view.convert(sourceNode.avatarNode.bounds, to: containerNode.view).center
|
var startContainerPosition = sourceNode.avatarNode.view.convert(sourceNode.avatarNode.bounds, to: containerNode.view).center
|
||||||
|
|||||||
@ -16,6 +16,8 @@ import AppBundle
|
|||||||
import PresentationDataUtils
|
import PresentationDataUtils
|
||||||
import AvatarNode
|
import AvatarNode
|
||||||
import AudioBlob
|
import AudioBlob
|
||||||
|
import TextFormat
|
||||||
|
import Markdown
|
||||||
|
|
||||||
private let backArrowImage = NavigationBarTheme.generateBackArrowImage(color: .white)
|
private let backArrowImage = NavigationBarTheme.generateBackArrowImage(color: .white)
|
||||||
private let backgroundCornerRadius: CGFloat = 11.0
|
private let backgroundCornerRadius: CGFloat = 11.0
|
||||||
@ -63,6 +65,7 @@ final class VoiceChatMainStageNode: ASDisplayNode {
|
|||||||
var tapped: (() -> Void)?
|
var tapped: (() -> Void)?
|
||||||
var back: (() -> Void)?
|
var back: (() -> Void)?
|
||||||
var togglePin: (() -> Void)?
|
var togglePin: (() -> Void)?
|
||||||
|
var switchTo: ((PeerId) -> Void)?
|
||||||
|
|
||||||
var getAudioLevel: ((PeerId) -> Signal<Float, NoError>)?
|
var getAudioLevel: ((PeerId) -> Signal<Float, NoError>)?
|
||||||
private let videoReadyDisposable = MetaDisposable()
|
private let videoReadyDisposable = MetaDisposable()
|
||||||
@ -138,7 +141,9 @@ final class VoiceChatMainStageNode: ASDisplayNode {
|
|||||||
self.microphoneNode.alpha = 0.0
|
self.microphoneNode.alpha = 0.0
|
||||||
|
|
||||||
self.speakingContainerNode = ASDisplayNode()
|
self.speakingContainerNode = ASDisplayNode()
|
||||||
|
self.speakingContainerNode.alpha = 0.0
|
||||||
self.speakingContainerNode.cornerRadius = 19.0
|
self.speakingContainerNode.cornerRadius = 19.0
|
||||||
|
self.speakingContainerNode.clipsToBounds = true
|
||||||
|
|
||||||
self.speakingAvatarNode = AvatarNode(font: avatarPlaceholderFont(size: 14.0))
|
self.speakingAvatarNode = AvatarNode(font: avatarPlaceholderFont(size: 14.0))
|
||||||
self.speakingTitleNode = ImmediateTextNode()
|
self.speakingTitleNode = ImmediateTextNode()
|
||||||
@ -165,6 +170,11 @@ final class VoiceChatMainStageNode: ASDisplayNode {
|
|||||||
self.headerNode.addSubnode(self.pinButtonIconNode)
|
self.headerNode.addSubnode(self.pinButtonIconNode)
|
||||||
self.headerNode.addSubnode(self.pinButtonTitleNode)
|
self.headerNode.addSubnode(self.pinButtonTitleNode)
|
||||||
self.headerNode.addSubnode(self.pinButtonNode)
|
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 }
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
self.backButtonNode.setTitle(presentationData.strings.Common_Back, with: Font.regular(17.0), with: .white, for: [])
|
self.backButtonNode.setTitle(presentationData.strings.Common_Back, with: Font.regular(17.0), with: .white, for: [])
|
||||||
@ -217,16 +227,25 @@ final class VoiceChatMainStageNode: ASDisplayNode {
|
|||||||
super.didLoad()
|
super.didLoad()
|
||||||
|
|
||||||
let speakingEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
|
let speakingEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
|
||||||
self.speakingContainerNode.view.addSubview(speakingEffectView)
|
self.speakingContainerNode.view.insertSubview(speakingEffectView, at: 0)
|
||||||
self.speakingEffectView = speakingEffectView
|
self.speakingEffectView = speakingEffectView
|
||||||
|
|
||||||
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)))
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func tap() {
|
@objc private func tap() {
|
||||||
self.tapped?()
|
self.tapped?()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc private func speakingTap() {
|
||||||
|
if let peerId = self.effectiveSpeakingPeerId {
|
||||||
|
self.switchTo?(peerId)
|
||||||
|
self.update(speakingPeerId: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@objc private func backPressed() {
|
@objc private func backPressed() {
|
||||||
self.back?()
|
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 {
|
guard let (_, sideInset, bottomInset, isLandscape) = self.validLayout else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -289,6 +308,7 @@ final class VoiceChatMainStageNode: ASDisplayNode {
|
|||||||
alphaTransition.updateAlpha(node: self.titleNode, alpha: 0.0)
|
alphaTransition.updateAlpha(node: self.titleNode, alpha: 0.0)
|
||||||
alphaTransition.updateAlpha(node: self.microphoneNode, alpha: 0.0)
|
alphaTransition.updateAlpha(node: self.microphoneNode, alpha: 0.0)
|
||||||
alphaTransition.updateAlpha(node: self.headerNode, 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 {
|
guard let targetNode = targetNode as? VoiceChatTileItemNode, let _ = targetNode.item else {
|
||||||
completion()
|
completion()
|
||||||
@ -296,10 +316,13 @@ final class VoiceChatMainStageNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
targetNode.isHidden = false
|
targetNode.isHidden = false
|
||||||
targetNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1)
|
if offset.isZero {
|
||||||
|
targetNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1)
|
||||||
|
}
|
||||||
|
|
||||||
self.animatingOut = true
|
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)
|
let targetFrame = targetNode.view.convert(targetNode.bounds, to: self.supernode?.view)
|
||||||
|
|
||||||
self.currentVideoNode?.keepBackdropSize = true
|
self.currentVideoNode?.keepBackdropSize = true
|
||||||
@ -317,6 +340,7 @@ final class VoiceChatMainStageNode: ASDisplayNode {
|
|||||||
|
|
||||||
targetNode.alpha = 0.0
|
targetNode.alpha = 0.0
|
||||||
|
|
||||||
|
self.frame = initialFrame
|
||||||
self.update(size: targetFrame.size, sideInset: sideInset, bottomInset: bottomInset, isLandscape: isLandscape, force: true, transition: transition)
|
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
|
transition.updateFrame(node: self, frame: targetFrame, completion: { [weak self] _ in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
@ -325,20 +349,25 @@ final class VoiceChatMainStageNode: ASDisplayNode {
|
|||||||
infoView?.removeFromSuperview()
|
infoView?.removeFromSuperview()
|
||||||
targetNode.alpha = 1.0
|
targetNode.alpha = 1.0
|
||||||
strongSelf.animatingOut = false
|
strongSelf.animatingOut = false
|
||||||
strongSelf.frame = initialFrame
|
strongSelf.frame = originalFrame
|
||||||
strongSelf.update(size: initialFrame.size, sideInset: sideInset, bottomInset: bottomInset, isLandscape: isLandscape, transition: .immediate)
|
strongSelf.update(size: initialFrame.size, sideInset: sideInset, bottomInset: bottomInset, isLandscape: isLandscape, transition: .immediate)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
self.update(speakingPeerId: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var effectiveSpeakingPeerId: PeerId?
|
||||||
private var speakingPeerId: PeerId?
|
private func updateSpeakingPeer() {
|
||||||
func update(speakingPeerId: PeerId?) {
|
var effectiveSpeakingPeerId = self.speakingPeerId
|
||||||
guard self.speakingPeerId != speakingPeerId else {
|
if let peerId = effectiveSpeakingPeerId, self.visiblePeerIds.contains(peerId) || self.currentPeer?.0 == peerId || self.callState?.myPeerId == peerId {
|
||||||
|
effectiveSpeakingPeerId = nil
|
||||||
|
}
|
||||||
|
guard self.effectiveSpeakingPeerId != effectiveSpeakingPeerId else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
self.effectiveSpeakingPeerId = effectiveSpeakingPeerId
|
||||||
if let getAudioLevel = self.getAudioLevel, let peerId = speakingPeerId {
|
if let getAudioLevel = self.getAudioLevel, let peerId = effectiveSpeakingPeerId {
|
||||||
let wavesColor = UIColor(rgb: 0x34c759)
|
let wavesColor = UIColor(rgb: 0x34c759)
|
||||||
if let speakingAudioLevelView = self.speakingAudioLevelView {
|
if let speakingAudioLevelView = self.speakingAudioLevelView {
|
||||||
speakingAudioLevelView.removeFromSuperview()
|
speakingAudioLevelView.removeFromSuperview()
|
||||||
@ -353,7 +382,11 @@ final class VoiceChatMainStageNode: ASDisplayNode {
|
|||||||
|
|
||||||
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
||||||
strongSelf.speakingAvatarNode.setPeer(context: strongSelf.context, theme: presentationData.theme, peer: peer)
|
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
|
strongSelf.speakingContainerNode.alpha = 0.0
|
||||||
|
|
||||||
@ -364,48 +397,46 @@ final class VoiceChatMainStageNode: ASDisplayNode {
|
|||||||
strongSelf.speakingContainerNode.alpha = 1.0
|
strongSelf.speakingContainerNode.alpha = 1.0
|
||||||
strongSelf.speakingContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
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)
|
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)
|
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] value in
|
|
||||||
guard let strongSelf = self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if strongSelf.speakingAudioLevelView == nil, value > 0.0 {
|
let blobFrame = strongSelf.speakingAvatarNode.frame.insetBy(dx: -7.0, dy: -7.0)
|
||||||
let audioLevelView = VoiceBlobView(
|
strongSelf.speakingAudioLevelDisposable.set((getAudioLevel(peerId)
|
||||||
frame: blobFrame,
|
|> deliverOnMainQueue).start(next: { [weak self] value in
|
||||||
maxLevel: 1.5,
|
guard let strongSelf = self else {
|
||||||
smallBlobRange: (0, 0),
|
return
|
||||||
mediumBlobRange: (0.69, 0.87),
|
|
||||||
bigBlobRange: (0.71, 1.0)
|
|
||||||
)
|
|
||||||
audioLevelView.isHidden = strongSelf.currentPeer?.1 != nil
|
|
||||||
|
|
||||||
audioLevelView.setColor(wavesColor)
|
|
||||||
audioLevelView.alpha = 1.0
|
|
||||||
|
|
||||||
strongSelf.speakingAudioLevelView = audioLevelView
|
|
||||||
strongSelf.speakingContainerNode.view.insertSubview(audioLevelView, belowSubview: strongSelf.speakingAvatarNode.view)
|
|
||||||
}
|
|
||||||
|
|
||||||
let level = min(1.5, max(0.0, CGFloat(value)))
|
|
||||||
if let audioLevelView = strongSelf.speakingAudioLevelView {
|
|
||||||
audioLevelView.updateLevel(CGFloat(value))
|
|
||||||
|
|
||||||
let avatarScale: CGFloat
|
|
||||||
if value > 0.02 {
|
|
||||||
audioLevelView.startAnimating()
|
|
||||||
avatarScale = 1.03 + level * 0.13
|
|
||||||
audioLevelView.setColor(wavesColor, animated: true)
|
|
||||||
} else {
|
|
||||||
avatarScale = 1.0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.15, curve: .easeInOut)
|
if strongSelf.speakingAudioLevelView == nil, value > 0.0 {
|
||||||
transition.updateTransformScale(node: strongSelf.avatarNode, scale: avatarScale, beginWithCurrentState: true)
|
let audioLevelView = VoiceBlobView(
|
||||||
}
|
frame: blobFrame,
|
||||||
|
maxLevel: 1.5,
|
||||||
|
smallBlobRange: (0, 0),
|
||||||
|
mediumBlobRange: (0.69, 0.87),
|
||||||
|
bigBlobRange: (0.71, 1.0)
|
||||||
|
)
|
||||||
|
audioLevelView.setColor(wavesColor)
|
||||||
|
audioLevelView.alpha = 1.0
|
||||||
|
|
||||||
|
strongSelf.speakingAudioLevelView = audioLevelView
|
||||||
|
strongSelf.speakingContainerNode.view.insertSubview(audioLevelView, belowSubview: strongSelf.speakingAvatarNode.view)
|
||||||
|
}
|
||||||
|
|
||||||
|
let level = min(1.5, max(0.0, CGFloat(value)))
|
||||||
|
if let audioLevelView = strongSelf.speakingAudioLevelView {
|
||||||
|
audioLevelView.updateLevel(CGFloat(value))
|
||||||
|
|
||||||
|
let avatarScale: CGFloat
|
||||||
|
if value > 0.02 {
|
||||||
|
audioLevelView.startAnimating()
|
||||||
|
avatarScale = 1.03 + level * 0.13
|
||||||
|
audioLevelView.setColor(wavesColor, animated: true)
|
||||||
|
} else {
|
||||||
|
avatarScale = 1.0
|
||||||
|
}
|
||||||
|
|
||||||
|
let transition: ContainedViewLayoutTransition = .animated(duration: 0.15, curve: .easeInOut)
|
||||||
|
transition.updateTransformScale(node: strongSelf.speakingAvatarNode, scale: avatarScale, beginWithCurrentState: true)
|
||||||
|
}
|
||||||
|
}))
|
||||||
}))
|
}))
|
||||||
} else {
|
} else {
|
||||||
self.speakingPeerDisposable.set(nil)
|
self.speakingPeerDisposable.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) {
|
func update(peerEntry: VoiceChatPeerEntry, pinned: Bool) {
|
||||||
let previousPeerEntry = self.currentPeerEntry
|
let previousPeerEntry = self.currentPeerEntry
|
||||||
self.currentPeerEntry = peerEntry
|
self.currentPeerEntry = peerEntry
|
||||||
@ -543,6 +586,8 @@ final class VoiceChatMainStageNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
self.currentPeer = peer
|
self.currentPeer = peer
|
||||||
|
|
||||||
|
self.updateSpeakingPeer()
|
||||||
|
|
||||||
if let (_, endpointId) = peer {
|
if let (_, endpointId) = peer {
|
||||||
if endpointId != previousPeer?.1 {
|
if endpointId != previousPeer?.1 {
|
||||||
if let endpointId = endpointId {
|
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) {
|
func update(size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, isLandscape: Bool, force: Bool = false, transition: ContainedViewLayoutTransition) {
|
||||||
self.validLayout = (size, sideInset, bottomInset, isLandscape)
|
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)))
|
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() {
|
func flipVideoIfNeeded() {
|
||||||
|
|||||||
@ -682,8 +682,14 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
|
|
||||||
let startContainerAvatarPosition = sourceNode.avatarNode.view.convert(sourceNode.avatarNode.bounds, to: containerNode.view).center
|
let startContainerAvatarPosition = sourceNode.avatarNode.view.convert(sourceNode.avatarNode.bounds, to: containerNode.view).center
|
||||||
var animate = true
|
var animate = true
|
||||||
if startContainerAvatarPosition.x < -tileSize.width || startContainerAvatarPosition.x > containerNode.frame.width + tileSize.width {
|
if containerNode.frame.width > containerNode.frame.height {
|
||||||
animate = false
|
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 {
|
if animate {
|
||||||
sourceNode.avatarNode.alpha = 0.0
|
sourceNode.avatarNode.alpha = 0.0
|
||||||
|
|||||||
@ -231,6 +231,7 @@ final class VoiceChatPeerProfileNode: ASDisplayNode {
|
|||||||
|
|
||||||
if let snapshotView = sourceNode.infoNode.view.snapshotView(afterScreenUpdates: false) {
|
if let snapshotView = sourceNode.infoNode.view.snapshotView(afterScreenUpdates: false) {
|
||||||
self.videoFadeNode.image = tileFadeImage
|
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.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)
|
self.insertSubnode(self.videoFadeNode, aboveSubnode: sourceNode.videoContainerNode)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user