Video Chat Improvements

This commit is contained in:
Ilya Laktyushin 2021-05-08 14:15:29 +04:00
parent 4a465a5893
commit dbc3f8882f
8 changed files with 311 additions and 129 deletions

View File

@ -552,7 +552,12 @@ class LocationDistancePickerScreenNode: ViewControllerTracingNode, UIScrollViewD
self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
let offset = self.contentContainerNode.frame.height let offset = self.contentContainerNode.frame.height
self.wrappingScrollNode.layer.animatePosition(from: CGPoint(x: 0.0, y: offset), to: CGPoint(), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, additive: true) let position = self.wrappingScrollNode.position
let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)
self.wrappingScrollNode.position = CGPoint(x: position.x, y: position.y + offset)
transition.animateView({
self.wrappingScrollNode.position = position
})
} }
func animateOut(completion: (() -> Void)? = nil) { func animateOut(completion: (() -> Void)? = nil) {

View File

@ -549,11 +549,17 @@ final class StickerPackPreviewControllerNode: ViewControllerTracingNode, UIScrol
func animateIn() { func animateIn() {
self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4) self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
let offset = self.bounds.size.height - self.contentBackgroundNode.frame.minY let offset: CGFloat = 510.0
let dimPosition = self.dimNode.layer.position let dimPosition = self.dimNode.layer.position
self.dimNode.layer.animatePosition(from: CGPoint(x: dimPosition.x, y: dimPosition.y - offset), to: dimPosition, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
self.layer.animateBoundsOriginYAdditive(from: -offset, to: 0.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring) let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)
let targetBounds = self.bounds
self.bounds = self.bounds.offsetBy(dx: 0.0, dy: -offset)
self.dimNode.position = CGPoint(x: dimPosition.x, y: dimPosition.y - offset)
transition.animateView({
self.bounds = targetBounds
self.dimNode.position = dimPosition
})
} }
func animateOut(completion: (() -> Void)? = nil) { func animateOut(completion: (() -> Void)? = nil) {

View File

@ -287,15 +287,15 @@ private class VoiceChatCameraPreviewControllerNode: ViewControllerTracingNode, U
self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4) self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
let offset = self.bounds.size.height - self.contentBackgroundNode.frame.minY let offset = self.bounds.size.height - self.contentBackgroundNode.frame.minY
let dimPosition = self.dimNode.layer.position let dimPosition = self.dimNode.layer.position
self.dimNode.layer.animatePosition(from: CGPoint(x: dimPosition.x, y: dimPosition.y - offset), to: dimPosition, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring) let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)
let targetBounds = self.bounds let targetBounds = self.bounds
self.bounds = self.bounds.offsetBy(dx: 0.0, dy: -offset) self.bounds = self.bounds.offsetBy(dx: 0.0, dy: -offset)
self.dimNode.position = CGPoint(x: dimPosition.x, y: dimPosition.y - offset)
transition.animateView({ transition.animateView({
self.bounds = targetBounds self.bounds = targetBounds
self.dimNode.position = dimPosition
}) })
self.applicationStateDisposable = (self.context.sharedContext.applicationBindings.applicationIsActive self.applicationStateDisposable = (self.context.sharedContext.applicationBindings.applicationIsActive

View File

@ -225,7 +225,7 @@ final class GroupVideoNode: ASDisplayNode {
rotatedVideoFrame.size.width = ceil(rotatedVideoFrame.size.width) rotatedVideoFrame.size.width = ceil(rotatedVideoFrame.size.width)
rotatedVideoFrame.size.height = ceil(rotatedVideoFrame.size.height) rotatedVideoFrame.size.height = ceil(rotatedVideoFrame.size.height)
var videoSize = rotatedVideoFrame.size var videoSize = rotatedVideoFrame.size.aspectFilled(CGSize(width: 1080.0, height: 1080.0))
transition.updatePosition(layer: self.videoView.view.layer, position: rotatedVideoFrame.center) transition.updatePosition(layer: self.videoView.view.layer, position: rotatedVideoFrame.center)
transition.updateBounds(layer: self.videoView.view.layer, bounds: CGRect(origin: CGPoint(), size: videoSize)) transition.updateBounds(layer: self.videoView.view.layer, bounds: CGRect(origin: CGPoint(), size: videoSize))
@ -241,7 +241,7 @@ final class GroupVideoNode: ASDisplayNode {
// TODO: properly fix the issue // TODO: properly fix the issue
// On iOS 13 and later metal layer transformation is broken if the layer does not require compositing // On iOS 13 and later metal layer transformation is broken if the layer does not require compositing
self.videoView.view.alpha = 0.995 self.videoView.view.alpha = 0.99
} }
} }
@ -254,8 +254,8 @@ private final class MainVideoContainerNode: ASDisplayNode {
private var currentVideoNode: GroupVideoNode? private var currentVideoNode: GroupVideoNode?
private var candidateVideoNode: GroupVideoNode? private var candidateVideoNode: GroupVideoNode?
fileprivate let otherVideoButtonNode: HighlightTrackingButtonNode private let otherVideoButtonNode: HighlightTrackingButtonNode
private let otherVideoWrapperNode: ASDisplayNode fileprivate let otherVideoWrapperNode: ASDisplayNode
private let otherVideoShadowNode: ASImageNode private let otherVideoShadowNode: ASImageNode
private var otherVideoNode: GroupVideoNode? private var otherVideoNode: GroupVideoNode?
@ -270,6 +270,9 @@ private final class MainVideoContainerNode: ASDisplayNode {
var tapped: (() -> Void)? var tapped: (() -> Void)?
var otherVideoTapped: (() -> Void)? var otherVideoTapped: (() -> Void)?
private let videoReadyDisposable = MetaDisposable()
private let otherVideoReadyDisposable = MetaDisposable()
init(context: AccountContext, call: PresentationGroupCall) { init(context: AccountContext, call: PresentationGroupCall) {
self.context = context self.context = context
self.call = call self.call = call
@ -337,6 +340,11 @@ private final class MainVideoContainerNode: ASDisplayNode {
self.otherVideoButtonNode.addTarget(self, action: #selector(self.otherVideoPressed), forControlEvents: .touchUpInside) self.otherVideoButtonNode.addTarget(self, action: #selector(self.otherVideoPressed), forControlEvents: .touchUpInside)
} }
deinit {
self.videoReadyDisposable.dispose()
self.otherVideoReadyDisposable.dispose()
}
override func didLoad() { override func didLoad() {
super.didLoad() super.didLoad()
@ -361,10 +369,6 @@ private final class MainVideoContainerNode: ASDisplayNode {
let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut) let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut)
if let otherEndpointId = otherEndpointId { if let otherEndpointId = otherEndpointId {
if otherEndpointId != previousPeer?.2 { if otherEndpointId != previousPeer?.2 {
if self.otherVideoWrapperNode.alpha.isZero {
transition.updateAlpha(node: self.otherVideoWrapperNode, alpha: 1.0)
transition.updateTransformScale(node: self.otherVideoWrapperNode, scale: 1.0)
}
self.call.makeIncomingVideoView(endpointId: otherEndpointId) { [weak self] videoView in self.call.makeIncomingVideoView(endpointId: otherEndpointId) { [weak self] videoView in
guard let strongSelf = self, let videoView = videoView else { guard let strongSelf = self, let videoView = videoView else {
return return
@ -380,11 +384,24 @@ private final class MainVideoContainerNode: ASDisplayNode {
if let (size, sideInset, isLandscape) = strongSelf.validLayout { if let (size, sideInset, isLandscape) = strongSelf.validLayout {
strongSelf.update(size: size, sideInset: sideInset, isLandscape: isLandscape, transition: .immediate) strongSelf.update(size: size, sideInset: sideInset, isLandscape: isLandscape, transition: .immediate)
} }
if strongSelf.otherVideoWrapperNode.alpha.isZero {
strongSelf.otherVideoReadyDisposable.set((videoNode.ready
|> filter { $0 }
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self] _ in
if let strongSelf = self {
transition.updateAlpha(node: strongSelf.otherVideoWrapperNode, alpha: 1.0)
transition.updateTransformScale(node: strongSelf.otherVideoWrapperNode, scale: 1.0)
}
}))
}
} }
} }
} else { } else {
if let otherVideoNode = self.otherVideoNode { if let otherVideoNode = self.otherVideoNode {
self.otherVideoNode = nil self.otherVideoNode = nil
self.otherVideoReadyDisposable.set(nil)
let completion = { let completion = {
otherVideoNode.removeFromSupernode() otherVideoNode.removeFromSupernode()
} }
@ -404,46 +421,38 @@ private final class MainVideoContainerNode: ASDisplayNode {
guard let strongSelf = self, let videoView = videoView else { guard let strongSelf = self, let videoView = videoView else {
return return
} }
if waitForFullSize { let videoNode = GroupVideoNode(videoView: videoView)
let candidateVideoNode = GroupVideoNode(videoView: videoView) if let currentVideoNode = strongSelf.currentVideoNode {
strongSelf.candidateVideoNode = candidateVideoNode strongSelf.currentVideoNode = nil
Queue.mainQueue().after(0.3, { [weak candidateVideoNode] in currentVideoNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak currentVideoNode] _ in
guard let strongSelf = self, let videoNode = candidateVideoNode, videoNode === strongSelf.candidateVideoNode else { currentVideoNode?.removeFromSupernode()
return
}
if let currentVideoNode = strongSelf.currentVideoNode {
currentVideoNode.removeFromSupernode()
strongSelf.currentVideoNode = nil
}
strongSelf.currentVideoNode = videoNode
strongSelf.insertSubnode(videoNode, belowSubnode: strongSelf.topCornersNode)
if let (size, sideInset, isLandscape) = strongSelf.validLayout {
strongSelf.update(size: size, sideInset: sideInset, isLandscape: isLandscape, transition: .immediate)
}
completion?()
}) })
}
strongSelf.currentVideoNode = videoNode
strongSelf.insertSubnode(videoNode, belowSubnode: strongSelf.topCornersNode)
if let (size, sideInset, isLandscape) = strongSelf.validLayout {
strongSelf.update(size: size, sideInset: sideInset, isLandscape: isLandscape, transition: .immediate)
}
if waitForFullSize {
strongSelf.videoReadyDisposable.set((videoNode.ready
|> filter { $0 }
|> take(1)
|> deliverOnMainQueue).start(next: { _ in
completion?()
}))
} else { } else {
strongSelf.candidateVideoNode = nil strongSelf.videoReadyDisposable.set(nil)
let videoNode = GroupVideoNode(videoView: videoView)
if let currentVideoNode = strongSelf.currentVideoNode {
currentVideoNode.removeFromSupernode()
strongSelf.currentVideoNode = nil
}
strongSelf.currentVideoNode = videoNode
strongSelf.insertSubnode(videoNode, belowSubnode: strongSelf.topCornersNode)
if let (size, sideInset, isLandscape) = strongSelf.validLayout {
strongSelf.update(size: size, sideInset: sideInset, isLandscape: isLandscape, transition: .immediate)
}
completion?() completion?()
} }
} }
}) })
} }
} else { } else {
self.videoReadyDisposable.set(nil)
self.otherVideoReadyDisposable.set(nil)
if let currentVideoNode = self.currentVideoNode { if let currentVideoNode = self.currentVideoNode {
currentVideoNode.removeFromSupernode() currentVideoNode.removeFromSupernode()
self.currentVideoNode = nil self.currentVideoNode = nil
@ -456,7 +465,7 @@ private final class MainVideoContainerNode: ASDisplayNode {
if let currentVideoNode = self.currentVideoNode { if let currentVideoNode = self.currentVideoNode {
transition.updateFrame(node: currentVideoNode, frame: CGRect(origin: CGPoint(), size: size)) transition.updateFrame(node: currentVideoNode, frame: CGRect(origin: CGPoint(), size: size))
currentVideoNode.updateLayout(size: size, isLandscape: isLandscape, transition: transition) currentVideoNode.updateLayout(size: size, isLandscape: true, transition: transition)
} }
let smallVideoSize = CGSize(width: 40.0, height: 40.0) let smallVideoSize = CGSize(width: 40.0, height: 40.0)
@ -502,6 +511,7 @@ public final class VoiceChatController: ViewController {
private final class Interaction { private final class Interaction {
let updateIsMuted: (PeerId, Bool) -> Void let updateIsMuted: (PeerId, Bool) -> Void
let pinPeer: (PeerId) -> Void let pinPeer: (PeerId) -> Void
let togglePeerVideo: (PeerId) -> Void
let openInvite: () -> Void let openInvite: () -> Void
let peerContextAction: (PeerEntry, ASDisplayNode, ContextGesture?) -> Void let peerContextAction: (PeerEntry, ASDisplayNode, ContextGesture?) -> Void
let setPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void let setPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void
@ -515,6 +525,7 @@ public final class VoiceChatController: ViewController {
init( init(
updateIsMuted: @escaping (PeerId, Bool) -> Void, updateIsMuted: @escaping (PeerId, Bool) -> Void,
pinPeer: @escaping (PeerId) -> Void, pinPeer: @escaping (PeerId) -> Void,
togglePeerVideo: @escaping (PeerId) -> Void,
openInvite: @escaping () -> Void, openInvite: @escaping () -> Void,
peerContextAction: @escaping (PeerEntry, ASDisplayNode, ContextGesture?) -> Void, peerContextAction: @escaping (PeerEntry, ASDisplayNode, ContextGesture?) -> Void,
setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void,
@ -522,6 +533,7 @@ public final class VoiceChatController: ViewController {
) { ) {
self.updateIsMuted = updateIsMuted self.updateIsMuted = updateIsMuted
self.pinPeer = pinPeer self.pinPeer = pinPeer
self.togglePeerVideo = togglePeerVideo
self.openInvite = openInvite self.openInvite = openInvite
self.peerContextAction = peerContextAction self.peerContextAction = peerContextAction
self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions
@ -870,7 +882,11 @@ public final class VoiceChatController: ViewController {
if case .list = peerEntry.style { if case .list = peerEntry.style {
interaction.peerContextAction(peerEntry, node, nil) interaction.peerContextAction(peerEntry, node, nil)
} else if peerEntry.effectiveVideoEndpointId != nil { } else if peerEntry.effectiveVideoEndpointId != nil {
interaction.pinPeer(peer.id) if peerEntry.pinned && peerEntry.videoEndpointId != nil && peerEntry.screencastEndpointId != nil {
interaction.togglePeerVideo(peer.id)
} else {
interaction.pinPeer(peer.id)
}
} }
}, contextAction: peerEntry.style == .list ? { node, gesture in }, contextAction: peerEntry.style == .list ? { node, gesture in
interaction.peerContextAction(peerEntry, node, gesture) interaction.peerContextAction(peerEntry, node, gesture)
@ -1237,6 +1253,11 @@ public final class VoiceChatController: ViewController {
} }
strongSelf.updateMainParticipant(waitForFullSize: false) strongSelf.updateMainParticipant(waitForFullSize: false)
} }
}, togglePeerVideo: { [weak self] peerId in
guard let strongSelf = self else {
return
}
strongSelf.mainVideoContainerNode?.otherVideoTapped?()
}, openInvite: { [weak self] in }, openInvite: { [weak self] in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
@ -2043,7 +2064,7 @@ public final class VoiceChatController: ViewController {
if strongSelf.currentDominantSpeakerWithVideo != peerId { if strongSelf.currentDominantSpeakerWithVideo != peerId {
strongSelf.currentDominantSpeakerWithVideo = peerId strongSelf.currentDominantSpeakerWithVideo = peerId
if !strongSelf.requestedVideoSources.isEmpty { if !strongSelf.requestedVideoSources.isEmpty {
strongSelf.updateMainParticipant(waitForFullSize: true) strongSelf.updateMainParticipant(waitForFullSize: false)
} }
} }
} }
@ -2144,8 +2165,8 @@ public final class VoiceChatController: ViewController {
let videoNode = GroupVideoNode(videoView: videoView) let videoNode = GroupVideoNode(videoView: videoView)
strongSelf.videoNodes.append((endpointId, videoNode)) strongSelf.videoNodes.append((endpointId, videoNode))
if let (layout, navigationHeight) = strongSelf.validLayout { if let _ = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .immediate) // strongSelf.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .immediate)
loop: for i in 0 ..< strongSelf.currentEntries.count { loop: for i in 0 ..< strongSelf.currentEntries.count {
let entry = strongSelf.currentEntries[i] let entry = strongSelf.currentEntries[i]
@ -2178,7 +2199,6 @@ public final class VoiceChatController: ViewController {
strongSelf.requestedVideoSources.remove(source) strongSelf.requestedVideoSources.remove(source)
} }
var updated = false
for i in (0 ..< strongSelf.videoNodes.count).reversed() { for i in (0 ..< strongSelf.videoNodes.count).reversed() {
if !validSources.contains(strongSelf.videoNodes[i].0) { if !validSources.contains(strongSelf.videoNodes[i].0) {
let endpointId = strongSelf.videoNodes[i].0 let endpointId = strongSelf.videoNodes[i].0
@ -2191,15 +2211,14 @@ public final class VoiceChatController: ViewController {
case let .peer(peerEntry): case let .peer(peerEntry):
if peerEntry.effectiveVideoEndpointId == endpointId { if peerEntry.effectiveVideoEndpointId == endpointId {
let presentationData = strongSelf.presentationData.withUpdated(theme: strongSelf.darkTheme) let presentationData = strongSelf.presentationData.withUpdated(theme: strongSelf.darkTheme)
strongSelf.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [ListViewUpdateItem(index: i, previousIndex: i, item: entry.item(context: strongSelf.context, presentationData: presentationData, interaction: strongSelf.itemInteraction!, transparent: false), directionHint: nil)], options: [.Synchronous], updateOpaqueState: nil) strongSelf.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [ListViewUpdateItem(index: j, previousIndex: j, item: entry.item(context: strongSelf.context, presentationData: presentationData, interaction: strongSelf.itemInteraction!, transparent: false), directionHint: nil)], options: [.Synchronous], updateOpaqueState: nil)
strongSelf.tileListNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [ListViewUpdateItem(index: i, previousIndex: i, item: tileEntry.item(context: strongSelf.context, presentationData: presentationData, interaction: strongSelf.itemInteraction!, transparent: false), directionHint: nil)], options: [.Synchronous], updateOpaqueState: nil) strongSelf.tileListNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [ListViewUpdateItem(index: j, previousIndex: j, item: tileEntry.item(context: strongSelf.context, presentationData: presentationData, interaction: strongSelf.itemInteraction!, transparent: false), directionHint: nil)], options: [.Synchronous], updateOpaqueState: nil)
break loop break loop
} }
default: default:
break break
} }
} }
updated = true
} }
} }
@ -2213,13 +2232,10 @@ public final class VoiceChatController: ViewController {
} }
strongSelf.updateMainParticipant(waitForFullSize: false) strongSelf.updateMainParticipant(waitForFullSize: false)
} }
} else if strongSelf.currentDominantSpeakerWithVideo != nil && !strongSelf.requestedVideoSources.isEmpty { } else if strongSelf.currentDominantSpeakerWithVideo != nil && !strongSelf.requestedVideoSources.isEmpty && !strongSelf.didSetMainParticipant {
strongSelf.updateMainParticipant(waitForFullSize: true) strongSelf.didSetMainParticipant = true
} Queue.mainQueue().after(0.1) {
strongSelf.updateMainParticipant(waitForFullSize: true)
if updated {
if let (layout, navigationHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .immediate)
} }
} }
})) }))
@ -2247,7 +2263,7 @@ public final class VoiceChatController: ViewController {
} }
self.mainVideoContainerNode?.tapped = { [weak self] in self.mainVideoContainerNode?.tapped = { [weak self] in
if let strongSelf = self { if let strongSelf = self, !strongSelf.animatingExpansion {
var effectiveDisplayMode = strongSelf.displayMode var effectiveDisplayMode = strongSelf.displayMode
var isLandscape = false var isLandscape = false
if let (layout, _) = strongSelf.validLayout, layout.size.width > layout.size.height, case .compact = layout.metrics.widthClass { if let (layout, _) = strongSelf.validLayout, layout.size.width > layout.size.height, case .compact = layout.metrics.widthClass {
@ -3402,10 +3418,16 @@ public final class VoiceChatController: ViewController {
} }
} }
private var animatingButtons = false
@objc private func cameraPressed() { @objc private func cameraPressed() {
if self.call.hasVideo || self.call.hasScreencast { if self.call.hasVideo || self.call.hasScreencast {
self.call.disableVideo() self.call.disableVideo()
self.call.disableScreencast() self.call.disableScreencast()
if let (layout, navigationHeight) = self.validLayout {
self.animatingButtons = true
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .linear))
}
} else { } else {
self.call.makeOutgoingVideoView { [weak self] view in self.call.makeOutgoingVideoView { [weak self] view in
guard let strongSelf = self, let view = view else { guard let strongSelf = self, let view = view else {
@ -3415,6 +3437,11 @@ public final class VoiceChatController: ViewController {
let controller = VoiceChatCameraPreviewController(context: strongSelf.context, cameraNode: cameraNode, shareCamera: { [weak self] videoNode in let controller = VoiceChatCameraPreviewController(context: strongSelf.context, cameraNode: cameraNode, shareCamera: { [weak self] videoNode in
if let strongSelf = self { if let strongSelf = self {
strongSelf.call.requestVideo() strongSelf.call.requestVideo()
if let (layout, navigationHeight) = strongSelf.validLayout {
strongSelf.animatingButtons = true
strongSelf.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .linear))
}
} }
}, switchCamera: { [weak self] in }, switchCamera: { [weak self] in
self?.call.switchVideoCamera() self?.call.switchVideoCamera()
@ -3444,6 +3471,10 @@ public final class VoiceChatController: ViewController {
return controlsHidden ? 0.0 : fullscreenBottomAreaHeight return controlsHidden ? 0.0 : fullscreenBottomAreaHeight
} }
} }
private var hasMainVideo: Bool {
return self.mainVideoContainerNode != nil && self.effectiveSpeakerWithVideo != nil
}
private var bringVideoToBackOnCompletion = false private var bringVideoToBackOnCompletion = false
private func updateDecorationsLayout(transition: ContainedViewLayoutTransition, completion: (() -> Void)? = nil) { private func updateDecorationsLayout(transition: ContainedViewLayoutTransition, completion: (() -> Void)? = nil) {
@ -3481,7 +3512,7 @@ public final class VoiceChatController: ViewController {
let listSize = CGSize(width: contentWidth, height: layout.size.height - listTopInset - bottomPanelHeight) let listSize = CGSize(width: contentWidth, height: layout.size.height - listTopInset - bottomPanelHeight)
let topInset: CGFloat let topInset: CGFloat
if let (panInitialTopInset, panOffset) = self.panGestureArguments { if let (panInitialTopInset, panOffset) = self.panGestureArguments {
if self.isExpanded { if self.isExpanded && !self.hasMainVideo {
topInset = min(self.topInset ?? listSize.height, panInitialTopInset + max(0.0, panOffset)) topInset = min(self.topInset ?? listSize.height, panInitialTopInset + max(0.0, panOffset))
} else { } else {
topInset = max(0.0, panInitialTopInset + min(0.0, panOffset)) topInset = max(0.0, panInitialTopInset + min(0.0, panOffset))
@ -3543,7 +3574,7 @@ public final class VoiceChatController: ViewController {
videoY = layout.statusBarHeight ?? 20.0 videoY = layout.statusBarHeight ?? 20.0
isFullscreen = true isFullscreen = true
} }
videoClippingFrame = CGRect(origin: CGPoint(x: videoInset, y: videoY), size: CGSize(width: layout.size.width - videoInset * 2.0, height: self.isFullscreen ? videoHeight : 0.0)) videoClippingFrame = CGRect(origin: CGPoint(x: videoInset, y: videoY), size: CGSize(width: layout.size.width - videoInset * 2.0, height: self.hasMainVideo ? videoHeight : 0.0))
videoContainerFrame = CGRect(origin: CGPoint(x: -videoInset, y: 0.0), size: CGSize(width: layout.size.width, height: videoHeight)) videoContainerFrame = CGRect(origin: CGPoint(x: -videoInset, y: 0.0), size: CGSize(width: layout.size.width, height: videoHeight))
} }
@ -3648,7 +3679,7 @@ public final class VoiceChatController: ViewController {
let previousBottomCornersFrame = self.bottomCornersNode.frame let previousBottomCornersFrame = self.bottomCornersNode.frame
if !bottomCornersFrame.equalTo(previousBottomCornersFrame) { if !bottomCornersFrame.equalTo(previousBottomCornersFrame) {
self.bottomCornersNode.frame = bottomCornersFrame self.bottomCornersNode.frame = bottomCornersFrame
self.bottomPanelBackgroundNode.frame = CGRect(x: 0.0, y: bottomOffset, width: size.width, height: 2000.0) self.bottomPanelBackgroundNode.frame = CGRect(x: 0.0, y: bottomOffset + bottomDelta, width: size.width, height: 2000.0)
let positionDelta = CGPoint(x: 0.0, y: previousBottomCornersFrame.minY - bottomCornersFrame.minY) let positionDelta = CGPoint(x: 0.0, y: previousBottomCornersFrame.minY - bottomCornersFrame.minY)
transition.animatePositionAdditive(node: self.bottomCornersNode, offset: positionDelta) transition.animatePositionAdditive(node: self.bottomCornersNode, offset: positionDelta)
@ -3873,10 +3904,13 @@ public final class VoiceChatController: ViewController {
let hasVideo = self.call.hasVideo || self.call.hasScreencast let hasVideo = self.call.hasVideo || self.call.hasScreencast
let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.3, curve: .linear) : .immediate let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.3, curve: .linear) : .immediate
self.cameraButton.update(size: videoButtonSize, content: CallControllerButtonItemNode.Content(appearance: normalButtonAppearance, image: hasVideo ? .cameraOn : .cameraOff), text: self.presentationData.strings.VoiceChat_Video, transition: transition) self.cameraButton.update(size: hasVideo ? sideButtonSize : videoButtonSize, content: CallControllerButtonItemNode.Content(appearance: hasVideo ? activeButtonAppearance : normalButtonAppearance, image: hasVideo ? .cameraOn : .cameraOff), text: self.presentationData.strings.VoiceChat_Video, transition: transition)
transition.updateAlpha(node: self.switchCameraButton, alpha: self.call.hasVideo ? 1.0 : 0.0) transition.updateAlpha(node: self.switchCameraButton, alpha: hasVideo ? 1.0 : 0.0)
transition.updateAlpha(node: self.audioButton, alpha: self.call.hasVideo ? 0.0 : 1.0) transition.updateTransformScale(node: self.switchCameraButton, scale: hasVideo ? 1.0 : 0.0)
transition.updateAlpha(node: self.audioButton, alpha: hasVideo ? 0.0 : 1.0)
transition.updateTransformScale(node: self.audioButton, scale: hasVideo ? 0.0 : 1.0)
self.switchCameraButton.update(size: videoButtonSize, content: CallControllerButtonItemNode.Content(appearance: normalButtonAppearance, image: .flipCamera), text: "", transition: transition) self.switchCameraButton.update(size: videoButtonSize, content: CallControllerButtonItemNode.Content(appearance: normalButtonAppearance, image: .flipCamera), text: "", transition: transition)
@ -3885,7 +3919,7 @@ public final class VoiceChatController: ViewController {
self.leaveButton.update(size: sideButtonSize, content: CallControllerButtonItemNode.Content(appearance: .color(.custom(0xff3b30, 0.3)), image: .cancel), text: self.presentationData.strings.VoiceChat_Leave, transition: .immediate) self.leaveButton.update(size: sideButtonSize, content: CallControllerButtonItemNode.Content(appearance: .color(.custom(0xff3b30, 0.3)), image: .cancel), text: self.presentationData.strings.VoiceChat_Leave, transition: .immediate)
transition.updateAlpha(node: self.cameraButton.textNode, alpha: 0.0) transition.updateAlpha(node: self.cameraButton.textNode, alpha: hasVideo ? 1.0 : 0.0)
transition.updateAlpha(node: self.switchCameraButton.textNode, alpha: buttonsTitleAlpha) transition.updateAlpha(node: self.switchCameraButton.textNode, alpha: buttonsTitleAlpha)
transition.updateAlpha(node: self.audioButton.textNode, alpha: buttonsTitleAlpha) transition.updateAlpha(node: self.audioButton.textNode, alpha: buttonsTitleAlpha)
transition.updateAlpha(node: self.leaveButton.textNode, alpha: buttonsTitleAlpha) transition.updateAlpha(node: self.leaveButton.textNode, alpha: buttonsTitleAlpha)
@ -3920,7 +3954,9 @@ public final class VoiceChatController: ViewController {
if !self.isFullscreen { if !self.isFullscreen {
self.isExpanded = true self.isExpanded = true
self.updateIsFullscreen(true) self.updateIsFullscreen(true)
// self.tileListNode.isHidden = false }
if self.hasMainVideo {
self.tileListNode.isHidden = false
} }
if case .fullscreen = effectiveDisplayMode { if case .fullscreen = effectiveDisplayMode {
} else { } else {
@ -3986,8 +4022,7 @@ public final class VoiceChatController: ViewController {
var topCornersY = topPanelHeight var topCornersY = topPanelHeight
if isLandscape { if isLandscape {
listTopInset = topPanelHeight listTopInset = topPanelHeight
// topCornersY = -50.0 } else if self.mainVideoContainerNode != nil && self.isExpanded {
} else if self.mainVideoContainerNode != nil && self.isFullscreen {
let videoContainerHeight = min(mainVideoHeight, layout.size.width) let videoContainerHeight = min(mainVideoHeight, layout.size.width)
listTopInset += videoContainerHeight listTopInset += videoContainerHeight
topCornersY += videoContainerHeight topCornersY += videoContainerHeight
@ -3996,7 +4031,7 @@ public final class VoiceChatController: ViewController {
let listSize = CGSize(width: contentWidth, height: layout.size.height - listTopInset - (isLandscape ? layout.intrinsicInsets.bottom : bottomPanelHeight)) let listSize = CGSize(width: contentWidth, height: layout.size.height - listTopInset - (isLandscape ? layout.intrinsicInsets.bottom : bottomPanelHeight))
let topInset: CGFloat let topInset: CGFloat
if let (panInitialTopInset, panOffset) = self.panGestureArguments { if let (panInitialTopInset, panOffset) = self.panGestureArguments {
if self.isExpanded { if self.isExpanded && !self.hasMainVideo {
topInset = min(self.topInset ?? listSize.height, panInitialTopInset + max(0.0, panOffset)) topInset = min(self.topInset ?? listSize.height, panInitialTopInset + max(0.0, panOffset))
} else { } else {
topInset = max(0.0, panInitialTopInset + min(0.0, panOffset)) topInset = max(0.0, panInitialTopInset + min(0.0, panOffset))
@ -4236,10 +4271,21 @@ public final class VoiceChatController: ViewController {
self.updateButtons(animated: !isFirstTime) self.updateButtons(animated: !isFirstTime)
if self.audioButton.supernode === self.bottomPanelNode { if self.audioButton.supernode === self.bottomPanelNode {
transition.updateFrame(node: self.cameraButton, frame: firstButtonFrame) transition.updateFrameAsPositionAndBounds(node: self.switchCameraButton, frame: firstButtonFrame)
transition.updateFrame(node: self.switchCameraButton, frame: firstButtonFrame)
transition.updateFrame(node: self.audioButton, frame: secondButtonFrame) if !self.animatingButtons || transition.isAnimated {
transition.updateFrame(node: self.leaveButton, frame: forthButtonFrame) if self.call.hasVideo {
transition.updateFrameAsPositionAndBounds(node: self.cameraButton, frame: secondButtonFrame, completion: { [weak self] _ in
self?.animatingButtons = false
})
} else {
transition.updateFrameAsPositionAndBounds(node: self.cameraButton, frame: firstButtonFrame, completion: { [weak self] _ in
self?.animatingButtons = false
})
}
}
transition.updateFrameAsPositionAndBounds(node: self.audioButton, frame: secondButtonFrame)
transition.updateFrameAsPositionAndBounds(node: self.leaveButton, frame: forthButtonFrame)
} }
if isFirstTime { if isFirstTime {
while !self.enqueuedTransitions.isEmpty { while !self.enqueuedTransitions.isEmpty {
@ -4255,6 +4301,11 @@ public final class VoiceChatController: ViewController {
guard let (layout, navigationHeight) = self.validLayout else { guard let (layout, navigationHeight) = self.validLayout else {
return return
} }
if self.hasMainVideo && !self.isFullscreen {
self.updateIsFullscreen(true)
}
self.updateDecorationsLayout(transition: .immediate) self.updateDecorationsLayout(transition: .immediate)
self.animatingAppearance = true self.animatingAppearance = true
@ -4645,6 +4696,7 @@ public final class VoiceChatController: ViewController {
self.endpointToPeerId = endpointIdToPeerId self.endpointToPeerId = endpointIdToPeerId
self.peerIdToEndpoint = peerIdToEndpointId self.peerIdToEndpoint = peerIdToEndpointId
let previousPinnedEntry = self.pinnedEntry
self.pinnedEntry = pinnedEntry self.pinnedEntry = pinnedEntry
let previousEntries = self.currentEntries let previousEntries = self.currentEntries
@ -4652,6 +4704,12 @@ public final class VoiceChatController: ViewController {
self.currentEntries = entries self.currentEntries = entries
self.currentTileEntries = tileEntries self.currentTileEntries = tileEntries
if let previousPinnedEntry = previousPinnedEntry, case let .peer(previousPeerEntry) = previousPinnedEntry, let pinnedEntry = pinnedEntry, case let .peer(peerEntry) = pinnedEntry {
if previousPeerEntry.videoEndpointId != peerEntry.videoEndpointId || previousPeerEntry.screencastEndpointId != peerEntry.screencastEndpointId {
self.updateMainParticipant(waitForFullSize: false, updateMembers: false, force: true)
}
}
if previousEntries.count == entries.count { if previousEntries.count == entries.count {
var allEqual = true var allEqual = true
for i in 0 ..< previousEntries.count { for i in 0 ..< previousEntries.count {
@ -4685,7 +4743,8 @@ public final class VoiceChatController: ViewController {
self.enqueueTileTransition(tileTransition) self.enqueueTileTransition(tileTransition)
} }
private func updateMainParticipant(waitForFullSize: Bool, force: Bool = false) { private var didSetMainParticipant = false
private func updateMainParticipant(waitForFullSize: Bool, updateMembers: Bool = true, force: Bool = false) {
let effectiveMainParticipant = self.currentForcedSpeakerWithVideo ?? self.currentDominantSpeakerWithVideo let effectiveMainParticipant = self.currentForcedSpeakerWithVideo ?? self.currentDominantSpeakerWithVideo
guard effectiveMainParticipant != self.effectiveSpeakerWithVideo?.0 || force else { guard effectiveMainParticipant != self.effectiveSpeakerWithVideo?.0 || force else {
return return
@ -4718,10 +4777,8 @@ public final class VoiceChatController: ViewController {
} }
} }
} }
let completion = { let completion = {
self.updateMembers(muteState: self.effectiveMuteState, callMembers: self.currentCallMembers ?? ([], nil), invitedPeers: self.currentInvitedPeers ?? [], speakingPeers: self.currentSpeakingPeers ?? Set())
var updateLayout = false var updateLayout = false
if self.effectiveSpeakerWithVideo != nil && !self.isExpanded { if self.effectiveSpeakerWithVideo != nil && !self.isExpanded {
self.isExpanded = true self.isExpanded = true
@ -4734,10 +4791,11 @@ public final class VoiceChatController: ViewController {
if updateLayout { if updateLayout {
self.updateIsFullscreen(self.isExpanded) self.updateIsFullscreen(self.isExpanded)
self.animatingExpansion = true self.animatingExpansion = true
let transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .spring)
if let (layout, navigationHeight) = self.validLayout { if let (layout, navigationHeight) = self.validLayout {
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .easeInOut)) self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: transition)
} }
self.updateDecorationsLayout(transition: .animated(duration: 0.3, curve: .easeInOut), completion: { self.updateDecorationsLayout(transition: transition, completion: {
self.animatingExpansion = false self.animatingExpansion = false
}) })
} }
@ -4746,13 +4804,22 @@ public final class VoiceChatController: ViewController {
var waitForFullSize = waitForFullSize var waitForFullSize = waitForFullSize
if !self.isExpanded { if !self.isExpanded {
waitForFullSize = true waitForFullSize = true
self.mainVideoClippingNode.alpha = 0.0
} }
self.effectiveSpeakerWithVideo = effectivePeer.flatMap { ($0.0, $0.1) } self.effectiveSpeakerWithVideo = effectivePeer.flatMap { ($0.0, $0.1) }
if updateMembers {
self.updateMembers(muteState: self.effectiveMuteState, callMembers: self.currentCallMembers ?? ([], nil), invitedPeers: self.currentInvitedPeers ?? [], speakingPeers: self.currentSpeakingPeers ?? Set())
}
self.call.setFullSizeVideo(endpointId: effectivePeer?.1) self.call.setFullSizeVideo(endpointId: effectivePeer?.1)
self.mainVideoContainerNode?.updatePeer(peer: effectivePeer, waitForFullSize: waitForFullSize, completion: { self.mainVideoContainerNode?.updatePeer(peer: effectivePeer, waitForFullSize: waitForFullSize, completion: { [weak self] in
if waitForFullSize { if waitForFullSize {
completion() completion()
if let strongSelf = self, strongSelf.mainVideoClippingNode.alpha.isZero {
strongSelf.mainVideoClippingNode.alpha = 1.0
strongSelf.mainVideoClippingNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
}
} }
}) })
if !waitForFullSize { if !waitForFullSize {
@ -4814,6 +4881,7 @@ public final class VoiceChatController: ViewController {
if isScheduling && translation < 0.0 { if isScheduling && translation < 0.0 {
return return
} }
var topInset: CGFloat = 0.0 var topInset: CGFloat = 0.0
if let (currentTopInset, currentPanOffset) = self.panGestureArguments { if let (currentTopInset, currentPanOffset) = self.panGestureArguments {
topInset = currentTopInset topInset = currentTopInset
@ -4836,7 +4904,7 @@ public final class VoiceChatController: ViewController {
self.updateIsFullscreen(false) self.updateIsFullscreen(false)
} }
if self.isExpanded { if self.isExpanded && !self.hasMainVideo {
} else { } else {
if currentOffset > 0.0 { if currentOffset > 0.0 {
self.listNode.scroller.panGestureRecognizer.setTranslation(CGPoint(), in: self.listNode.scroller) self.listNode.scroller.panGestureRecognizer.setTranslation(CGPoint(), in: self.listNode.scroller)
@ -4848,7 +4916,7 @@ public final class VoiceChatController: ViewController {
self.updateDecorationsLayout(transition: .immediate) self.updateDecorationsLayout(transition: .immediate)
} }
if !self.isExpanded { if !self.isExpanded || self.hasMainVideo {
var bounds = self.contentContainer.bounds var bounds = self.contentContainer.bounds
bounds.origin.y = -translation bounds.origin.y = -translation
bounds.origin.y = min(0.0, bounds.origin.y) bounds.origin.y = min(0.0, bounds.origin.y)
@ -4882,7 +4950,7 @@ public final class VoiceChatController: ViewController {
topInset = self.listNode.frame.height topInset = self.listNode.frame.height
} }
if self.isExpanded { if self.isExpanded && !self.hasMainVideo {
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.isExpanded = false self.isExpanded = false
@ -4934,7 +5002,7 @@ public final class VoiceChatController: ViewController {
self.updateDecorationsLayout(transition: .animated(duration: 0.3, curve: .easeInOut), completion: { self.updateDecorationsLayout(transition: .animated(duration: 0.3, curve: .easeInOut), completion: {
self.animatingExpansion = false self.animatingExpansion = false
}) })
} else if !isScheduling { } else if !isScheduling && !self.hasMainVideo {
self.updateIsFullscreen(false) self.updateIsFullscreen(false)
self.animatingExpansion = true self.animatingExpansion = true
self.listNode.scroller.setContentOffset(CGPoint(), animated: false) self.listNode.scroller.setContentOffset(CGPoint(), animated: false)
@ -4946,7 +5014,7 @@ public final class VoiceChatController: ViewController {
self.animatingExpansion = false self.animatingExpansion = false
}) })
} }
if !dismissing { if !dismissing && self.hasMainVideo {
var bounds = self.contentContainer.bounds var bounds = self.contentContainer.bounds
let previousBounds = bounds let previousBounds = bounds
bounds.origin.y = 0.0 bounds.origin.y = 0.0
@ -5304,7 +5372,7 @@ public final class VoiceChatController: ViewController {
switch self.displayMode { switch self.displayMode {
case .default: case .default:
let location = videoContainerNode.view.convert(videoContainerNode.otherVideoButtonNode.frame, to: nil) let location = videoContainerNode.view.convert(videoContainerNode.otherVideoWrapperNode.frame, to: nil)
self.controller?.present(TooltipScreen(text: screencast ? self.presentationData.strings.VoiceChat_TapToViewCameraVideo : self.presentationData.strings.VoiceChat_TapToViewScreenVideo, icon: nil, location: .point(location.offsetBy(dx: -9.0, dy: 0.0), .right), displayDuration: .custom(3.0), shouldDismissOnTouch: { _ in self.controller?.present(TooltipScreen(text: screencast ? self.presentationData.strings.VoiceChat_TapToViewCameraVideo : self.presentationData.strings.VoiceChat_TapToViewScreenVideo, icon: nil, location: .point(location.offsetBy(dx: -9.0, dy: 0.0), .right), displayDuration: .custom(3.0), shouldDismissOnTouch: { _ in
return .dismiss(consume: false) return .dismiss(consume: false)
}), in: .window(.root)) }), in: .window(.root))

View File

@ -814,6 +814,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
if item.pinned { if item.pinned {
self.avatarNode.alpha = 1.0 self.avatarNode.alpha = 1.0
videoNode.alpha = 0.0 videoNode.alpha = 0.0
startContainerPosition = startContainerPosition.offsetBy(dx: 0.0, dy: 9.0)
} else { } else {
self.avatarNode.alpha = 0.0 self.avatarNode.alpha = 0.0
} }
@ -971,6 +972,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
let currentItem = self.layoutParams?.0 let currentItem = self.layoutParams?.0
let currentTitle = self.currentTitle let currentTitle = self.currentTitle
let hasVideo = self.videoNode != nil
return { item, params, first, last in return { item, params, first, last in
var updatedTheme: PresentationTheme? var updatedTheme: PresentationTheme?
@ -988,14 +990,16 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
if case .list = item.style, item.transparent{ if case .list = item.style, item.transparent{
titleFont = Font.semibold(17.0) titleFont = Font.semibold(17.0)
titleColor = UIColor(rgb: 0xffffff, alpha: 0.65) titleColor = UIColor(rgb: 0xffffff, alpha: 0.65)
} else if case .tile = item.style { } else if case .tile = item.style, !hasVideo {
switch item.text { switch item.text {
case let .text(_, _, textColor): case let .text(_, _, textColor):
switch textColor { switch textColor {
case .generic: case .generic:
titleColor = item.presentationData.theme.list.itemPrimaryTextColor titleColor = item.presentationData.theme.list.itemPrimaryTextColor
case .accent: case .accent:
titleColor = item.presentationData.theme.list.itemAccentColor if item.peer.id != item.context.account.peerId {
titleColor = item.presentationData.theme.list.itemAccentColor
}
case .constructive: case .constructive:
titleColor = constructiveColor titleColor = constructiveColor
case .destructive: case .destructive:
@ -1550,6 +1554,8 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
strongSelf.borderImageNode.isHidden = !item.pinned || item.style == .list strongSelf.borderImageNode.isHidden = !item.pinned || item.style == .list
let canUpdateAvatarVisibility = !strongSelf.isExtracted && !strongSelf.animatingExtraction
if let videoNode = videoNode { if let videoNode = videoNode {
let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut) let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut)
if !strongSelf.isExtracted && !strongSelf.animatingExtraction { if !strongSelf.isExtracted && !strongSelf.animatingExtraction {
@ -1575,7 +1581,9 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
} else { } else {
if item.pinned { if item.pinned {
videoNode.alpha = 0.0 videoNode.alpha = 0.0
strongSelf.avatarNode.alpha = 1.0 if canUpdateAvatarVisibility {
strongSelf.avatarNode.alpha = 1.0
}
} else if strongSelf.videoReady { } else if strongSelf.videoReady {
videoNode.alpha = 1.0 videoNode.alpha = 1.0
strongSelf.avatarNode.alpha = 0.0 strongSelf.avatarNode.alpha = 0.0
@ -1584,7 +1592,9 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
} else { } else {
if item.pinned { if item.pinned {
videoNode.alpha = 0.0 videoNode.alpha = 0.0
strongSelf.avatarNode.alpha = 1.0 if canUpdateAvatarVisibility {
strongSelf.avatarNode.alpha = 1.0
}
} else if strongSelf.videoReady { } else if strongSelf.videoReady {
videoNode.alpha = 1.0 videoNode.alpha = 1.0
strongSelf.avatarNode.alpha = 0.0 strongSelf.avatarNode.alpha = 0.0
@ -1602,7 +1612,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
videoNode.position = CGPoint(x: videoSize.width / 2.0, y: videoSize.height / 2.0) videoNode.position = CGPoint(x: videoSize.width / 2.0, y: videoSize.height / 2.0)
videoNode.bounds = CGRect(origin: CGPoint(), size: videoSize) videoNode.bounds = CGRect(origin: CGPoint(), size: videoSize)
} }
if videoNodeUpdated { if videoNodeUpdated {
strongSelf.videoReadyDelayed = false strongSelf.videoReadyDelayed = false
strongSelf.videoReadyDisposable.set((videoNode.ready strongSelf.videoReadyDisposable.set((videoNode.ready
@ -1612,13 +1622,18 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
strongSelf.videoReadyDelayed = true strongSelf.videoReadyDelayed = true
} }
strongSelf.videoReady = ready strongSelf.videoReady = ready
if let videoNode = strongSelf.videoNode, ready && (strongSelf.item?.transparent != true) { if let videoNode = strongSelf.videoNode, ready && !item.transparent {
if strongSelf.videoReadyDelayed { if strongSelf.videoReadyDelayed {
Queue.mainQueue().after(0.15) { Queue.mainQueue().after(0.15) {
switch item.style { guard let currentItem = strongSelf.item else {
return
}
switch currentItem.style {
case .list: case .list:
if item.pinned { if currentItem.pinned {
strongSelf.avatarNode.alpha = 1.0 if canUpdateAvatarVisibility {
strongSelf.avatarNode.alpha = 1.0
}
videoNode.alpha = 0.0 videoNode.alpha = 0.0
} else { } else {
strongSelf.avatarNode.alpha = 0.0 strongSelf.avatarNode.alpha = 0.0
@ -1627,8 +1642,10 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
videoNode.alpha = 1.0 videoNode.alpha = 1.0
} }
case .tile: case .tile:
if item.pinned { if currentItem.pinned {
strongSelf.avatarNode.alpha = 1.0 if canUpdateAvatarVisibility {
strongSelf.avatarNode.alpha = 1.0
}
videoNode.alpha = 0.0 videoNode.alpha = 0.0
} else { } else {
strongSelf.avatarNode.alpha = 0.0 strongSelf.avatarNode.alpha = 0.0
@ -1640,7 +1657,9 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
} }
} else { } else {
if item.pinned { if item.pinned {
strongSelf.avatarNode.alpha = 1.0 if canUpdateAvatarVisibility {
strongSelf.avatarNode.alpha = 1.0
}
videoNode.alpha = 0.0 videoNode.alpha = 0.0
} else { } else {
strongSelf.avatarNode.alpha = 0.0 strongSelf.avatarNode.alpha = 0.0

View File

@ -1025,6 +1025,33 @@ final class ChatMediaInputNode: ChatInputNode {
let _ = self?.controllerInteraction.sendBotContextResultAsGif(collection, result, sourceNode, sourceRect) let _ = self?.controllerInteraction.sendBotContextResultAsGif(collection, result, sourceNode, sourceRect)
} }
}))) })))
if let (_, _, _, _, _, _, _, _, interfaceState, _, _) = strongSelf.validLayout {
var isScheduledMessages = false
if case .scheduledMessages = interfaceState.subject {
isScheduledMessages = true
}
if !isScheduledMessages {
if case let .peer(peerId) = interfaceState.chatLocation {
if peerId != self?.context.account.peerId && peerId.namespace != Namespaces.Peer.SecretChat {
items.append(.action(ContextMenuActionItem(text: strongSelf.strings.Conversation_SendMessage_SendSilently, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/SilentIcon"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in
f(.default)
})))
}
items.append(.action(ContextMenuActionItem(text: strongSelf.strings.Conversation_SendMessage_ScheduleMessage, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/ScheduleIcon"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in
f(.default)
})))
}
}
}
if isSaved || isGifSaved { if isSaved || isGifSaved {
items.append(.action(ContextMenuActionItem(text: strongSelf.strings.Conversation_ContextMenuDelete, textColor: .destructive, icon: { theme in items.append(.action(ContextMenuActionItem(text: strongSelf.strings.Conversation_ContextMenuDelete, textColor: .destructive, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor)
@ -1098,13 +1125,34 @@ final class ChatMediaInputNode: ChatInputNode {
|> deliverOnMainQueue |> deliverOnMainQueue
|> map { isStarred -> (ASDisplayNode, PeekControllerContent)? in |> map { isStarred -> (ASDisplayNode, PeekControllerContent)? in
if let strongSelf = self { if let strongSelf = self {
var menuItems: [ContextMenuItem] = [] var menuItems: [ContextMenuItem] = []
menuItems = [ if let (_, _, _, _, _, _, _, _, interfaceState, _, _) = strongSelf.validLayout {
.action(ContextMenuActionItem(text: strongSelf.strings.StickerPack_Send, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Resend"), color: theme.contextMenu.primaryColor) }, action: { _, f in var isScheduledMessages = false
f(.default) if case .scheduledMessages = interfaceState.subject {
isScheduledMessages = true
// let _ = strongSelf.sendSticker?(.standalone(media: item.file), node, rect) }
})), if !isScheduledMessages {
if case let .peer(peerId) = interfaceState.chatLocation {
if peerId != self?.context.account.peerId && peerId.namespace != Namespaces.Peer.SecretChat {
menuItems.append(.action(ContextMenuActionItem(text: strongSelf.strings.Conversation_SendMessage_SendSilently, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/SilentIcon"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in
f(.default)
})))
}
menuItems.append(.action(ContextMenuActionItem(text: strongSelf.strings.Conversation_SendMessage_ScheduleMessage, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/ScheduleIcon"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in
f(.default)
})))
}
}
}
menuItems.append(
.action(ContextMenuActionItem(text: isStarred ? strongSelf.strings.Stickers_RemoveFromFavorites : strongSelf.strings.Stickers_AddToFavorites, icon: { theme in generateTintedImage(image: isStarred ? UIImage(bundleImageName: "Chat/Context Menu/Unstar") : UIImage(bundleImageName: "Chat/Context Menu/Rate"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in .action(ContextMenuActionItem(text: isStarred ? strongSelf.strings.Stickers_RemoveFromFavorites : strongSelf.strings.Stickers_AddToFavorites, icon: { theme in generateTintedImage(image: isStarred ? UIImage(bundleImageName: "Chat/Context Menu/Unstar") : UIImage(bundleImageName: "Chat/Context Menu/Rate"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
f(.default) f(.default)
@ -1115,8 +1163,9 @@ final class ChatMediaInputNode: ChatInputNode {
let _ = addSavedSticker(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, file: item.file).start() let _ = addSavedSticker(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, file: item.file).start()
} }
} }
})), }))
.action(ContextMenuActionItem(text: strongSelf.strings.StickerPack_ViewPack, icon: { theme in )
menuItems.append(.action(ContextMenuActionItem(text: strongSelf.strings.StickerPack_ViewPack, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Sticker"), color: theme.actionSheet.primaryTextColor) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Sticker"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in }, action: { _, f in
f(.default) f(.default)
@ -1143,8 +1192,8 @@ final class ChatMediaInputNode: ChatInputNode {
} }
} }
} }
})) })))
]
return (itemNode, StickerPreviewPeekContent(account: strongSelf.context.account, item: item, menu: menuItems)) return (itemNode, StickerPreviewPeekContent(account: strongSelf.context.account, item: item, menu: menuItems))
} else { } else {
@ -1185,12 +1234,33 @@ final class ChatMediaInputNode: ChatInputNode {
|> map { isStarred -> (ASDisplayNode, PeekControllerContent)? in |> map { isStarred -> (ASDisplayNode, PeekControllerContent)? in
if let strongSelf = self { if let strongSelf = self {
var menuItems: [ContextMenuItem] = [] var menuItems: [ContextMenuItem] = []
menuItems = [ if let (_, _, _, _, _, _, _, _, interfaceState, _, _) = strongSelf.validLayout {
.action(ContextMenuActionItem(text: strongSelf.strings.StickerPack_Send, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Resend"), color: theme.contextMenu.primaryColor) }, action: { _, f in var isScheduledMessages = false
f(.default) if case .scheduledMessages = interfaceState.subject {
isScheduledMessages = true
// let _ = strongSelf.sendSticker?(.standalone(media: item.file), node, rect) }
})), if !isScheduledMessages {
if case let .peer(peerId) = interfaceState.chatLocation {
if peerId != self?.context.account.peerId && peerId.namespace != Namespaces.Peer.SecretChat {
menuItems.append(.action(ContextMenuActionItem(text: strongSelf.strings.Conversation_SendMessage_SendSilently, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/SilentIcon"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in
f(.default)
})))
}
menuItems.append(.action(ContextMenuActionItem(text: strongSelf.strings.Conversation_SendMessage_ScheduleMessage, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/ScheduleIcon"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in
f(.default)
})))
}
}
}
menuItems.append(
.action(ContextMenuActionItem(text: isStarred ? strongSelf.strings.Stickers_RemoveFromFavorites : strongSelf.strings.Stickers_AddToFavorites, icon: { theme in generateTintedImage(image: isStarred ? UIImage(bundleImageName: "Chat/Context Menu/Unstar") : UIImage(bundleImageName: "Chat/Context Menu/Rate"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in .action(ContextMenuActionItem(text: isStarred ? strongSelf.strings.Stickers_RemoveFromFavorites : strongSelf.strings.Stickers_AddToFavorites, icon: { theme in generateTintedImage(image: isStarred ? UIImage(bundleImageName: "Chat/Context Menu/Unstar") : UIImage(bundleImageName: "Chat/Context Menu/Rate"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
f(.default) f(.default)
@ -1201,7 +1271,9 @@ final class ChatMediaInputNode: ChatInputNode {
let _ = addSavedSticker(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, file: item.file).start() let _ = addSavedSticker(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, file: item.file).start()
} }
} }
})), }))
)
menuItems.append(
.action(ContextMenuActionItem(text: strongSelf.strings.StickerPack_ViewPack, icon: { theme in .action(ContextMenuActionItem(text: strongSelf.strings.StickerPack_ViewPack, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Sticker"), color: theme.actionSheet.primaryTextColor) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Sticker"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in }, action: { _, f in
@ -1230,7 +1302,7 @@ final class ChatMediaInputNode: ChatInputNode {
} }
} }
})) }))
] )
return (itemNode, StickerPreviewPeekContent(account: strongSelf.context.account, item: .pack(item), menu: menuItems)) return (itemNode, StickerPreviewPeekContent(account: strongSelf.context.account, item: .pack(item), menu: menuItems))
} else { } else {
return nil return nil

View File

@ -314,10 +314,16 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, UIScrollViewDel
self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4) self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
let offset = self.bounds.size.height - self.contentBackgroundNode.frame.minY let offset = self.bounds.size.height - self.contentBackgroundNode.frame.minY
let dimPosition = self.dimNode.layer.position let dimPosition = self.dimNode.layer.position
self.dimNode.layer.animatePosition(from: CGPoint(x: dimPosition.x, y: dimPosition.y - offset), to: dimPosition, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
self.layer.animateBoundsOriginYAdditive(from: -offset, to: 0.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring) let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)
let targetBounds = self.bounds
self.bounds = self.bounds.offsetBy(dx: 0.0, dy: -offset)
self.dimNode.position = CGPoint(x: dimPosition.x, y: dimPosition.y - offset)
transition.animateView({
self.bounds = targetBounds
self.dimNode.position = dimPosition
})
} }
func animateOut(completion: (() -> Void)? = nil) { func animateOut(completion: (() -> Void)? = nil) {

View File

@ -401,10 +401,16 @@ class ChatTimerScreenNode: ViewControllerTracingNode, UIScrollViewDelegate, UIPi
self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4) self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
let offset = self.bounds.size.height - self.contentBackgroundNode.frame.minY let offset = self.bounds.size.height - self.contentBackgroundNode.frame.minY
let dimPosition = self.dimNode.layer.position let dimPosition = self.dimNode.layer.position
self.dimNode.layer.animatePosition(from: CGPoint(x: dimPosition.x, y: dimPosition.y - offset), to: dimPosition, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
self.layer.animateBoundsOriginYAdditive(from: -offset, to: 0.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring) let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)
let targetBounds = self.bounds
self.bounds = self.bounds.offsetBy(dx: 0.0, dy: -offset)
self.dimNode.position = CGPoint(x: dimPosition.x, y: dimPosition.y - offset)
transition.animateView({
self.bounds = targetBounds
self.dimNode.position = dimPosition
})
} }
func animateOut(completion: (() -> Void)? = nil) { func animateOut(completion: (() -> Void)? = nil) {