diff --git a/submodules/AudioBlob/Sources/BlobView.swift b/submodules/AudioBlob/Sources/BlobView.swift index 27d6080366..dfe97bfe62 100644 --- a/submodules/AudioBlob/Sources/BlobView.swift +++ b/submodules/AudioBlob/Sources/BlobView.swift @@ -114,6 +114,9 @@ public final class VoiceBlobView: UIView, TGModernConversationInputMicButtonDeco if !immediately { mediumBlob.layer.animateScale(from: 0.75, to: 1, duration: 0.35, removeOnCompletion: false) bigBlob.layer.animateScale(from: 0.75, to: 1, duration: 0.35, removeOnCompletion: false) + } else { + mediumBlob.layer.removeAllAnimations() + bigBlob.layer.removeAllAnimations() } updateBlobsState() diff --git a/submodules/AvatarNode/Sources/AvatarNode.swift b/submodules/AvatarNode/Sources/AvatarNode.swift index 670d1ad420..22b15b4753 100644 --- a/submodules/AvatarNode/Sources/AvatarNode.swift +++ b/submodules/AvatarNode/Sources/AvatarNode.swift @@ -623,11 +623,13 @@ public final class AvatarNode: ASDisplayNode { } } -public func drawPeerAvatarLetters(context: CGContext, size: CGSize, font: UIFont, letters: [String], peerId: PeerId) { - context.beginPath() - context.addEllipse(in: CGRect(x: 0.0, y: 0.0, width: size.width, height: - size.height)) - context.clip() +public func drawPeerAvatarLetters(context: CGContext, size: CGSize, round: Bool = true, font: UIFont, letters: [String], peerId: PeerId) { + if round { + context.beginPath() + context.addEllipse(in: CGRect(x: 0.0, y: 0.0, width: size.width, height: + size.height)) + context.clip() + } let colorIndex: Int if peerId.namespace == .max { diff --git a/submodules/AvatarNode/Sources/PeerAvatar.swift b/submodules/AvatarNode/Sources/PeerAvatar.swift index 4012e26d00..c37effc85b 100644 --- a/submodules/AvatarNode/Sources/PeerAvatar.swift +++ b/submodules/AvatarNode/Sources/PeerAvatar.swift @@ -87,9 +87,9 @@ public func peerAvatarImageData(account: Account, peerReference: PeerReference?, } } -public func peerAvatarCompleteImage(account: Account, peer: Peer, size: CGSize, font: UIFont = avatarPlaceholderFont(size: 13.0), fullSize: Bool = false) -> Signal { +public func peerAvatarCompleteImage(account: Account, peer: Peer, size: CGSize, round: Bool = true, font: UIFont = avatarPlaceholderFont(size: 13.0), drawLetters: Bool = true, fullSize: Bool = false) -> Signal { let iconSignal: Signal - if let signal = peerAvatarImage(account: account, peerReference: PeerReference(peer), authorOfMessage: nil, representation: peer.profileImageRepresentations.first, displayDimensions: size, inset: 0.0, emptyColor: nil, synchronousLoad: fullSize) { + if let signal = peerAvatarImage(account: account, peerReference: PeerReference(peer), authorOfMessage: nil, representation: peer.profileImageRepresentations.first, displayDimensions: size, round: round, inset: 0.0, emptyColor: nil, synchronousLoad: fullSize) { if fullSize, let fullSizeSignal = peerAvatarImage(account: account, peerReference: PeerReference(peer), authorOfMessage: nil, representation: peer.profileImageRepresentations.last, displayDimensions: size, emptyColor: nil, synchronousLoad: true) { iconSignal = combineLatest(.single(nil) |> then(signal), .single(nil) |> then(fullSizeSignal)) |> mapToSignal { thumbnailImage, fullSizeImage -> Signal in @@ -113,10 +113,13 @@ public func peerAvatarCompleteImage(account: Account, peer: Peer, size: CGSize, if displayLetters.count == 2 && displayLetters[0].isSingleEmoji && displayLetters[1].isSingleEmoji { displayLetters = [displayLetters[0]] } + if !drawLetters { + displayLetters = [] + } iconSignal = Signal { subscriber in let image = generateImage(size, rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) - drawPeerAvatarLetters(context: context, size: CGSize(width: size.width, height: size.height), font: font, letters: displayLetters, peerId: peerId) + drawPeerAvatarLetters(context: context, size: CGSize(width: size.width, height: size.height), round: round, font: font, letters: displayLetters, peerId: peerId) })?.withRenderingMode(.alwaysOriginal) subscriber.putNext(image) diff --git a/submodules/TelegramCallsUI/Sources/CallControllerNode.swift b/submodules/TelegramCallsUI/Sources/CallControllerNode.swift index 6c5651cc17..ddf2440048 100644 --- a/submodules/TelegramCallsUI/Sources/CallControllerNode.swift +++ b/submodules/TelegramCallsUI/Sources/CallControllerNode.swift @@ -132,6 +132,14 @@ private final class CallVideoNode: ASDisplayNode { self.isReadyTimer?.invalidate() } + override func didLoad() { + super.didLoad() + + if #available(iOS 13.0, *) { + self.layer.cornerCurve = .continuous + } + } + func animateRadialMask(from fromRect: CGRect, to toRect: CGRect) { let maskLayer = CAShapeLayer() maskLayer.frame = fromRect diff --git a/submodules/TelegramCallsUI/Sources/CallControllerToastNode.swift b/submodules/TelegramCallsUI/Sources/CallControllerToastNode.swift index b8a472ddcd..ddcb9aa279 100644 --- a/submodules/TelegramCallsUI/Sources/CallControllerToastNode.swift +++ b/submodules/TelegramCallsUI/Sources/CallControllerToastNode.swift @@ -222,9 +222,6 @@ private class CallControllerToastItemNode: ASDisplayNode { self.clipNode = ASDisplayNode() self.clipNode.clipsToBounds = true self.clipNode.layer.cornerRadius = 14.0 - if #available(iOS 13.0, *) { - self.clipNode.layer.cornerCurve = .continuous - } self.effectView = UIVisualEffectView() self.effectView.effect = UIBlurEffect(style: .light) @@ -248,6 +245,14 @@ private class CallControllerToastItemNode: ASDisplayNode { self.clipNode.addSubnode(self.textNode) } + override func didLoad() { + super.didLoad() + + if #available(iOS 13.0, *) { + self.clipNode.layer.cornerCurve = .continuous + } + } + func update(width: CGFloat, content: Content, transition: ContainedViewLayoutTransition) -> CGFloat { let inset: CGFloat = 30.0 let isNarrowScreen = width <= 320.0 diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift index f7343e3bfc..2797799d0c 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift @@ -723,6 +723,7 @@ public final class VoiceChatController: ViewController { private var animatingAppearance = false private var animatingButtonsSwap = false private var panGestureArguments: (topInset: CGFloat, offset: CGFloat)? + private var isPanning = false private var peer: Peer? private var currentTitle: String = "" @@ -826,11 +827,7 @@ public final class VoiceChatController: ViewController { } } } - - private var effectiveDisplayMode: DisplayMode { - return self.displayMode - } - + private var isExpanded: Bool { switch self.displayMode { case .modal(true, _), .fullscreen: @@ -1060,7 +1057,7 @@ public final class VoiceChatController: ViewController { if peerId != strongSelf.currentDominantSpeaker?.0 { strongSelf.currentDominantSpeaker = (peerId, CACurrentMediaTime()) } - strongSelf.updateMainVideo(waitForFullSize: false, updateMembers: true, force: true) + strongSelf.updateMainVideo(waitForFullSize: true, updateMembers: true, force: true) } } }, openInvite: { [weak self] in @@ -1628,6 +1625,9 @@ public final class VoiceChatController: ViewController { if ignore { return nil } + if !strongSelf.readyVideoNodes.contains(endpointId) { + return nil + } for (listEndpointId, videoNode) in strongSelf.videoNodes { if listEndpointId == endpointId { return videoNode @@ -2005,18 +2005,6 @@ public final class VoiceChatController: ViewController { self.mainStageNode.switchTo = { [weak self] peerId in if let strongSelf = self, let interaction = strongSelf.itemInteraction { interaction.switchToPeer(peerId, nil, false) - -// let position: ListViewScrollPosition -// var index: Int = 0 -// if index > strongSelf.currentFullscreenEntries.count - 3 { -// index = strongSelf.currentFullscreenEntries.count - 1 -// position = .bottom(0.0) -// } else { -// position = .center(.bottom) -// } -// strongSelf.fullscreenListNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: index, position: position, animated: true, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in -// completion() -// }) } } @@ -3111,7 +3099,7 @@ public final class VoiceChatController: ViewController { } private var isFullscreen: Bool { - switch self.effectiveDisplayMode { + switch self.displayMode { case .fullscreen(_), .modal(_, true): return true default: @@ -3137,7 +3125,7 @@ public final class VoiceChatController: ViewController { let layoutTopInset: CGFloat = max(layout.statusBarHeight ?? 0.0, layout.safeInsets.top) let listTopInset = isLandscape ? topPanelHeight : layoutTopInset + topPanelHeight - let bottomPanelHeight = isLandscape ? layout.intrinsicInsets.bottom : self.effectiveBottomAreaHeight + layout.intrinsicInsets.bottom + let bottomPanelHeight = isLandscape ? layout.intrinsicInsets.bottom : bottomAreaHeight + layout.intrinsicInsets.bottom var size = layout.size if case .regular = layout.metrics.widthClass { @@ -3218,12 +3206,10 @@ public final class VoiceChatController: ViewController { } var bottomInset: CGFloat = 0.0 - var bottomEdgeInset: CGFloat = 0.0 - if case let .fullscreen(controlsHidden) = self.effectiveDisplayMode { + if case let .fullscreen(controlsHidden) = self.displayMode { if !controlsHidden { bottomInset = 80.0 } - bottomEdgeInset = 154.0 } transition.updateAlpha(node: self.bottomGradientNode, alpha: self.isLandscape ? 0.0 : 1.0) @@ -3277,14 +3263,13 @@ public final class VoiceChatController: ViewController { self.topPanelBackgroundNode.frame = CGRect(x: 0.0, y: topPanelHeight - 24.0, width: size.width, height: min(topPanelFrame.height, 24.0)) let listMaxY = listTopInset + listSize.height - let bottomOffset: CGFloat = min(0.0, bottomEdge - listMaxY) + layout.size.height - bottomPanelHeight - let bottomDelta = bottomGradientHeight - bottomEdgeInset - - let bottomCornersFrame = CGRect(origin: CGPoint(x: sideInset + floorToScreenPixels((size.width - contentWidth) / 2.0), y: -50.0 + bottomOffset + bottomDelta), size: CGSize(width: contentWidth - sideInset * 2.0, height: 50.0)) + let bottomOffset = min(0.0, bottomEdge - listMaxY) + layout.size.height - bottomPanelHeight + + let bottomCornersFrame = CGRect(origin: CGPoint(x: sideInset + floorToScreenPixels((size.width - contentWidth) / 2.0), y: -50.0 + bottomOffset + bottomGradientHeight), size: CGSize(width: contentWidth - sideInset * 2.0, height: 50.0)) let previousBottomCornersFrame = self.bottomCornersNode.frame if !bottomCornersFrame.equalTo(previousBottomCornersFrame) { self.bottomCornersNode.frame = bottomCornersFrame - self.bottomPanelBackgroundNode.frame = CGRect(x: 0.0, y: bottomOffset + bottomDelta, width: size.width, height: 2000.0) + self.bottomPanelBackgroundNode.frame = CGRect(x: 0.0, y: bottomOffset + bottomGradientHeight, width: size.width, height: 2000.0) let positionDelta = CGPoint(x: 0.0, y: previousBottomCornersFrame.minY - bottomCornersFrame.minY) transition.animatePositionAdditive(node: self.bottomCornersNode, offset: positionDelta) @@ -3299,8 +3284,7 @@ public final class VoiceChatController: ViewController { } let isFullscreen = self.isFullscreen - let isLandscape = self.isLandscape - let effectiveDisplayMode = self.effectiveDisplayMode + let effectiveDisplayMode = self.displayMode self.controller?.statusBar.updateStatusBarStyle(isFullscreen ? .White : .Ignore, animated: true) @@ -3324,11 +3308,7 @@ public final class VoiceChatController: ViewController { let backgroundColor: UIColor if case .fullscreen = effectiveDisplayMode { - if isLandscape { - backgroundColor = isFullscreen ? panelBackgroundColor : secondaryPanelBackgroundColor - } else { - backgroundColor = fullscreenBackgroundColor - } + backgroundColor = isFullscreen ? panelBackgroundColor : secondaryPanelBackgroundColor } else if self.isScheduling || self.callState?.scheduleTimestamp != nil { backgroundColor = panelBackgroundColor } else { @@ -3578,7 +3558,7 @@ public final class VoiceChatController: ViewController { if previousIsLandscape != isLandscape { - if case .modal = self.effectiveDisplayMode { + if case .modal = self.displayMode { self.displayMode = .modal(isExpanded: true, isFilled: true) } self.updateDecorationsColors() @@ -3586,7 +3566,7 @@ public final class VoiceChatController: ViewController { self.updateMembers() } - let effectiveDisplayMode = self.effectiveDisplayMode + let effectiveDisplayMode = self.displayMode transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - contentWidth) / 2.0), y: 10.0), size: CGSize(width: contentWidth, height: 44.0))) self.updateTitle(transition: transition) @@ -4440,6 +4420,22 @@ public final class VoiceChatController: ViewController { strongSelf.wideVideoNodes.insert(channel.endpointId) } strongSelf.updateMembers() + + if let interaction = strongSelf.itemInteraction { + loop: for i in 0 ..< strongSelf.currentFullscreenEntries.count { + let entry = strongSelf.currentFullscreenEntries[i] + switch entry { + case let .peer(peerEntry, _): + if peerEntry.effectiveVideoEndpointId == channel.endpointId { + let presentationData = strongSelf.presentationData.withUpdated(theme: strongSelf.darkTheme) + strongSelf.fullscreenListNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [ListViewUpdateItem(index: i, previousIndex: i, item: entry.fullscreenItem(context: strongSelf.context, presentationData: presentationData, interaction: interaction), directionHint: nil)], options: [.Synchronous], updateOpaqueState: nil) + break loop + } + default: + break + } + } + } } } }), forKey: channel.endpointId) @@ -4595,7 +4591,9 @@ public final class VoiceChatController: ViewController { self.controller?.dismissAllTooltips() - if case .fullscreen = self.effectiveDisplayMode { + if case .fullscreen = self.displayMode { + self.isPanning = true + self.mainStageBackgroundNode.alpha = 0.0 self.mainStageBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.4) self.mainStageNode.setControlsHidden(true, animated: true) @@ -4613,7 +4611,7 @@ public final class VoiceChatController: ViewController { return } - switch self.effectiveDisplayMode { + switch self.displayMode { case let .modal(isExpanded, previousIsFilled): var topInset: CGFloat = 0.0 if let (currentTopInset, currentPanOffset) = self.panGestureArguments { @@ -4699,13 +4697,14 @@ public final class VoiceChatController: ViewController { topInset = self.listNode.frame.height } - if case .fullscreen = self.effectiveDisplayMode { + if case .fullscreen = self.displayMode { self.panGestureArguments = nil if abs(translation.y) > 100.0 || abs(velocity.y) > 300.0 { self.currentForcedSpeaker = nil self.updateDisplayMode(.modal(isExpanded: true, isFilled: true), fromPan: true) self.effectiveSpeaker = nil } else { + self.isPanning = false self.mainStageBackgroundNode.alpha = 1.0 self.mainStageBackgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, completion: { [weak self] _ in self?.attachFullscreenVideos() @@ -4725,7 +4724,7 @@ public final class VoiceChatController: ViewController { } }) } - } else if case .modal(true, _) = self.effectiveDisplayMode { + } else if case .modal(true, _) = self.displayMode { self.panGestureArguments = nil if velocity.y > 300.0 || offset > topInset / 2.0 { self.displayMode = .modal(isExpanded: false, isFilled: false) @@ -4758,7 +4757,7 @@ public final class VoiceChatController: ViewController { if self.isScheduling { self.dismissScheduled() } else { - if case .fullscreen = self.effectiveDisplayMode { + if case .fullscreen = self.displayMode { } else { self.controller?.dismiss(closing: false, manual: true) dismissing = true @@ -4771,7 +4770,7 @@ public final class VoiceChatController: ViewController { } } - if case .modal = self.effectiveDisplayMode { + if case .modal = self.displayMode { self.displayMode = .modal(isExpanded: true, isFilled: true) } self.updateDecorationsColors() @@ -4818,6 +4817,28 @@ public final class VoiceChatController: ViewController { self.updateDecorationsLayout(transition: .animated(duration: 0.3, curve: .easeInOut), completion: { self.animatingExpansion = false }) + + if case .fullscreen = self.displayMode { + self.isPanning = false + self.mainStageBackgroundNode.alpha = 1.0 + self.mainStageBackgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, completion: { [weak self] _ in + self?.attachFullscreenVideos() + }) + self.mainStageNode.setControlsHidden(false, animated: true) + + self.fullscreenListNode.alpha = 1.0 + self.fullscreenListNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, delay: 0.15) + + var bounds = self.mainStageContainerNode.bounds + let previousBounds = bounds + bounds.origin.y = 0.0 + self.mainStageContainerNode.bounds = bounds + self.mainStageContainerNode.layer.animateBounds(from: previousBounds, to: self.mainStageContainerNode.bounds, duration: 0.2, timingFunction: kCAMediaTimingFunctionSpring, completion: { [weak self] _ in + if let strongSelf = self { + strongSelf.contentContainer.insertSubnode(strongSelf.mainStageContainerNode, belowSubnode: strongSelf.transitionContainerNode) + } + }) + } default: break } @@ -5277,13 +5298,17 @@ public final class VoiceChatController: ViewController { } self.fullscreenListNode.forEachItemNode { itemNode in - if let itemNode = itemNode as? VoiceChatFullscreenParticipantItemNode, let item = itemNode.item, let otherItemNode = verticalItemNodes[item.peer.id] { - itemNode.animateTransitionIn(from: otherItemNode, containerNode: self.transitionContainerNode, transition: transition, animate: item.peer.id != effectiveSpeakerPeerId) + if let itemNode = itemNode as? VoiceChatFullscreenParticipantItemNode, let item = itemNode.item { + itemNode.animateTransitionIn(from: verticalItemNodes[item.peer.id], containerNode: self.transitionContainerNode, transition: transition, animate: item.peer.id != effectiveSpeakerPeerId) } } + if self.isLandscape { + self.transitionMaskTopFillLayer.opacity = 1.0 + } self.transitionMaskBottomFillLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3, removeOnCompletion: false, completion: { [weak self] _ in Queue.mainQueue().after(0.2) { + self?.transitionMaskTopFillLayer.opacity = 0.0 self?.transitionMaskBottomFillLayer.removeAllAnimations() } }) @@ -5378,6 +5403,8 @@ public final class VoiceChatController: ViewController { strongSelf.mainStageContainerNode.bounds = bounds strongSelf.contentContainer.insertSubnode(strongSelf.mainStageContainerNode, belowSubnode: strongSelf.transitionContainerNode) + + strongSelf.isPanning = false }) self.transitionMaskTopFillLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15) diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatFullscreenParticipantItem.swift b/submodules/TelegramCallsUI/Sources/VoiceChatFullscreenParticipantItem.swift index 1c65c9d978..39fc53d115 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatFullscreenParticipantItem.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatFullscreenParticipantItem.swift @@ -188,9 +188,6 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode { let videoContainerNode: ASDisplayNode private let videoFadeNode: ASDisplayNode var videoNode: GroupVideoNode? - private let videoReadyDisposable = MetaDisposable() - private var videoReadyDelayed = false - private var videoReady = false private var profileNode: VoiceChatPeerProfileNode? @@ -289,7 +286,6 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode { } deinit { - self.videoReadyDisposable.dispose() self.audioLevelDisposable.dispose() self.raiseHandTimer?.invalidate() self.silenceTimer?.invalidate() @@ -300,7 +296,7 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode { self.layoutParams?.0.action?(self.contextSourceNode) } - func animateTransitionIn(from sourceNode: ASDisplayNode, containerNode: ASDisplayNode, transition: ContainedViewLayoutTransition, animate: Bool = true) { + func animateTransitionIn(from sourceNode: ASDisplayNode?, containerNode: ASDisplayNode, transition: ContainedViewLayoutTransition, animate: Bool = true) { guard let item = self.item else { return } @@ -372,7 +368,7 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode { self.contentWrapperNode.layer.animateScale(from: 0.001, to: 1.0, duration: duration, timingFunction: timingFunction) self.contentWrapperNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration, timingFunction: timingFunction) } else if !initialAnimate { - if case .animated = transition { + if transition.isAnimated { self.contextSourceNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration, timingFunction: timingFunction) self.contextSourceNode.layer.animateScale(from: 0.0, to: 1.0, duration: duration, timingFunction: timingFunction) } @@ -380,7 +376,7 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode { } else if let sourceNode = sourceNode as? VoiceChatParticipantItemNode, let _ = sourceNode.item { var startContainerPosition = sourceNode.avatarNode.view.convert(sourceNode.avatarNode.bounds, to: containerNode.view).center var animate = true - if startContainerPosition.y > containerNode.frame.height { + if startContainerPosition.y < -tileHeight || startContainerPosition.y > containerNode.frame.height + tileHeight { animate = false } startContainerPosition = startContainerPosition.offsetBy(dx: 0.0, dy: 9.0) @@ -416,6 +412,11 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode { self.contentWrapperNode.layer.animateScale(from: 0.001, to: 1.0, duration: duration, timingFunction: timingFunction) self.contentWrapperNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration, timingFunction: timingFunction) } + } else { + if transition.isAnimated { + self.contextSourceNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration, timingFunction: timingFunction) + self.contextSourceNode.layer.animateScale(from: 0.0, to: 1.0, duration: duration, timingFunction: timingFunction) + } } } private func updateIsExtracted(_ isExtracted: Bool, transition: ContainedViewLayoutTransition) { @@ -539,10 +540,30 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode { strongSelf.layoutParams = (item, params, first, last) strongSelf.wavesColor = wavesColor + let videoContainerScale = tileSize.width / videoSize.width + let videoNode = item.getVideo() - if let current = strongSelf.videoNode, current !== videoNode { - current.removeFromSupernode() - strongSelf.videoReadyDisposable.set(nil) + if let currentVideoNode = strongSelf.videoNode, currentVideoNode !== videoNode { + if videoNode == nil { + let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut) + if strongSelf.avatarNode.alpha.isZero { + strongSelf.animatingSelection = true + strongSelf.videoContainerNode.layer.animateScale(from: videoContainerScale, to: 0.001, duration: 0.2) + strongSelf.avatarNode.layer.animateScale(from: 0.0, to: 1.0, duration: 0.2, completion: { [weak self] _ in + self?.animatingSelection = false + }) + strongSelf.videoContainerNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -9.0), duration: 0.2, additive: true) + strongSelf.audioLevelView?.layer.animateScale(from: 0.0, to: 1.0, duration: 0.2) + } + transition.updateAlpha(node: currentVideoNode, alpha: 0.0) + transition.updateAlpha(node: strongSelf.videoFadeNode, alpha: 0.0) + transition.updateAlpha(node: strongSelf.avatarNode, alpha: 1.0) + if let audioLevelView = strongSelf.audioLevelView { + transition.updateAlpha(layer: audioLevelView.layer, alpha: 1.0) + } + } else { + currentVideoNode.removeFromSupernode() + } } let videoNodeUpdated = strongSelf.videoNode !== videoNode @@ -667,7 +688,7 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode { strongSelf.audioLevelView = audioLevelView strongSelf.offsetContainerNode.view.insertSubview(audioLevelView, at: 0) - if let item = strongSelf.item, strongSelf.videoNode != nil || item.active { + if let item = strongSelf.item, strongSelf.videoNode != nil && !item.active { audioLevelView.alpha = 0.0 } } @@ -816,8 +837,6 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode { node.layer.animateScale(from: 0.001, to: 1.0, duration: 0.2) } - let videoContainerScale = tileSize.width / videoSize.width - if !strongSelf.isExtracted && !strongSelf.animatingExtraction { strongSelf.videoFadeNode.frame = CGRect(x: 0.0, y: videoSize.height - fadeHeight, width: videoSize.width, height: fadeHeight) strongSelf.videoContainerNode.bounds = CGRect(origin: CGPoint(), size: videoSize) @@ -842,8 +861,11 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode { if currentItem != nil { if item.active { if strongSelf.avatarNode.alpha.isZero { + strongSelf.animatingSelection = true strongSelf.videoContainerNode.layer.animateScale(from: videoContainerScale, to: 0.001, duration: 0.2) - strongSelf.avatarNode.layer.animateScale(from: 0.0, to: 1.0, duration: 0.2) + strongSelf.avatarNode.layer.animateScale(from: 0.0, to: 1.0, duration: 0.2, completion: { [weak self] _ in + self?.animatingSelection = false + }) strongSelf.videoContainerNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -9.0), duration: 0.2, additive: true) strongSelf.audioLevelView?.layer.animateScale(from: 0.0, to: 1.0, duration: 0.2) } @@ -873,7 +895,7 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode { if canUpdateAvatarVisibility { strongSelf.avatarNode.alpha = 1.0 } - } else if strongSelf.videoReady { + } else { videoNode.alpha = 1.0 strongSelf.avatarNode.alpha = 0.0 } @@ -890,48 +912,29 @@ class VoiceChatFullscreenParticipantItemNode: 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 - |> deliverOnMainQueue).start(next: { [weak self] ready in - if let strongSelf = self { - if !ready { - strongSelf.videoReadyDelayed = true - } - strongSelf.videoReady = ready - if let videoNode = strongSelf.videoNode, ready { - if strongSelf.videoReadyDelayed { - Queue.mainQueue().after(0.15) { - guard let currentItem = strongSelf.item else { - return - } - if currentItem.active { - if canUpdateAvatarVisibility { - strongSelf.avatarNode.alpha = 1.0 - } - videoNode.alpha = 0.0 - } else { - strongSelf.avatarNode.alpha = 0.0 - strongSelf.avatarNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2) - videoNode.layer.animateScale(from: 0.01, to: 1.0, duration: 0.2) - videoNode.alpha = 1.0 - } - } - } else { - if item.active { - if canUpdateAvatarVisibility { - strongSelf.avatarNode.alpha = 1.0 - } - videoNode.alpha = 0.0 - } else { - strongSelf.avatarNode.alpha = 0.0 - videoNode.alpha = 1.0 - } - } - } + + if let _ = currentItem, videoNodeUpdated { + if item.active { + if canUpdateAvatarVisibility { + strongSelf.avatarNode.alpha = 1.0 } - })) + videoNode.alpha = 0.0 + } else { + strongSelf.avatarNode.alpha = 0.0 + strongSelf.avatarNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2) + videoNode.layer.animateScale(from: 0.01, to: 1.0, duration: 0.2) + videoNode.alpha = 1.0 + } + } else { + if item.active { + if canUpdateAvatarVisibility { + strongSelf.avatarNode.alpha = 1.0 + } + videoNode.alpha = 0.0 + } else { + strongSelf.avatarNode.alpha = 0.0 + videoNode.alpha = 1.0 + } } } else if canUpdateAvatarVisibility { strongSelf.avatarNode.alpha = 1.0 diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatMainStageNode.swift b/submodules/TelegramCallsUI/Sources/VoiceChatMainStageNode.swift index 2ebd27d0c1..39f45c8eb9 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatMainStageNode.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatMainStageNode.swift @@ -48,12 +48,12 @@ final class VoiceChatMainStageNode: ASDisplayNode { private let audioLevelDisposable = MetaDisposable() private let speakingPeerDisposable = MetaDisposable() private let speakingAudioLevelDisposable = MetaDisposable() - private var avatarNode: ASImageNode + private var backdropAvatarNode: ImageNode + private var backdropEffectView: UIVisualEffectView? + private var avatarNode: ImageNode private let titleNode: ImmediateTextNode private let microphoneNode: VoiceChatMicrophoneNode - - private let avatarDisposable = MetaDisposable() - + private let speakingContainerNode: ASDisplayNode private var speakingEffectView: UIVisualEffectView? private let speakingAvatarNode: AvatarNode @@ -129,7 +129,12 @@ final class VoiceChatMainStageNode: ASDisplayNode { self.pinButtonTitleNode.attributedText = NSAttributedString(string: "Unpin", font: Font.regular(17.0), textColor: .white) self.pinButtonNode = HighlightableButtonNode() - self.avatarNode = ASImageNode() + self.backdropAvatarNode = ImageNode() + self.backdropAvatarNode.contentMode = .scaleAspectFill + self.backdropAvatarNode.displaysAsynchronously = false + self.backdropAvatarNode.isHidden = true + + self.avatarNode = ImageNode() self.avatarNode.displaysAsynchronously = false self.avatarNode.isHidden = true @@ -152,14 +157,12 @@ final class VoiceChatMainStageNode: ASDisplayNode { self.clipsToBounds = true self.cornerRadius = backgroundCornerRadius - if #available(iOS 13.0, *) { - self.layer.cornerCurve = .continuous - } self.addSubnode(self.backgroundNode) self.addSubnode(self.topFadeNode) self.addSubnode(self.bottomFadeNode) self.addSubnode(self.bottomFillNode) + self.addSubnode(self.backdropAvatarNode) self.addSubnode(self.avatarNode) self.addSubnode(self.titleNode) self.addSubnode(self.microphoneNode) @@ -215,7 +218,6 @@ final class VoiceChatMainStageNode: ASDisplayNode { } deinit { - self.avatarDisposable.dispose() self.videoReadyDisposable.dispose() self.audioLevelDisposable.dispose() self.speakingPeerDisposable.dispose() @@ -226,10 +228,25 @@ final class VoiceChatMainStageNode: ASDisplayNode { override func didLoad() { super.didLoad() + if #available(iOS 13.0, *) { + self.layer.cornerCurve = .continuous + } + let speakingEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .dark)) self.speakingContainerNode.view.insertSubview(speakingEffectView, at: 0) self.speakingEffectView = speakingEffectView + let effect: UIVisualEffect + if #available(iOS 13.0, *) { + effect = UIBlurEffect(style: .systemMaterialDark) + } else { + effect = UIBlurEffect(style: .dark) + } + let backdropEffectView = UIVisualEffectView(effect: effect) + backdropEffectView.isHidden = true + self.view.insertSubview(backdropEffectView, aboveSubview: self.backdropAvatarNode.view) + self.backdropEffectView = backdropEffectView + self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tap))) self.speakingContainerNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.speakingTap))) @@ -476,12 +493,8 @@ final class VoiceChatMainStageNode: ASDisplayNode { if !arePeersEqual(previousPeerEntry?.peer, peerEntry.peer) { let peer = peerEntry.peer let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } - self.avatarDisposable.set((peerAvatarCompleteImage(account: self.context.account, peer: peer, size: CGSize(width: 180.0, height: 180.0), font: avatarPlaceholderFont(size: 78.0), fullSize: true) - |> deliverOnMainQueue).start(next: { [weak self] image in - if let strongSelf = self { - strongSelf.avatarNode.image = image - } - })) + self.backdropAvatarNode.setSignal(peerAvatarCompleteImage(account: self.context.account, peer: peer, size: CGSize(width: 180.0, height: 180.0), round: false, font: avatarPlaceholderFont(size: 78.0), drawLetters: false)) + self.avatarNode.setSignal(peerAvatarCompleteImage(account: self.context.account, peer: peer, size: CGSize(width: 180.0, height: 180.0), font: avatarPlaceholderFont(size: 78.0), fullSize: true)) self.titleNode.attributedText = NSAttributedString(string: peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), font: Font.semibold(15.0), textColor: .white) if let (size, sideInset, bottomInset, isLandscape) = self.validLayout { self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, isLandscape: isLandscape, transition: .immediate) @@ -491,6 +504,7 @@ final class VoiceChatMainStageNode: ASDisplayNode { self.pinButtonTitleNode.isHidden = !pinned self.pinButtonIconNode.image = !pinned ? generateTintedImage(image: UIImage(bundleImageName: "Call/Pin"), color: .white) : generateTintedImage(image: UIImage(bundleImageName: "Call/Unpin"), color: .white) + var firstTime = true var wavesColor = UIColor(rgb: 0x34c759) if let getAudioLevel = self.getAudioLevel, previousPeerEntry?.peer.id != peerEntry.peer.id { if let audioLevelView = self.audioLevelView { @@ -528,7 +542,7 @@ final class VoiceChatMainStageNode: ASDisplayNode { let avatarScale: CGFloat if value > 0.02 { - audioLevelView.startAnimating(immediately: true) + audioLevelView.startAnimating(immediately: firstTime) avatarScale = 1.03 + level * 0.13 audioLevelView.setColor(wavesColor, animated: true) @@ -551,6 +565,7 @@ final class VoiceChatMainStageNode: ASDisplayNode { let transition: ContainedViewLayoutTransition = .animated(duration: 0.15, curve: .easeInOut) transition.updateTransformScale(node: strongSelf.avatarNode, scale: avatarScale, beginWithCurrentState: true) } + firstTime = false })) } @@ -579,6 +594,13 @@ final class VoiceChatMainStageNode: ASDisplayNode { self.microphoneNode.update(state: VoiceChatMicrophoneNode.State(muted: muted, filled: true, color: .white), animated: true) } + private func setAvatarHidden(_ hidden: Bool) { + self.backdropAvatarNode.isHidden = hidden + self.backdropEffectView?.isHidden = hidden + self.avatarNode.isHidden = hidden + self.audioLevelView?.isHidden = hidden + } + func update(peer: (peer: PeerId, endpointId: String?)?, waitForFullSize: Bool, completion: (() -> Void)? = nil) { let previousPeer = self.currentPeer if previousPeer?.0 == peer?.0 && previousPeer?.1 == peer?.1 { @@ -592,8 +614,7 @@ final class VoiceChatMainStageNode: ASDisplayNode { if let (_, endpointId) = peer { if endpointId != previousPeer?.1 { if let endpointId = endpointId { - self.avatarNode.isHidden = true - self.audioLevelView?.isHidden = true + self.setAvatarHidden(true) self.call.makeIncomingVideoView(endpointId: endpointId, completion: { [weak self] videoView in Queue.mainQueue().async { @@ -607,28 +628,32 @@ final class VoiceChatMainStageNode: ASDisplayNode { let previousVideoNode = strongSelf.currentVideoNode strongSelf.currentVideoNode = videoNode strongSelf.insertSubnode(videoNode, aboveSubnode: strongSelf.backgroundNode) - if let previousVideoNode = previousVideoNode { - Queue.mainQueue().after(0.03) { - previousVideoNode.removeFromSupernode() - } -// currentVideoNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak currentVideoNode] _ in -// currentVideoNode?.removeFromSupernode() -// }) - } - if let (size, sideInset, bottomInset, isLandscape) = strongSelf.validLayout { - strongSelf.update(size: size, sideInset: sideInset, bottomInset: bottomInset, isLandscape: isLandscape, transition: .immediate) - } if waitForFullSize { strongSelf.videoReadyDisposable.set((videoNode.ready |> filter { $0 } |> take(1) - |> deliverOnMainQueue).start(next: { _ in + |> deliverOnMainQueue).start(next: { [weak self] _ in Queue.mainQueue().after(0.07) { completion?() + + if let strongSelf = self { + if let (size, sideInset, bottomInset, isLandscape) = strongSelf.validLayout { + strongSelf.update(size: size, sideInset: sideInset, bottomInset: bottomInset, isLandscape: isLandscape, transition: .immediate) + } + } + if let previousVideoNode = previousVideoNode { + previousVideoNode.removeFromSupernode() + } } })) } else { + if let (size, sideInset, bottomInset, isLandscape) = strongSelf.validLayout { + strongSelf.update(size: size, sideInset: sideInset, bottomInset: bottomInset, isLandscape: isLandscape, transition: .immediate) + } + if let previousVideoNode = previousVideoNode { + previousVideoNode.removeFromSupernode() + } strongSelf.videoReadyDisposable.set(nil) completion?() } @@ -637,15 +662,14 @@ final class VoiceChatMainStageNode: ASDisplayNode { } }) } else { - self.avatarNode.isHidden = false - self.audioLevelView?.isHidden = false + self.setAvatarHidden(false) if let currentVideoNode = self.currentVideoNode { currentVideoNode.removeFromSupernode() self.currentVideoNode = nil } } } else { - self.audioLevelView?.isHidden = self.currentPeer?.1 != nil + self.setAvatarHidden(endpointId != nil) completion?() } } else { @@ -699,6 +723,10 @@ final class VoiceChatMainStageNode: ASDisplayNode { } transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: size)) + transition.updateFrame(node: self.backdropAvatarNode, frame: CGRect(origin: CGPoint(), size: size)) + if let backdropEffectView = self.backdropEffectView { + transition.updateFrame(view: backdropEffectView, frame: CGRect(origin: CGPoint(), size: size)) + } let avatarSize = CGSize(width: 180.0, height: 180.0) let avatarFrame = CGRect(origin: CGPoint(x: (size.width - avatarSize.width) / 2.0, y: (size.height - avatarSize.height) / 2.0), size: avatarSize) diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatTileItemNode.swift b/submodules/TelegramCallsUI/Sources/VoiceChatTileItemNode.swift index 56773baa86..207868260b 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatTileItemNode.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatTileItemNode.swift @@ -129,9 +129,7 @@ final class VoiceChatTileItemNode: ASDisplayNode { self.contentNode = ASDisplayNode() self.contentNode.clipsToBounds = true self.contentNode.cornerRadius = backgroundCornerRadius - if #available(iOS 13.0, *) { - self.contentNode.layer.cornerCurve = .continuous - } + self.backgroundNode = ASDisplayNode() self.backgroundNode.backgroundColor = panelBackgroundColor @@ -203,6 +201,10 @@ final class VoiceChatTileItemNode: ASDisplayNode { override func didLoad() { super.didLoad() + if #available(iOS 13.0, *) { + self.contentNode.layer.cornerCurve = .continuous + } + self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tap))) } @@ -405,12 +407,6 @@ final class VoiceChatTileItemNode: ASDisplayNode { videoNode.alpha = 1.0 self.videoNode = videoNode self.videoContainerNode.addSubnode(videoNode) - - if animate { -// self.videoContainerNode.layer.animateScale(from: sourceNode.bounds.width / videoSize.width, to: tileSize.width / videoSize.width, duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue) -// self.videoContainerNode.layer.animate(from: (tileSize.width / 2.0) as NSNumber, to: videoCornerRadius as NSNumber, keyPath: "cornerRadius", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.2, removeOnCompletion: false, completion: { _ in -// }) - } } if animate { @@ -445,7 +441,9 @@ final class VoiceChatTileItemNode: ASDisplayNode { sourceNode.layer.animateScale(from: 1.0, to: 0.0, duration: duration, timingFunction: timingFunction) } - self.fadeNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) + if transition.isAnimated { + self.fadeNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) + } } } }