diff --git a/submodules/LocationUI/Sources/LocationDistancePickerScreen.swift b/submodules/LocationUI/Sources/LocationDistancePickerScreen.swift index 78ce35ae8e..ece1421f38 100644 --- a/submodules/LocationUI/Sources/LocationDistancePickerScreen.swift +++ b/submodules/LocationUI/Sources/LocationDistancePickerScreen.swift @@ -552,7 +552,12 @@ class LocationDistancePickerScreenNode: ViewControllerTracingNode, UIScrollViewD self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) 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) { diff --git a/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewControllerNode.swift b/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewControllerNode.swift index 2816256be2..6dcbd9e908 100644 --- a/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewControllerNode.swift +++ b/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewControllerNode.swift @@ -549,11 +549,17 @@ final class StickerPackPreviewControllerNode: ViewControllerTracingNode, UIScrol func animateIn() { 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 - 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) { diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatCameraPreviewController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatCameraPreviewController.swift index bdcc420119..a2b79a434c 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatCameraPreviewController.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatCameraPreviewController.swift @@ -287,15 +287,15 @@ private class VoiceChatCameraPreviewControllerNode: ViewControllerTracingNode, U self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4) let offset = self.bounds.size.height - self.contentBackgroundNode.frame.minY - 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 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 }) self.applicationStateDisposable = (self.context.sharedContext.applicationBindings.applicationIsActive diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift index c44827a022..659570199a 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift @@ -225,7 +225,7 @@ final class GroupVideoNode: ASDisplayNode { rotatedVideoFrame.size.width = ceil(rotatedVideoFrame.size.width) 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.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 // 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 candidateVideoNode: GroupVideoNode? - fileprivate let otherVideoButtonNode: HighlightTrackingButtonNode - private let otherVideoWrapperNode: ASDisplayNode + private let otherVideoButtonNode: HighlightTrackingButtonNode + fileprivate let otherVideoWrapperNode: ASDisplayNode private let otherVideoShadowNode: ASImageNode private var otherVideoNode: GroupVideoNode? @@ -270,6 +270,9 @@ private final class MainVideoContainerNode: ASDisplayNode { var tapped: (() -> Void)? var otherVideoTapped: (() -> Void)? + private let videoReadyDisposable = MetaDisposable() + private let otherVideoReadyDisposable = MetaDisposable() + init(context: AccountContext, call: PresentationGroupCall) { self.context = context self.call = call @@ -337,6 +340,11 @@ private final class MainVideoContainerNode: ASDisplayNode { self.otherVideoButtonNode.addTarget(self, action: #selector(self.otherVideoPressed), forControlEvents: .touchUpInside) } + deinit { + self.videoReadyDisposable.dispose() + self.otherVideoReadyDisposable.dispose() + } + override func didLoad() { super.didLoad() @@ -361,10 +369,6 @@ private final class MainVideoContainerNode: ASDisplayNode { let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut) if let otherEndpointId = otherEndpointId { 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 guard let strongSelf = self, let videoView = videoView else { return @@ -380,11 +384,24 @@ private final class MainVideoContainerNode: ASDisplayNode { if let (size, sideInset, isLandscape) = strongSelf.validLayout { 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 { if let otherVideoNode = self.otherVideoNode { self.otherVideoNode = nil + self.otherVideoReadyDisposable.set(nil) let completion = { otherVideoNode.removeFromSupernode() } @@ -404,46 +421,38 @@ private final class MainVideoContainerNode: ASDisplayNode { guard let strongSelf = self, let videoView = videoView else { return } - - if waitForFullSize { - let candidateVideoNode = GroupVideoNode(videoView: videoView) - strongSelf.candidateVideoNode = candidateVideoNode - - Queue.mainQueue().after(0.3, { [weak candidateVideoNode] in - guard let strongSelf = self, let videoNode = candidateVideoNode, videoNode === strongSelf.candidateVideoNode else { - 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?() + + let videoNode = GroupVideoNode(videoView: videoView) + if let currentVideoNode = strongSelf.currentVideoNode { + strongSelf.currentVideoNode = nil + + currentVideoNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak currentVideoNode] _ in + currentVideoNode?.removeFromSupernode() }) + } + 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 { - strongSelf.candidateVideoNode = 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) - } + strongSelf.videoReadyDisposable.set(nil) completion?() } } }) } } else { + self.videoReadyDisposable.set(nil) + self.otherVideoReadyDisposable.set(nil) if let currentVideoNode = self.currentVideoNode { currentVideoNode.removeFromSupernode() self.currentVideoNode = nil @@ -456,7 +465,7 @@ private final class MainVideoContainerNode: ASDisplayNode { if let currentVideoNode = self.currentVideoNode { 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) @@ -502,6 +511,7 @@ public final class VoiceChatController: ViewController { private final class Interaction { let updateIsMuted: (PeerId, Bool) -> Void let pinPeer: (PeerId) -> Void + let togglePeerVideo: (PeerId) -> Void let openInvite: () -> Void let peerContextAction: (PeerEntry, ASDisplayNode, ContextGesture?) -> Void let setPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void @@ -515,6 +525,7 @@ public final class VoiceChatController: ViewController { init( updateIsMuted: @escaping (PeerId, Bool) -> Void, pinPeer: @escaping (PeerId) -> Void, + togglePeerVideo: @escaping (PeerId) -> Void, openInvite: @escaping () -> Void, peerContextAction: @escaping (PeerEntry, ASDisplayNode, ContextGesture?) -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, @@ -522,6 +533,7 @@ public final class VoiceChatController: ViewController { ) { self.updateIsMuted = updateIsMuted self.pinPeer = pinPeer + self.togglePeerVideo = togglePeerVideo self.openInvite = openInvite self.peerContextAction = peerContextAction self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions @@ -870,7 +882,11 @@ public final class VoiceChatController: ViewController { if case .list = peerEntry.style { interaction.peerContextAction(peerEntry, node, 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 interaction.peerContextAction(peerEntry, node, gesture) @@ -1237,6 +1253,11 @@ public final class VoiceChatController: ViewController { } strongSelf.updateMainParticipant(waitForFullSize: false) } + }, togglePeerVideo: { [weak self] peerId in + guard let strongSelf = self else { + return + } + strongSelf.mainVideoContainerNode?.otherVideoTapped?() }, openInvite: { [weak self] in guard let strongSelf = self else { return @@ -2043,7 +2064,7 @@ public final class VoiceChatController: ViewController { if strongSelf.currentDominantSpeakerWithVideo != peerId { strongSelf.currentDominantSpeakerWithVideo = peerId 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) strongSelf.videoNodes.append((endpointId, videoNode)) - if let (layout, navigationHeight) = strongSelf.validLayout { - strongSelf.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .immediate) + if let _ = strongSelf.validLayout { +// strongSelf.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .immediate) loop: for i in 0 ..< strongSelf.currentEntries.count { let entry = strongSelf.currentEntries[i] @@ -2178,7 +2199,6 @@ public final class VoiceChatController: ViewController { strongSelf.requestedVideoSources.remove(source) } - var updated = false for i in (0 ..< strongSelf.videoNodes.count).reversed() { if !validSources.contains(strongSelf.videoNodes[i].0) { let endpointId = strongSelf.videoNodes[i].0 @@ -2191,15 +2211,14 @@ public final class VoiceChatController: ViewController { case let .peer(peerEntry): if peerEntry.effectiveVideoEndpointId == endpointId { 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.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.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: j, previousIndex: j, item: tileEntry.item(context: strongSelf.context, presentationData: presentationData, interaction: strongSelf.itemInteraction!, transparent: false), directionHint: nil)], options: [.Synchronous], updateOpaqueState: nil) break loop } default: break } } - updated = true } } @@ -2213,13 +2232,10 @@ public final class VoiceChatController: ViewController { } strongSelf.updateMainParticipant(waitForFullSize: false) } - } else if strongSelf.currentDominantSpeakerWithVideo != nil && !strongSelf.requestedVideoSources.isEmpty { - strongSelf.updateMainParticipant(waitForFullSize: true) - } - - if updated { - if let (layout, navigationHeight) = strongSelf.validLayout { - strongSelf.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .immediate) + } else if strongSelf.currentDominantSpeakerWithVideo != nil && !strongSelf.requestedVideoSources.isEmpty && !strongSelf.didSetMainParticipant { + strongSelf.didSetMainParticipant = true + Queue.mainQueue().after(0.1) { + strongSelf.updateMainParticipant(waitForFullSize: true) } } })) @@ -2247,7 +2263,7 @@ public final class VoiceChatController: ViewController { } self.mainVideoContainerNode?.tapped = { [weak self] in - if let strongSelf = self { + if let strongSelf = self, !strongSelf.animatingExpansion { var effectiveDisplayMode = strongSelf.displayMode var isLandscape = false 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() { if self.call.hasVideo || self.call.hasScreencast { self.call.disableVideo() 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 { self.call.makeOutgoingVideoView { [weak self] view in 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 if let strongSelf = self { 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 self?.call.switchVideoCamera() @@ -3444,6 +3471,10 @@ public final class VoiceChatController: ViewController { return controlsHidden ? 0.0 : fullscreenBottomAreaHeight } } + + private var hasMainVideo: Bool { + return self.mainVideoContainerNode != nil && self.effectiveSpeakerWithVideo != nil + } private var bringVideoToBackOnCompletion = false 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 topInset: CGFloat 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)) } else { topInset = max(0.0, panInitialTopInset + min(0.0, panOffset)) @@ -3543,7 +3574,7 @@ public final class VoiceChatController: ViewController { videoY = layout.statusBarHeight ?? 20.0 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)) } @@ -3648,7 +3679,7 @@ public final class VoiceChatController: ViewController { let previousBottomCornersFrame = self.bottomCornersNode.frame if !bottomCornersFrame.equalTo(previousBottomCornersFrame) { 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) 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 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.audioButton, alpha: self.call.hasVideo ? 0.0 : 1.0) + transition.updateAlpha(node: self.switchCameraButton, alpha: hasVideo ? 1.0 : 0.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) @@ -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) - 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.audioButton.textNode, alpha: buttonsTitleAlpha) transition.updateAlpha(node: self.leaveButton.textNode, alpha: buttonsTitleAlpha) @@ -3920,7 +3954,9 @@ public final class VoiceChatController: ViewController { if !self.isFullscreen { self.isExpanded = true self.updateIsFullscreen(true) -// self.tileListNode.isHidden = false + } + if self.hasMainVideo { + self.tileListNode.isHidden = false } if case .fullscreen = effectiveDisplayMode { } else { @@ -3986,8 +4022,7 @@ public final class VoiceChatController: ViewController { var topCornersY = topPanelHeight if isLandscape { listTopInset = topPanelHeight -// topCornersY = -50.0 - } else if self.mainVideoContainerNode != nil && self.isFullscreen { + } else if self.mainVideoContainerNode != nil && self.isExpanded { let videoContainerHeight = min(mainVideoHeight, layout.size.width) listTopInset += 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 topInset: CGFloat 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)) } else { topInset = max(0.0, panInitialTopInset + min(0.0, panOffset)) @@ -4236,10 +4271,21 @@ public final class VoiceChatController: ViewController { self.updateButtons(animated: !isFirstTime) if self.audioButton.supernode === self.bottomPanelNode { - transition.updateFrame(node: self.cameraButton, frame: firstButtonFrame) - transition.updateFrame(node: self.switchCameraButton, frame: firstButtonFrame) - transition.updateFrame(node: self.audioButton, frame: secondButtonFrame) - transition.updateFrame(node: self.leaveButton, frame: forthButtonFrame) + transition.updateFrameAsPositionAndBounds(node: self.switchCameraButton, frame: firstButtonFrame) + + if !self.animatingButtons || transition.isAnimated { + 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 { while !self.enqueuedTransitions.isEmpty { @@ -4255,6 +4301,11 @@ public final class VoiceChatController: ViewController { guard let (layout, navigationHeight) = self.validLayout else { return } + + if self.hasMainVideo && !self.isFullscreen { + self.updateIsFullscreen(true) + } + self.updateDecorationsLayout(transition: .immediate) self.animatingAppearance = true @@ -4645,6 +4696,7 @@ public final class VoiceChatController: ViewController { self.endpointToPeerId = endpointIdToPeerId self.peerIdToEndpoint = peerIdToEndpointId + let previousPinnedEntry = self.pinnedEntry self.pinnedEntry = pinnedEntry let previousEntries = self.currentEntries @@ -4652,6 +4704,12 @@ public final class VoiceChatController: ViewController { self.currentEntries = entries 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 { var allEqual = true for i in 0 ..< previousEntries.count { @@ -4685,7 +4743,8 @@ public final class VoiceChatController: ViewController { 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 guard effectiveMainParticipant != self.effectiveSpeakerWithVideo?.0 || force else { return @@ -4718,10 +4777,8 @@ public final class VoiceChatController: ViewController { } } } - + let completion = { - self.updateMembers(muteState: self.effectiveMuteState, callMembers: self.currentCallMembers ?? ([], nil), invitedPeers: self.currentInvitedPeers ?? [], speakingPeers: self.currentSpeakingPeers ?? Set()) - var updateLayout = false if self.effectiveSpeakerWithVideo != nil && !self.isExpanded { self.isExpanded = true @@ -4734,10 +4791,11 @@ public final class VoiceChatController: ViewController { if updateLayout { self.updateIsFullscreen(self.isExpanded) self.animatingExpansion = true + let transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .spring) 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 }) } @@ -4746,13 +4804,22 @@ public final class VoiceChatController: ViewController { var waitForFullSize = waitForFullSize if !self.isExpanded { waitForFullSize = true + self.mainVideoClippingNode.alpha = 0.0 } 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.mainVideoContainerNode?.updatePeer(peer: effectivePeer, waitForFullSize: waitForFullSize, completion: { + self.mainVideoContainerNode?.updatePeer(peer: effectivePeer, waitForFullSize: waitForFullSize, completion: { [weak self] in if waitForFullSize { 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 { @@ -4814,6 +4881,7 @@ public final class VoiceChatController: ViewController { if isScheduling && translation < 0.0 { return } + var topInset: CGFloat = 0.0 if let (currentTopInset, currentPanOffset) = self.panGestureArguments { topInset = currentTopInset @@ -4836,7 +4904,7 @@ public final class VoiceChatController: ViewController { self.updateIsFullscreen(false) } - if self.isExpanded { + if self.isExpanded && !self.hasMainVideo { } else { if currentOffset > 0.0 { self.listNode.scroller.panGestureRecognizer.setTranslation(CGPoint(), in: self.listNode.scroller) @@ -4848,7 +4916,7 @@ public final class VoiceChatController: ViewController { self.updateDecorationsLayout(transition: .immediate) } - if !self.isExpanded { + if !self.isExpanded || self.hasMainVideo { var bounds = self.contentContainer.bounds bounds.origin.y = -translation bounds.origin.y = min(0.0, bounds.origin.y) @@ -4882,7 +4950,7 @@ public final class VoiceChatController: ViewController { topInset = self.listNode.frame.height } - if self.isExpanded { + if self.isExpanded && !self.hasMainVideo { self.panGestureArguments = nil if velocity.y > 300.0 || offset > topInset / 2.0 { self.isExpanded = false @@ -4934,7 +5002,7 @@ public final class VoiceChatController: ViewController { self.updateDecorationsLayout(transition: .animated(duration: 0.3, curve: .easeInOut), completion: { self.animatingExpansion = false }) - } else if !isScheduling { + } else if !isScheduling && !self.hasMainVideo { self.updateIsFullscreen(false) self.animatingExpansion = true self.listNode.scroller.setContentOffset(CGPoint(), animated: false) @@ -4946,7 +5014,7 @@ public final class VoiceChatController: ViewController { self.animatingExpansion = false }) } - if !dismissing { + if !dismissing && self.hasMainVideo { var bounds = self.contentContainer.bounds let previousBounds = bounds bounds.origin.y = 0.0 @@ -5304,7 +5372,7 @@ public final class VoiceChatController: ViewController { switch self.displayMode { 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 return .dismiss(consume: false) }), in: .window(.root)) diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatParticipantItem.swift b/submodules/TelegramCallsUI/Sources/VoiceChatParticipantItem.swift index 24ac757560..6b2624a988 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatParticipantItem.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatParticipantItem.swift @@ -814,6 +814,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode { if item.pinned { self.avatarNode.alpha = 1.0 videoNode.alpha = 0.0 + startContainerPosition = startContainerPosition.offsetBy(dx: 0.0, dy: 9.0) } else { self.avatarNode.alpha = 0.0 } @@ -971,6 +972,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode { let currentItem = self.layoutParams?.0 let currentTitle = self.currentTitle + let hasVideo = self.videoNode != nil return { item, params, first, last in var updatedTheme: PresentationTheme? @@ -988,14 +990,16 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode { if case .list = item.style, item.transparent{ titleFont = Font.semibold(17.0) titleColor = UIColor(rgb: 0xffffff, alpha: 0.65) - } else if case .tile = item.style { + } else if case .tile = item.style, !hasVideo { switch item.text { case let .text(_, _, textColor): switch textColor { case .generic: titleColor = item.presentationData.theme.list.itemPrimaryTextColor case .accent: - titleColor = item.presentationData.theme.list.itemAccentColor + if item.peer.id != item.context.account.peerId { + titleColor = item.presentationData.theme.list.itemAccentColor + } case .constructive: titleColor = constructiveColor case .destructive: @@ -1550,6 +1554,8 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode { strongSelf.borderImageNode.isHidden = !item.pinned || item.style == .list + let canUpdateAvatarVisibility = !strongSelf.isExtracted && !strongSelf.animatingExtraction + if let videoNode = videoNode { let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut) if !strongSelf.isExtracted && !strongSelf.animatingExtraction { @@ -1575,7 +1581,9 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode { } else { if item.pinned { videoNode.alpha = 0.0 - strongSelf.avatarNode.alpha = 1.0 + if canUpdateAvatarVisibility { + strongSelf.avatarNode.alpha = 1.0 + } } else if strongSelf.videoReady { videoNode.alpha = 1.0 strongSelf.avatarNode.alpha = 0.0 @@ -1584,7 +1592,9 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode { } else { if item.pinned { videoNode.alpha = 0.0 - strongSelf.avatarNode.alpha = 1.0 + if canUpdateAvatarVisibility { + strongSelf.avatarNode.alpha = 1.0 + } } else if strongSelf.videoReady { videoNode.alpha = 1.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.bounds = CGRect(origin: CGPoint(), size: videoSize) } - + if videoNodeUpdated { strongSelf.videoReadyDelayed = false strongSelf.videoReadyDisposable.set((videoNode.ready @@ -1612,13 +1622,18 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode { strongSelf.videoReadyDelayed = true } strongSelf.videoReady = ready - if let videoNode = strongSelf.videoNode, ready && (strongSelf.item?.transparent != true) { + if let videoNode = strongSelf.videoNode, ready && !item.transparent { if strongSelf.videoReadyDelayed { Queue.mainQueue().after(0.15) { - switch item.style { + guard let currentItem = strongSelf.item else { + return + } + switch currentItem.style { case .list: - if item.pinned { - strongSelf.avatarNode.alpha = 1.0 + if currentItem.pinned { + if canUpdateAvatarVisibility { + strongSelf.avatarNode.alpha = 1.0 + } videoNode.alpha = 0.0 } else { strongSelf.avatarNode.alpha = 0.0 @@ -1627,8 +1642,10 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode { videoNode.alpha = 1.0 } case .tile: - if item.pinned { - strongSelf.avatarNode.alpha = 1.0 + if currentItem.pinned { + if canUpdateAvatarVisibility { + strongSelf.avatarNode.alpha = 1.0 + } videoNode.alpha = 0.0 } else { strongSelf.avatarNode.alpha = 0.0 @@ -1640,7 +1657,9 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode { } } else { if item.pinned { - strongSelf.avatarNode.alpha = 1.0 + if canUpdateAvatarVisibility { + strongSelf.avatarNode.alpha = 1.0 + } videoNode.alpha = 0.0 } else { strongSelf.avatarNode.alpha = 0.0 diff --git a/submodules/TelegramUI/Sources/ChatMediaInputNode.swift b/submodules/TelegramUI/Sources/ChatMediaInputNode.swift index 3d00bea8c8..513ec1ee99 100644 --- a/submodules/TelegramUI/Sources/ChatMediaInputNode.swift +++ b/submodules/TelegramUI/Sources/ChatMediaInputNode.swift @@ -1025,6 +1025,33 @@ final class ChatMediaInputNode: ChatInputNode { 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 { 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) @@ -1098,13 +1125,34 @@ final class ChatMediaInputNode: ChatInputNode { |> deliverOnMainQueue |> map { isStarred -> (ASDisplayNode, PeekControllerContent)? in if let strongSelf = self { - var menuItems: [ContextMenuItem] = [] - menuItems = [ - .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 - f(.default) - -// let _ = strongSelf.sendSticker?(.standalone(media: item.file), node, rect) - })), + var menuItems: [ContextMenuItem] = [] + 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 { + 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 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() } } - })), - .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) }, action: { _, f in f(.default) @@ -1143,8 +1192,8 @@ final class ChatMediaInputNode: ChatInputNode { } } } - })) - ] + }))) + return (itemNode, StickerPreviewPeekContent(account: strongSelf.context.account, item: item, menu: menuItems)) } else { @@ -1185,12 +1234,33 @@ final class ChatMediaInputNode: ChatInputNode { |> map { isStarred -> (ASDisplayNode, PeekControllerContent)? in if let strongSelf = self { var menuItems: [ContextMenuItem] = [] - menuItems = [ - .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 - f(.default) - - // let _ = strongSelf.sendSticker?(.standalone(media: item.file), node, rect) - })), + 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 { + 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 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() } } - })), + })) + ) + 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) }, action: { _, f in @@ -1230,7 +1302,7 @@ final class ChatMediaInputNode: ChatInputNode { } } })) - ] + ) return (itemNode, StickerPreviewPeekContent(account: strongSelf.context.account, item: .pack(item), menu: menuItems)) } else { return nil diff --git a/submodules/TelegramUI/Sources/ChatScheduleTimeControllerNode.swift b/submodules/TelegramUI/Sources/ChatScheduleTimeControllerNode.swift index 0233a7eafc..fee537ce93 100644 --- a/submodules/TelegramUI/Sources/ChatScheduleTimeControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatScheduleTimeControllerNode.swift @@ -314,10 +314,16 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, UIScrollViewDel self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4) let offset = self.bounds.size.height - self.contentBackgroundNode.frame.minY - 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) { diff --git a/submodules/TelegramUI/Sources/ChatTimerScreen.swift b/submodules/TelegramUI/Sources/ChatTimerScreen.swift index 2ccb917a2b..a529d71497 100644 --- a/submodules/TelegramUI/Sources/ChatTimerScreen.swift +++ b/submodules/TelegramUI/Sources/ChatTimerScreen.swift @@ -401,10 +401,16 @@ class ChatTimerScreenNode: ViewControllerTracingNode, UIScrollViewDelegate, UIPi self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4) let offset = self.bounds.size.height - self.contentBackgroundNode.frame.minY - 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) {