diff --git a/submodules/ContextUI/Sources/PeekController.swift b/submodules/ContextUI/Sources/PeekController.swift index c5483234cc..43d8c9f45f 100644 --- a/submodules/ContextUI/Sources/PeekController.swift +++ b/submodules/ContextUI/Sources/PeekController.swift @@ -41,6 +41,10 @@ public final class PeekController: ViewController, ContextControllerProtocol { return self.displayNode as! PeekControllerNode } + public var contentNode: PeekControllerContentNode & ASDisplayNode { + return self.controllerNode.contentNode + } + private let presentationData: PresentationData private let content: PeekControllerContent var sourceNode: () -> ASDisplayNode? diff --git a/submodules/ContextUI/Sources/PeekControllerNode.swift b/submodules/ContextUI/Sources/PeekControllerNode.swift index 2417bac3f6..9f865a877b 100644 --- a/submodules/ContextUI/Sources/PeekControllerNode.swift +++ b/submodules/ContextUI/Sources/PeekControllerNode.swift @@ -23,7 +23,7 @@ final class PeekControllerNode: ViewControllerTracingNode { private var validLayout: ContainerViewLayout? private var content: PeekControllerContent - private var contentNode: PeekControllerContentNode & ASDisplayNode + var contentNode: PeekControllerContentNode & ASDisplayNode private var contentNodeHasValidLayout = false private var topAccessoryNode: ASDisplayNode? diff --git a/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewControllerNode.swift b/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewControllerNode.swift index 6dcbd9e908..dc2c3257e5 100644 --- a/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewControllerNode.swift +++ b/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewControllerNode.swift @@ -89,6 +89,8 @@ final class StickerPackPreviewControllerNode: ViewControllerTracingNode, UIScrol private var hapticFeedback: HapticFeedback? + private weak var peekController: PeekController? + init(context: AccountContext, openShare: (() -> Void)?, openMention: @escaping (String) -> Void, actionPerformed: ((StickerPackCollectionInfo, [ItemCollectionItem], StickerPackScreenPerformedAction) -> Void)?) { self.context = context self.openShare = openShare @@ -208,10 +210,11 @@ final class StickerPackPreviewControllerNode: ViewControllerTracingNode, UIScrol var menuItems: [ContextMenuItem] = [] if let stickerPack = strongSelf.stickerPack, case let .result(info, _, _) = stickerPack, info.id.namespace == Namespaces.ItemCollection.CloudStickerPacks { if strongSelf.sendSticker != nil { - menuItems.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.StickerPack_Send, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Resend"), color: theme.contextMenu.primaryColor) }, action: { _, f in + menuItems.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.StickerPack_Send, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Resend"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in + if let strongSelf = self, let peekController = strongSelf.peekController, let animationNode = (peekController.contentNode as? StickerPreviewPeekContentNode)?.animationNode { + let _ = strongSelf.sendSticker?(.standalone(media: item.file), animationNode, animationNode.bounds) + } f(.default) - -// let _ = strongSelf.sendSticker?(.standalone(media: item.file), node, rect) }))) } menuItems.append(.action(ContextMenuActionItem(text: isStarred ? strongSelf.presentationData.strings.Stickers_RemoveFromFavorites : strongSelf.presentationData.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 @@ -244,6 +247,7 @@ final class StickerPackPreviewControllerNode: ViewControllerTracingNode, UIScrol strongSelf.contentGridNode.forceHidden = visible } } + strongSelf.peekController = controller strongSelf.presentInGlobalOverlay?(controller, nil) return controller } diff --git a/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift b/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift index ab05e621fb..e7ad90f035 100644 --- a/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift +++ b/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift @@ -101,6 +101,8 @@ private final class StickerPackContainer: ASDisplayNode { private let interaction: StickerPackPreviewInteraction + private weak var peekController: PeekController? + init(index: Int, context: AccountContext, presentationData: PresentationData, stickerPack: StickerPackReference, decideNextAction: @escaping (StickerPackContainer, StickerPackAction) -> StickerPackNextAction, requestDismiss: @escaping () -> Void, expandProgressUpdated: @escaping (StickerPackContainer, ContainedViewLayoutTransition, ContainedViewLayoutTransition) -> Void, presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)?) { self.index = index self.context = context @@ -281,9 +283,10 @@ private final class StickerPackContainer: ASDisplayNode { if let (info, _, _) = strongSelf.currentStickerPack, info.id.namespace == Namespaces.ItemCollection.CloudStickerPacks { if strongSelf.sendSticker != nil { menuItems.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.StickerPack_Send, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Resend"), color: theme.contextMenu.primaryColor) }, action: { _, f in + if let strongSelf = self, let peekController = strongSelf.peekController, let animationNode = (peekController.contentNode as? StickerPreviewPeekContentNode)?.animationNode { + let _ = strongSelf.sendSticker?(.standalone(media: item.file), animationNode, animationNode.bounds) + } f(.default) - -// let _ = strongSelf.sendSticker?(.standalone(media: item.file), node, rect) }))) } menuItems.append(.action(ContextMenuActionItem(text: isStarred ? strongSelf.presentationData.strings.Stickers_RemoveFromFavorites : strongSelf.presentationData.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 @@ -311,6 +314,7 @@ private final class StickerPackContainer: ASDisplayNode { let controller = PeekController(presentationData: strongSelf.presentationData, content: content, sourceNode: { return sourceNode }) + strongSelf.peekController = controller strongSelf.presentInGlobalOverlay(controller, nil) return controller } diff --git a/submodules/StickerPackPreviewUI/Sources/StickerPreviewPeekContent.swift b/submodules/StickerPackPreviewUI/Sources/StickerPreviewPeekContent.swift index 7f55f7cd67..6d5d122f2f 100644 --- a/submodules/StickerPackPreviewUI/Sources/StickerPreviewPeekContent.swift +++ b/submodules/StickerPackPreviewUI/Sources/StickerPreviewPeekContent.swift @@ -65,13 +65,13 @@ public final class StickerPreviewPeekContent: PeekControllerContent { } } -private final class StickerPreviewPeekContentNode: ASDisplayNode, PeekControllerContentNode { +public final class StickerPreviewPeekContentNode: ASDisplayNode, PeekControllerContentNode { private let account: Account private let item: StickerPreviewPeekItem private var textNode: ASTextNode private var imageNode: TransformImageNode - private var animationNode: AnimatedStickerNode? + public var animationNode: AnimatedStickerNode? private var containerLayout: (ContainerViewLayout, CGFloat)? @@ -115,7 +115,7 @@ private final class StickerPreviewPeekContentNode: ASDisplayNode, PeekController } } - func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { + public func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { let boundingSize = CGSize(width: 180.0, height: 180.0).fitted(size) if let dimensitons = self.item.file.dimensions { diff --git a/submodules/TelegramCallsUI/Sources/GroupVideoNode.swift b/submodules/TelegramCallsUI/Sources/GroupVideoNode.swift index 574f744121..0b09a00aaa 100644 --- a/submodules/TelegramCallsUI/Sources/GroupVideoNode.swift +++ b/submodules/TelegramCallsUI/Sources/GroupVideoNode.swift @@ -6,6 +6,18 @@ import SwiftSignalKit import AccountContext final class GroupVideoNode: ASDisplayNode { + enum Position { + case tile + case list + case mainstage + } + + enum LayoutMode { + case fillOrFitToSquare + case fillHorizontal + case fillVertical + } + private let videoViewContainer: UIView private let videoView: PresentationCallVideoView @@ -16,7 +28,7 @@ final class GroupVideoNode: ASDisplayNode { private var effectView: UIVisualEffectView? private var isBlurred: Bool = false - private var validLayout: (CGSize, Bool)? + private var validLayout: (CGSize, LayoutMode)? var tapped: (() -> Void)? @@ -62,8 +74,8 @@ final class GroupVideoNode: ASDisplayNode { return } strongSelf.readyPromise.set(true) - if let (size, isLandscape) = strongSelf.validLayout { - strongSelf.updateLayout(size: size, isLandscape: isLandscape, transition: .immediate) + if let (size, layoutMode) = strongSelf.validLayout { + strongSelf.updateLayout(size: size, layoutMode: layoutMode, transition: .immediate) } } }) @@ -73,8 +85,8 @@ final class GroupVideoNode: ASDisplayNode { guard let strongSelf = self else { return } - if let (size, isLandscape) = strongSelf.validLayout { - strongSelf.updateLayout(size: size, isLandscape: isLandscape, transition: .immediate) + if let (size, layoutMode) = strongSelf.validLayout { + strongSelf.updateLayout(size: size, layoutMode: layoutMode, transition: .immediate) } } }) @@ -138,8 +150,9 @@ final class GroupVideoNode: ASDisplayNode { return self.videoView.getAspect() } - func updateLayout(size: CGSize, isLandscape: Bool, transition: ContainedViewLayoutTransition) { - self.validLayout = (size, isLandscape) + var keepBackdropSize = false + func updateLayout(size: CGSize, layoutMode: LayoutMode, transition: ContainedViewLayoutTransition) { + self.validLayout = (size, layoutMode) let bounds = CGRect(origin: CGPoint(), size: size) transition.updateFrameAsPositionAndBounds(layer: self.videoViewContainer.layer, frame: bounds) transition.updateFrameAsPositionAndBounds(layer: self.backdropVideoViewContainer.layer, frame: bounds) @@ -182,13 +195,25 @@ final class GroupVideoNode: ASDisplayNode { let fittedSize = rotatedVideoSize.aspectFitted(containerSize) let filledSize = rotatedVideoSize.aspectFilled(containerSize) + let filledToSquareSize = rotatedVideoSize.aspectFilled(CGSize(width: containerSize.height, height: containerSize.height)) - if isLandscape { - rotatedVideoSize = fittedSize - } else { - rotatedVideoSize = filledSize + switch layoutMode { + case .fillOrFitToSquare: + rotatedVideoSize = filledToSquareSize + case .fillHorizontal: + if rotatedVideoSize.width > rotatedVideoSize.height { + rotatedVideoSize = filledSize + } else { + rotatedVideoSize = fittedSize + } + case .fillVertical: + if rotatedVideoSize.width < rotatedVideoSize.height { + rotatedVideoSize = filledSize + } else { + rotatedVideoSize = fittedSize + } } - + var rotatedVideoFrame = CGRect(origin: CGPoint(x: floor((size.width - rotatedVideoSize.width) / 2.0), y: floor((size.height - rotatedVideoSize.height) / 2.0)), size: rotatedVideoSize) rotatedVideoFrame.origin.x = floor(rotatedVideoFrame.origin.x) rotatedVideoFrame.origin.y = floor(rotatedVideoFrame.origin.y) @@ -224,7 +249,9 @@ final class GroupVideoNode: ASDisplayNode { if let backdropEffectView = self.backdropEffectView { let maxSide = max(bounds.width, bounds.height) let squareBounds = CGRect(x: (bounds.width - maxSide) / 2.0, y: (bounds.width - maxSide) / 2.0, width: maxSide, height: maxSide) - transition.updateFrame(view: backdropEffectView, frame: squareBounds) + transition.animateView { + backdropEffectView.frame = squareBounds + } } let transition: ContainedViewLayoutTransition = .immediate @@ -233,9 +260,5 @@ final class GroupVideoNode: ASDisplayNode { if let effectView = self.effectView { transition.updateFrame(view: effectView, frame: bounds) } - - // 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 } } diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatCameraPreviewController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatCameraPreviewController.swift index dcfc9d67fd..415c15383e 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatCameraPreviewController.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatCameraPreviewController.swift @@ -438,7 +438,7 @@ private class VoiceChatCameraPreviewControllerNode: ViewControllerTracingNode, U transition.updateFrame(node: self.previewContainerNode, frame: CGRect(origin: CGPoint(x: previewInset, y: 56.0), size: previewSize)) self.cameraNode.frame = CGRect(origin: CGPoint(), size: previewSize) - self.cameraNode.updateLayout(size: previewSize, isLandscape: false, transition: .immediate) + self.cameraNode.updateLayout(size: previewSize, layoutMode: .fillVertical, transition: .immediate) let microphoneFrame = CGRect(x: 16.0, y: previewSize.height - 48.0 - 16.0, width: 48.0, height: 48.0) transition.updateFrame(node: self.microphoneButton, frame: microphoneFrame) diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift index 5e6e2dba11..24cc8f1bcc 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift @@ -219,7 +219,7 @@ public final class VoiceChatController: ViewController { let togglePeerVideo: (PeerId) -> Void let openInvite: () -> Void let peerContextAction: (VoiceChatPeerEntry, ASDisplayNode, ContextGesture?) -> Void - let getPeerVideo: (String, Bool) -> GroupVideoNode? + let getPeerVideo: (String, GroupVideoNode.Position) -> GroupVideoNode? var isExpanded: Bool = false private var audioLevels: [PeerId: ValuePipe] = [:] @@ -232,7 +232,7 @@ public final class VoiceChatController: ViewController { togglePeerVideo: @escaping (PeerId) -> Void, openInvite: @escaping () -> Void, peerContextAction: @escaping (VoiceChatPeerEntry, ASDisplayNode, ContextGesture?) -> Void, - getPeerVideo: @escaping (String, Bool) -> GroupVideoNode? + getPeerVideo: @escaping (String, GroupVideoNode.Position) -> GroupVideoNode? ) { self.updateIsMuted = updateIsMuted self.switchToPeer = switchToPeer @@ -446,7 +446,7 @@ public final class VoiceChatController: ViewController { }, contextAction: { node, gesture in interaction.peerContextAction(peerEntry, node, gesture) }, getVideo: { - return interaction.getPeerVideo(videoEndpointId, false) + return interaction.getPeerVideo(videoEndpointId, .tile) }, getAudioLevel: { return interaction.getAudioLevel(peerEntry.peer.id) }) @@ -532,7 +532,7 @@ public final class VoiceChatController: ViewController { return VoiceChatFullscreenParticipantItem(presentationData: ItemListPresentationData(presentationData), nameDisplayOrder: presentationData.nameDisplayOrder, context: context, peer: peerEntry.peer, icon: icon, text: text, color: color, isLandscape: peerEntry.isLandscape, active: peerEntry.active, getAudioLevel: { return interaction.getAudioLevel(peerEntry.peer.id) }, getVideo: { if let endpointId = peerEntry.effectiveVideoEndpointId { - return interaction.getPeerVideo(endpointId, true) + return interaction.getPeerVideo(endpointId, .list) } else { return nil } @@ -1602,17 +1602,17 @@ public final class VoiceChatController: ViewController { let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData.withUpdated(theme: strongSelf.darkTheme), source: .extracted(source), items: items, reactionItems: [], gesture: gesture) contextController.useComplexItemsTransitionAnimation = true strongSelf.controller?.presentInGlobalOverlay(contextController) - }, getPeerVideo: { [weak self] endpointId, tile in + }, getPeerVideo: { [weak self] endpointId, position in guard let strongSelf = self else { return nil } - var skip = false + var ignore = false if case .fullscreen = strongSelf.displayMode { - skip = !tile + ignore = ![.mainstage, .list].contains(position) } else { - skip = tile + ignore = position != .tile } - if skip { + if ignore { return nil } for (listEndpointId, videoNode) in strongSelf.videoNodes { @@ -1814,9 +1814,11 @@ public final class VoiceChatController: ViewController { } } - if let (peerId, _) = maxLevelWithVideo { - strongSelf.currentDominantSpeaker = peerId - strongSelf.updateMainVideo(waitForFullSize: false) + if case .fullscreen = strongSelf.displayMode { + if let (peerId, _) = maxLevelWithVideo { + strongSelf.currentDominantSpeaker = peerId + strongSelf.updateMainVideo(waitForFullSize: false) + } } strongSelf.itemInteraction?.updateAudioLevels(levels) @@ -3138,7 +3140,7 @@ public final class VoiceChatController: ViewController { if !self.mainStageNode.animating { transition.updateFrame(node: self.mainStageNode, frame: CGRect(origin: CGPoint(), size: videoFrame.size)) } - self.mainStageNode.update(size: videoFrame.size, sideInset: layout.safeInsets.left, bottomInset: bottomInset, isLandscape: true, transition: transition) + self.mainStageNode.update(size: videoFrame.size, sideInset: layout.safeInsets.left, bottomInset: bottomInset, isLandscape: self.isLandscape, transition: transition) let backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: topPanelFrame.maxY), size: CGSize(width: size.width, height: layout.size.height)) @@ -4995,9 +4997,9 @@ public final class VoiceChatController: ViewController { isFullscreen = true } + self.displayMode = displayMode + let completion = { - self.displayMode = displayMode - self.updateDecorationsColors() self.mainStageContainerNode.isHidden = false diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatFullscreenParticipantItem.swift b/submodules/TelegramCallsUI/Sources/VoiceChatFullscreenParticipantItem.swift index acddff3063..517f8c2fe4 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatFullscreenParticipantItem.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatFullscreenParticipantItem.swift @@ -190,6 +190,7 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode { private var profileNode: VoiceChatPeerProfileNode? private var raiseHandTimer: SwiftSignalKit.Timer? + private var silenceTimer: SwiftSignalKit.Timer? var item: VoiceChatFullscreenParticipantItem? { return self.layoutParams?.0 @@ -280,306 +281,13 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode { } strongSelf.updateIsExtracted(isExtracted, transition: transition) } - -// self.contextSourceNode.willUpdateIsExtractedToContextPreview = { [weak self] isExtracted, transition in -// guard let strongSelf = self, let item = strongSelf.layoutParams?.0 else { -// return -// } -// -// strongSelf.isExtracted = isExtracted -// -// let inset: CGFloat = 12.0 -//// if isExtracted { -//// strongSelf.contextSourceNode.contentNode.customHitTest = { [weak self] point in -//// if let strongSelf = self { -//// if let avatarListWrapperNode = strongSelf.avatarListWrapperNode, avatarListWrapperNode.frame.contains(point) { -//// return strongSelf.avatarListNode?.view -//// } -//// } -//// return nil -//// } -//// } else { -//// strongSelf.contextSourceNode.contentNode.customHitTest = nil -//// } -// -// let extractedVerticalOffset = strongSelf.extractedVerticalOffset ?? 0.0 -// if let extractedRect = strongSelf.extractedRect, let nonExtractedRect = strongSelf.nonExtractedRect { -// let rect: CGRect -// if isExtracted { -// if extractedVerticalOffset > 0.0 { -// rect = CGRect(x: extractedRect.minX, y: extractedRect.minY + extractedVerticalOffset, width: extractedRect.width, height: extractedRect.height - extractedVerticalOffset) -// } else { -// rect = extractedRect -// } -// } else { -// rect = nonExtractedRect -// } -// -// let springDuration: Double = isExtracted ? 0.42 : 0.3 -// let springDamping: CGFloat = isExtracted ? 104.0 : 1000.0 -// -// let itemBackgroundColor: UIColor = item.getIsExpanded() ? UIColor(rgb: 0x1c1c1e) : UIColor(rgb: 0x2c2c2e) -// -// if !extractedVerticalOffset.isZero { -// let radiusTransition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut) -// if isExtracted { -// strongSelf.backgroundImageNode.image = generateImage(CGSize(width: backgroundCornerRadius * 2.0, height: backgroundCornerRadius * 2.0), rotatedContext: { (size, context) in -// let bounds = CGRect(origin: CGPoint(), size: size) -// context.clear(bounds) -// -// context.setFillColor(itemBackgroundColor.cgColor) -// context.fillEllipse(in: bounds) -// context.fill(CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height / 2.0)) -// })?.stretchableImage(withLeftCapWidth: Int(backgroundCornerRadius), topCapHeight: Int(backgroundCornerRadius)) -// strongSelf.extractedBackgroundImageNode.image = generateImage(CGSize(width: backgroundCornerRadius * 2.0, height: backgroundCornerRadius * 2.0), rotatedContext: { (size, context) in -// let bounds = CGRect(origin: CGPoint(), size: size) -// context.clear(bounds) -// -// context.setFillColor(item.presentationData.theme.list.itemBlocksBackgroundColor.cgColor) -// context.fillEllipse(in: bounds) -// context.fill(CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height / 2.0)) -// })?.stretchableImage(withLeftCapWidth: Int(backgroundCornerRadius), topCapHeight: Int(backgroundCornerRadius)) -// strongSelf.backgroundImageNode.cornerRadius = backgroundCornerRadius -// -// strongSelf.avatarNode.transform = CATransform3DIdentity -// var avatarInitialRect = strongSelf.avatarNode.view.convert(strongSelf.avatarNode.bounds, to: strongSelf.offsetContainerNode.supernode?.view) -// if strongSelf.avatarTransitionNode == nil { -// transition.updateCornerRadius(node: strongSelf.backgroundImageNode, cornerRadius: 0.0) -// -// let targetRect = CGRect(x: extractedRect.minX, y: extractedRect.minY, width: extractedRect.width, height: extractedRect.width) -// let initialScale = avatarInitialRect.width / targetRect.width -// avatarInitialRect.origin.y += backgroundCornerRadius / 2.0 * initialScale -// -// let avatarListWrapperNode = PinchSourceContainerNode() -// avatarListWrapperNode.clipsToBounds = true -// avatarListWrapperNode.cornerRadius = backgroundCornerRadius -// avatarListWrapperNode.activate = { [weak self] sourceNode in -// guard let strongSelf = self else { -// return -// } -// strongSelf.avatarListNode?.controlsContainerNode.alpha = 0.0 -// let pinchController = PinchController(sourceNode: sourceNode, getContentAreaInScreenSpace: { -// return UIScreen.main.bounds -// }) -// item.context.sharedContext.mainWindow?.presentInGlobalOverlay(pinchController) -// } -// avatarListWrapperNode.deactivated = { [weak self] in -// guard let strongSelf = self else { -// return -// } -// strongSelf.avatarListWrapperNode?.contentNode.layer.animate(from: 0.0 as NSNumber, to: backgroundCornerRadius as NSNumber, keyPath: "cornerRadius", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.3, completion: { _ in -// }) -// } -// avatarListWrapperNode.update(size: targetRect.size, transition: .immediate) -// avatarListWrapperNode.frame = CGRect(x: targetRect.minX, y: targetRect.minY, width: targetRect.width, height: targetRect.height + backgroundCornerRadius) -// avatarListWrapperNode.animatedOut = { [weak self] in -// guard let strongSelf = self else { -// return -// } -// strongSelf.avatarListNode?.controlsContainerNode.alpha = 1.0 -// strongSelf.avatarListNode?.controlsContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) -// } -// -// let transitionNode = ASImageNode() -// transitionNode.clipsToBounds = true -// transitionNode.displaysAsynchronously = false -// transitionNode.displayWithoutProcessing = true -// transitionNode.image = strongSelf.avatarNode.unroundedImage -// transitionNode.frame = CGRect(origin: CGPoint(), size: targetRect.size) -// transitionNode.cornerRadius = targetRect.width / 2.0 -// radiusTransition.updateCornerRadius(node: transitionNode, cornerRadius: 0.0) -// -// strongSelf.avatarNode.isHidden = true -// avatarListWrapperNode.contentNode.addSubnode(transitionNode) -// -// strongSelf.videoContainerNode.position = CGPoint(x: avatarListWrapperNode.frame.width / 2.0, y: avatarListWrapperNode.frame.height / 2.0) -// strongSelf.videoContainerNode.cornerRadius = tileSize.width / 2.0 -// strongSelf.videoContainerNode.transform = CATransform3DMakeScale(avatarListWrapperNode.frame.width / tileSize.width * 1.05, avatarListWrapperNode.frame.height / tileSize.width * 1.05, 1.0) -// avatarListWrapperNode.contentNode.addSubnode(strongSelf.videoContainerNode) -// -// strongSelf.avatarTransitionNode = transitionNode -// -// let avatarListContainerNode = ASDisplayNode() -// avatarListContainerNode.clipsToBounds = true -// avatarListContainerNode.frame = CGRect(origin: CGPoint(), size: targetRect.size) -// avatarListContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) -// avatarListContainerNode.cornerRadius = targetRect.width / 2.0 -// -// avatarListWrapperNode.layer.animateSpring(from: initialScale as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: springDuration, initialVelocity: 0.0, damping: springDamping) -// avatarListWrapperNode.layer.animateSpring(from: NSValue(cgPoint: avatarInitialRect.center), to: NSValue(cgPoint: avatarListWrapperNode.position), keyPath: "position", duration: springDuration, initialVelocity: 0.0, damping: springDamping, completion: { [weak self] _ in -// if let strongSelf = self, let avatarListNode = strongSelf.avatarListNode { -// avatarListNode.currentItemNode?.addSubnode(strongSelf.videoContainerNode) -// } -// }) -// -// radiusTransition.updateCornerRadius(node: avatarListContainerNode, cornerRadius: 0.0) -// radiusTransition.updateCornerRadius(node: strongSelf.videoContainerNode, cornerRadius: 0.0) -// -// let avatarListNode = PeerInfoAvatarListContainerNode(context: item.context) -// avatarListWrapperNode.contentNode.clipsToBounds = true -// avatarListNode.backgroundColor = .clear -// avatarListNode.peer = item.peer -// avatarListNode.firstFullSizeOnly = true -// avatarListNode.offsetLocation = true -// avatarListNode.customCenterTapAction = { [weak self] in -// self?.contextSourceNode.requestDismiss?() -// } -// avatarListNode.frame = CGRect(x: targetRect.width / 2.0, y: targetRect.height / 2.0, width: targetRect.width, height: targetRect.height) -// avatarListNode.controlsClippingNode.frame = CGRect(x: -targetRect.width / 2.0, y: -targetRect.height / 2.0, width: targetRect.width, height: targetRect.height) -// avatarListNode.controlsClippingOffsetNode.frame = CGRect(origin: CGPoint(x: targetRect.width / 2.0, y: targetRect.height / 2.0), size: CGSize()) -// avatarListNode.stripContainerNode.frame = CGRect(x: 0.0, y: 13.0, width: targetRect.width, height: 2.0) -// -// avatarListContainerNode.addSubnode(avatarListNode) -// avatarListContainerNode.addSubnode(avatarListNode.controlsClippingOffsetNode) -// avatarListWrapperNode.contentNode.addSubnode(avatarListContainerNode) -// -// avatarListNode.update(size: targetRect.size, peer: item.peer, customNode: strongSelf.videoContainerNode, additionalEntry: item.getUpdatingAvatar(), isExpanded: true, transition: .immediate) -// strongSelf.offsetContainerNode.supernode?.addSubnode(avatarListWrapperNode) -// -// strongSelf.audioLevelView?.alpha = 0.0 -// -// strongSelf.avatarListWrapperNode = avatarListWrapperNode -// strongSelf.avatarListContainerNode = avatarListContainerNode -// strongSelf.avatarListNode = avatarListNode -// } -// } else if let transitionNode = strongSelf.avatarTransitionNode, let avatarListWrapperNode = strongSelf.avatarListWrapperNode, let avatarListContainerNode = strongSelf.avatarListContainerNode { -// strongSelf.animatingExtraction = true -// -// transition.updateCornerRadius(node: strongSelf.backgroundImageNode, cornerRadius: backgroundCornerRadius) -// -// var avatarInitialRect = CGRect(origin: strongSelf.avatarNode.frame.origin, size: strongSelf.avatarNode.frame.size) -// let targetScale = avatarInitialRect.width / avatarListContainerNode.frame.width -// avatarInitialRect.origin.y += backgroundCornerRadius / 2.0 * targetScale -// -// strongSelf.avatarTransitionNode = nil -// strongSelf.avatarListWrapperNode = nil -// strongSelf.avatarListContainerNode = nil -// strongSelf.avatarListNode = nil -// -// avatarListContainerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak avatarListContainerNode] _ in -// avatarListContainerNode?.removeFromSupernode() -// }) -// -// avatarListWrapperNode.contentNode.insertSubnode(strongSelf.videoContainerNode, aboveSubnode: transitionNode) -// -// avatarListWrapperNode.layer.animate(from: 1.0 as NSNumber, to: targetScale as NSNumber, keyPath: "transform.scale", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.2, removeOnCompletion: false) -// avatarListWrapperNode.layer.animate(from: NSValue(cgPoint: avatarListWrapperNode.position), to: NSValue(cgPoint: avatarInitialRect.center), keyPath: "position", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.2, removeOnCompletion: false, completion: { [weak transitionNode, weak self] _ in -// transitionNode?.removeFromSupernode() -// self?.avatarNode.isHidden = false -// -// self?.audioLevelView?.alpha = 1.0 -// self?.audioLevelView?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) -// -// if let strongSelf = self { -// strongSelf.animatingExtraction = false -// -// strongSelf.offsetContainerNode.insertSubnode(strongSelf.videoContainerNode, belowSubnode: strongSelf.contentWrapperNode) -// -// switch item.style { -// case .list: -// strongSelf.videoFadeNode.alpha = 0.0 -// strongSelf.videoContainerNode.position = strongSelf.avatarNode.position -// strongSelf.videoContainerNode.cornerRadius = tileSize.width / 2.0 -// strongSelf.videoContainerNode.transform = CATransform3DMakeScale(avatarSize / tileSize.width, avatarSize / tileSize.width, 1.0) -// case .tile: -// strongSelf.videoFadeNode.alpha = 1.0 -// strongSelf.videoContainerNode.position = CGPoint(x: tileSize.width / 2.0, y: tileSize.height / 2.0) -// strongSelf.videoContainerNode.cornerRadius = backgroundCornerRadius -// strongSelf.videoContainerNode.transform = CATransform3DMakeScale(1.0, 1.0, 1.0) -// } -// } -// }) -// -// radiusTransition.updateCornerRadius(node: avatarListContainerNode, cornerRadius: avatarListContainerNode.frame.width / 2.0) -// radiusTransition.updateCornerRadius(node: transitionNode, cornerRadius: avatarListContainerNode.frame.width / 2.0) -// radiusTransition.updateCornerRadius(node: strongSelf.videoContainerNode, cornerRadius: tileSize.width / 2.0) -// } -// -// let alphaTransition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut) -// alphaTransition.updateAlpha(node: strongSelf.statusNode, alpha: isExtracted ? 0.0 : 1.0) -// alphaTransition.updateAlpha(node: strongSelf.expandedStatusNode, alpha: isExtracted ? 1.0 : 0.0) -// alphaTransition.updateAlpha(node: strongSelf.actionContainerNode, alpha: isExtracted ? 0.0 : 1.0, delay: isExtracted ? 0.0 : 0.1) -// -// let offsetInitialSublayerTransform = strongSelf.offsetContainerNode.layer.sublayerTransform -// strongSelf.offsetContainerNode.layer.sublayerTransform = CATransform3DMakeTranslation(isExtracted ? -33 : 0.0, isExtracted ? extractedVerticalOffset : 0.0, 0.0) -// -// let actionInitialSublayerTransform = strongSelf.actionContainerNode.layer.sublayerTransform -// strongSelf.actionContainerNode.layer.sublayerTransform = CATransform3DMakeTranslation(isExtracted ? 21.0 : 0.0, 0.0, 0.0) -// -// let initialBackgroundPosition = strongSelf.backgroundImageNode.position -// strongSelf.backgroundImageNode.layer.position = rect.center -// let initialBackgroundBounds = strongSelf.backgroundImageNode.bounds -// strongSelf.backgroundImageNode.layer.bounds = CGRect(origin: CGPoint(), size: rect.size) -// -// let initialExtractedBackgroundPosition = strongSelf.extractedBackgroundImageNode.position -// strongSelf.extractedBackgroundImageNode.layer.position = CGPoint(x: rect.size.width / 2.0, y: rect.size.height / 2.0) -// let initialExtractedBackgroundBounds = strongSelf.extractedBackgroundImageNode.bounds -// strongSelf.extractedBackgroundImageNode.layer.bounds = strongSelf.backgroundImageNode.layer.bounds -// if isExtracted { -// strongSelf.offsetContainerNode.layer.animateSpring(from: NSValue(caTransform3D: offsetInitialSublayerTransform), to: NSValue(caTransform3D: strongSelf.offsetContainerNode.layer.sublayerTransform), keyPath: "sublayerTransform", duration: springDuration, delay: 0.0, initialVelocity: 0.0, damping: springDamping) -// strongSelf.actionContainerNode.layer.animateSpring(from: NSValue(caTransform3D: actionInitialSublayerTransform), to: NSValue(caTransform3D: strongSelf.actionContainerNode.layer.sublayerTransform), keyPath: "sublayerTransform", duration: springDuration, delay: 0.0, initialVelocity: 0.0, damping: springDamping) -// strongSelf.backgroundImageNode.layer.animateSpring(from: NSValue(cgPoint: initialBackgroundPosition), to: NSValue(cgPoint: strongSelf.backgroundImageNode.position), keyPath: "position", duration: springDuration, delay: 0.0, initialVelocity: 0.0, damping: springDamping) -// strongSelf.backgroundImageNode.layer.animateSpring(from: NSValue(cgRect: initialBackgroundBounds), to: NSValue(cgRect: strongSelf.backgroundImageNode.bounds), keyPath: "bounds", duration: springDuration, initialVelocity: 0.0, damping: springDamping) -// strongSelf.extractedBackgroundImageNode.layer.animateSpring(from: NSValue(cgPoint: initialExtractedBackgroundPosition), to: NSValue(cgPoint: strongSelf.extractedBackgroundImageNode.position), keyPath: "position", duration: springDuration, delay: 0.0, initialVelocity: 0.0, damping: springDamping) -// strongSelf.extractedBackgroundImageNode.layer.animateSpring(from: NSValue(cgRect: initialExtractedBackgroundBounds), to: NSValue(cgRect: strongSelf.extractedBackgroundImageNode.bounds), keyPath: "bounds", duration: springDuration, initialVelocity: 0.0, damping: springDamping) -// } else { -// strongSelf.offsetContainerNode.layer.animate(from: NSValue(caTransform3D: offsetInitialSublayerTransform), to: NSValue(caTransform3D: strongSelf.offsetContainerNode.layer.sublayerTransform), keyPath: "sublayerTransform", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.2) -// strongSelf.actionContainerNode.layer.animate(from: NSValue(caTransform3D: actionInitialSublayerTransform), to: NSValue(caTransform3D: strongSelf.actionContainerNode.layer.sublayerTransform), keyPath: "sublayerTransform", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.2) -// strongSelf.backgroundImageNode.layer.animate(from: NSValue(cgPoint: initialBackgroundPosition), to: NSValue(cgPoint: strongSelf.backgroundImageNode.position), keyPath: "position", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.2) -// strongSelf.backgroundImageNode.layer.animate(from: NSValue(cgRect: initialBackgroundBounds), to: NSValue(cgRect: strongSelf.backgroundImageNode.bounds), keyPath: "bounds", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.2) -// strongSelf.extractedBackgroundImageNode.layer.animate(from: NSValue(cgPoint: initialExtractedBackgroundPosition), to: NSValue(cgPoint: strongSelf.extractedBackgroundImageNode.position), keyPath: "position", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.2) -// strongSelf.extractedBackgroundImageNode.layer.animate(from: NSValue(cgRect: initialExtractedBackgroundBounds), to: NSValue(cgRect: strongSelf.extractedBackgroundImageNode.bounds), keyPath: "bounds", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.2) -// } -// -// if isExtracted { -// strongSelf.backgroundImageNode.alpha = 1.0 -// strongSelf.extractedBackgroundImageNode.alpha = 1.0 -// strongSelf.extractedBackgroundImageNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1, delay: 0.1, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue) -// } else { -// strongSelf.extractedBackgroundImageNode.alpha = 0.0 -// strongSelf.extractedBackgroundImageNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, delay: 0.0, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, completion: { [weak self] _ in -// if let strongSelf = self { -// if strongSelf.item?.style == .list { -// strongSelf.backgroundImageNode.image = nil -// } -// strongSelf.extractedBackgroundImageNode.image = nil -// strongSelf.extractedBackgroundImageNode.layer.removeAllAnimations() -// } -// }) -// } -// } else { -// if isExtracted { -// strongSelf.backgroundImageNode.alpha = 0.0 -// strongSelf.extractedBackgroundImageNode.alpha = 1.0 -// strongSelf.backgroundImageNode.image = generateStretchableFilledCircleImage(diameter: backgroundCornerRadius * 2.0, color: itemBackgroundColor) -// strongSelf.extractedBackgroundImageNode.image = generateStretchableFilledCircleImage(diameter: backgroundCornerRadius * 2.0, color: item.presentationData.theme.list.itemBlocksBackgroundColor) -// } -// -// transition.updateFrame(node: strongSelf.backgroundImageNode, frame: rect) -// transition.updateFrame(node: strongSelf.extractedBackgroundImageNode, frame: CGRect(origin: CGPoint(), size: rect.size)) -// -// transition.updateAlpha(node: strongSelf.statusNode, alpha: isExtracted ? 0.0 : 1.0) -// transition.updateAlpha(node: strongSelf.expandedStatusNode, alpha: isExtracted ? 1.0 : 0.0) -// transition.updateAlpha(node: strongSelf.actionContainerNode, alpha: isExtracted ? 0.0 : 1.0) -// -// transition.updateSublayerTransformOffset(layer: strongSelf.offsetContainerNode.layer, offset: CGPoint(x: isExtracted ? inset : 0.0, y: isExtracted ? extractedVerticalOffset : 0.0)) -// transition.updateSublayerTransformOffset(layer: strongSelf.actionContainerNode.layer, offset: CGPoint(x: isExtracted ? -24.0 : 0.0, y: 0.0)) -// -// transition.updateAlpha(node: strongSelf.backgroundImageNode, alpha: isExtracted ? 1.0 : 0.0, completion: { _ in -// if !isExtracted { -// self?.backgroundImageNode.image = nil -// self?.extractedBackgroundImageNode.image = nil -// } -// }) -// } -// } -// } } deinit { self.videoReadyDisposable.dispose() self.audioLevelDisposable.dispose() self.raiseHandTimer?.invalidate() + self.silenceTimer?.invalidate() } override func selected() { @@ -620,7 +328,7 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode { self.videoContainerNode.insertSubnode(videoNode, at: 0) if animate { - videoNode.updateLayout(size: videoSize, isLandscape: true, transition: transition) + videoNode.updateLayout(size: videoSize, layoutMode: .fillOrFitToSquare, transition: transition) let scale = sourceNode.bounds.width / videoSize.width self.videoContainerNode.layer.animateScale(from: sourceNode.bounds.width / videoSize.width, to: tileSize.width / videoSize.width, duration: duration, timingFunction: timingFunction) @@ -630,7 +338,7 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode { self.videoFadeNode.alpha = 1.0 self.videoFadeNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue) } else if initialAnimate { - videoNode.updateLayout(size: videoSize, isLandscape: true, transition: .immediate) + videoNode.updateLayout(size: videoSize, layoutMode: .fillOrFitToSquare, transition: .immediate) self.videoFadeNode.alpha = 1.0 } } @@ -966,8 +674,21 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode { if let wavesColor = strongSelf.wavesColor { audioLevelView.setColor(wavesColor, animated: true) } + + if let silenceTimer = strongSelf.silenceTimer { + silenceTimer.invalidate() + strongSelf.silenceTimer = nil + } } else { avatarScale = 1.0 + if strongSelf.silenceTimer == nil { + let silenceTimer = SwiftSignalKit.Timer(timeout: 1.0, repeat: false, completion: { [weak self] in + self?.audioLevelView?.stopAnimating(duration: 0.5) + self?.silenceTimer = nil + }, queue: Queue.mainQueue()) + strongSelf.silenceTimer = silenceTimer + silenceTimer.start() + } } if !strongSelf.animatingSelection { @@ -1150,7 +871,7 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode { } } - videoNode.updateLayout(size: videoSize, isLandscape: true, transition: .immediate) + videoNode.updateLayout(size: videoSize, layoutMode: .fillOrFitToSquare, transition: .immediate) if !strongSelf.isExtracted && !strongSelf.animatingExtraction { if videoNode.supernode !== strongSelf.videoContainerNode { videoNode.clipsToBounds = true diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatMainStageNode.swift b/submodules/TelegramCallsUI/Sources/VoiceChatMainStageNode.swift index b20e47b41f..466aceb394 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatMainStageNode.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatMainStageNode.swift @@ -18,6 +18,7 @@ import AvatarNode import AudioBlob private let backArrowImage = NavigationBarTheme.generateBackArrowImage(color: .white) +private let backgroundCornerRadius: CGFloat = 11.0 final class VoiceChatMainStageNode: ASDisplayNode { private let context: AccountContext @@ -58,9 +59,9 @@ final class VoiceChatMainStageNode: ASDisplayNode { var togglePin: (() -> Void)? var getAudioLevel: ((PeerId) -> Signal)? - private let videoReadyDisposable = MetaDisposable() - + private var silenceTimer: SwiftSignalKit.Timer? + init(context: AccountContext, call: PresentationGroupCall) { self.context = context self.call = call @@ -85,7 +86,6 @@ final class VoiceChatMainStageNode: ASDisplayNode { }) self.bottomFadeNode = ASImageNode() - self.bottomFadeNode.alpha = 0.0 self.bottomFadeNode.displaysAsynchronously = false self.bottomFadeNode.displayWithoutProcessing = true self.bottomFadeNode.contentMode = .scaleToFill @@ -136,8 +136,11 @@ final class VoiceChatMainStageNode: ASDisplayNode { super.init() self.clipsToBounds = true - self.cornerRadius = 11.0 - + self.cornerRadius = backgroundCornerRadius + if #available(iOS 13.0, *) { + self.layer.cornerCurve = .continuous + } + self.addSubnode(self.backgroundNode) self.addSubnode(self.topFadeNode) self.addSubnode(self.bottomFadeNode) @@ -195,6 +198,7 @@ final class VoiceChatMainStageNode: ASDisplayNode { self.audioLevelDisposable.dispose() self.speakingPeerDisposable.dispose() self.speakingAudioLevelDisposable.dispose() + self.silenceTimer?.invalidate() } override func didLoad() { @@ -219,42 +223,53 @@ final class VoiceChatMainStageNode: ASDisplayNode { self.togglePin?() } - var animating = false + var animating: Bool { + return self.animatingIn || self.animatingOut + } + private var animatingIn = false + private var animatingOut = false func animateTransitionIn(from sourceNode: ASDisplayNode, transition: ContainedViewLayoutTransition) { - guard let sourceNode = sourceNode as? VoiceChatTileItemNode, let _ = sourceNode.item, let (_, sideInset, bottomInset, _) = self.validLayout else { + guard let sourceNode = sourceNode as? VoiceChatTileItemNode, let _ = sourceNode.item, let (_, sideInset, bottomInset, isLandscape) = self.validLayout else { return } let alphaTransition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .linear) alphaTransition.updateAlpha(node: self.backgroundNode, alpha: 1.0) alphaTransition.updateAlpha(node: self.topFadeNode, alpha: 1.0) - alphaTransition.updateAlpha(node: self.bottomFadeNode, alpha: 1.0) alphaTransition.updateAlpha(node: self.titleNode, alpha: 1.0) alphaTransition.updateAlpha(node: self.microphoneNode, alpha: 1.0) alphaTransition.updateAlpha(node: self.headerNode, alpha: 1.0) - sourceNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3) - - self.animating = true let targetFrame = self.frame + + if let snapshotView = sourceNode.infoNode.view.snapshotView(afterScreenUpdates: false) { + self.view.addSubview(snapshotView) + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak snapshotView] _ in + snapshotView?.removeFromSuperview() + }) + var infoFrame = snapshotView.frame + infoFrame.origin.y = targetFrame.height - infoFrame.height - bottomInset + transition.updateFrame(view: snapshotView, frame: infoFrame) + } + + self.animatingIn = true let startLocalFrame = sourceNode.view.convert(sourceNode.bounds, to: self.supernode?.view) - self.update(size: startLocalFrame.size, sideInset: sideInset, bottomInset: bottomInset, isLandscape: true, force: true, transition: .immediate) + self.update(size: startLocalFrame.size, sideInset: sideInset, bottomInset: bottomInset, isLandscape: isLandscape, force: true, transition: .immediate) self.frame = startLocalFrame - self.update(size: targetFrame.size, sideInset: sideInset, bottomInset: bottomInset, isLandscape: true, force: true, transition: transition) + self.update(size: targetFrame.size, sideInset: sideInset, bottomInset: bottomInset, isLandscape: isLandscape, force: true, transition: transition) transition.updateFrame(node: self, frame: targetFrame, completion: { [weak self] _ in - self?.animating = false + self?.animatingIn = false }) } func animateTransitionOut(to targetNode: ASDisplayNode?, transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) { - guard let (_, sideInset, bottomInset, _) = self.validLayout else { + guard let (_, sideInset, bottomInset, isLandscape) = self.validLayout else { return } let alphaTransition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .linear) alphaTransition.updateAlpha(node: self.backgroundNode, alpha: 0.0) alphaTransition.updateAlpha(node: self.topFadeNode, alpha: 0.0) -// alphaTransition.updateAlpha(node: self.bottomFadeNode, alpha: 0.0) alphaTransition.updateAlpha(node: self.titleNode, alpha: 0.0) alphaTransition.updateAlpha(node: self.microphoneNode, alpha: 0.0) alphaTransition.updateAlpha(node: self.headerNode, alpha: 0.0) @@ -264,25 +279,35 @@ final class VoiceChatMainStageNode: ASDisplayNode { return } - targetNode.fadeNode.isHidden = true - targetNode.isHidden = false targetNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) - self.animating = true + self.animatingOut = true let initialFrame = self.frame let targetFrame = targetNode.view.convert(targetNode.bounds, to: self.supernode?.view) - self.update(size: targetFrame.size, sideInset: sideInset, bottomInset: bottomInset, isLandscape: true, force: true, transition: transition) + + self.currentVideoNode?.keepBackdropSize = true + + var infoView: UIView? + if let snapshotView = targetNode.infoNode.view.snapshotView(afterScreenUpdates: false) { + infoView = snapshotView + self.view.addSubview(snapshotView) + snapshotView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3, removeOnCompletion: false) + var infoFrame = snapshotView.frame + infoFrame.origin.y = initialFrame.height - infoFrame.height - bottomInset + snapshotView.frame = infoFrame + transition.updateFrame(view: snapshotView, frame: CGRect(origin: CGPoint(), size: targetFrame.size)) + } + + self.update(size: targetFrame.size, sideInset: sideInset, bottomInset: bottomInset, isLandscape: isLandscape, force: true, transition: transition) transition.updateFrame(node: self, frame: targetFrame, completion: { [weak self] _ in if let strongSelf = self { completion() - strongSelf.bottomFadeNode.alpha = 0.0 - targetNode.fadeNode.isHidden = false - - strongSelf.animating = false + infoView?.removeFromSuperview() + strongSelf.animatingOut = false strongSelf.frame = initialFrame - strongSelf.update(size: initialFrame.size, sideInset: sideInset, bottomInset: bottomInset, isLandscape: true, transition: .immediate) + strongSelf.update(size: initialFrame.size, sideInset: sideInset, bottomInset: bottomInset, isLandscape: isLandscape, transition: .immediate) } }) } @@ -331,11 +356,6 @@ final class VoiceChatMainStageNode: ASDisplayNode { audioLevelView.startAnimating() avatarScale = 1.03 + level * 0.13 audioLevelView.setColor(wavesColor, animated: true) - - if let silenceTimer = strongSelf.silenceTimer { - silenceTimer.invalidate() - strongSelf.silenceTimer = nil - } } else { avatarScale = 1.0 } @@ -354,7 +374,6 @@ final class VoiceChatMainStageNode: ASDisplayNode { } } - private var silenceTimer: SwiftSignalKit.Timer? func update(peerEntry: VoiceChatPeerEntry, pinned: Bool) { let previousPeerEntry = self.currentPeerEntry self.currentPeerEntry = peerEntry @@ -481,37 +500,41 @@ final class VoiceChatMainStageNode: ASDisplayNode { self.call.makeIncomingVideoView(endpointId: endpointId, completion: { [weak self] videoView in Queue.mainQueue().async { - guard let strongSelf = self, let videoView = videoView else { - return - } - - let videoNode = GroupVideoNode(videoView: videoView, backdropVideoView: nil) - 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, aboveSubnode: strongSelf.backgroundNode) - 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 - Queue.mainQueue().after(0.01) { + self?.call.makeIncomingVideoView(endpointId: endpointId, completion: { [weak self] backdropVideoView in + Queue.mainQueue().async { + guard let strongSelf = self, let videoView = videoView else { + return + } + + let videoNode = GroupVideoNode(videoView: videoView, backdropVideoView: backdropVideoView) + 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, aboveSubnode: strongSelf.backgroundNode) + 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 + Queue.mainQueue().after(0.01) { + completion?() + } + })) + } else { + strongSelf.videoReadyDisposable.set(nil) completion?() } - })) - } else { - strongSelf.videoReadyDisposable.set(nil) - completion?() - } + } + }) } }) } else { @@ -549,9 +572,20 @@ final class VoiceChatMainStageNode: ASDisplayNode { bottomInset = 14.0 } + let layoutMode: GroupVideoNode.LayoutMode + if case .immediate = transition, self.animatingIn { + layoutMode = .fillOrFitToSquare + bottomInset = 0.0 + } else if self.animatingOut { + layoutMode = .fillOrFitToSquare + bottomInset = 0.0 + } else { + layoutMode = isLandscape ? .fillHorizontal : .fillVertical + } + 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, layoutMode: layoutMode, transition: transition) } transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: size)) @@ -570,7 +604,7 @@ final class VoiceChatMainStageNode: ASDisplayNode { transition.updateFrame(node: self.microphoneNode, frame: CGRect(origin: CGPoint(x: sideInset + 7.0, y: size.height - bottomInset - animationSize.height - 6.0), size: animationSize)) var fadeHeight: CGFloat = 50.0 - if size.width < size.height { + if size.height != 180.0 && size.width < size.height { fadeHeight = 140.0 } transition.updateFrame(node: self.bottomFadeNode, frame: CGRect(x: 0.0, y: size.height - fadeHeight, width: size.width, height: fadeHeight)) diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatParticipantItem.swift b/submodules/TelegramCallsUI/Sources/VoiceChatParticipantItem.swift index baf7a4e6f8..a9593d69b8 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatParticipantItem.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatParticipantItem.swift @@ -287,6 +287,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode { private var wavesColor: UIColor? private var raiseHandTimer: SwiftSignalKit.Timer? + private var silenceTimer: SwiftSignalKit.Timer? var item: VoiceChatParticipantItem? { return self.layoutParams?.0 @@ -646,6 +647,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode { deinit { self.audioLevelDisposable.dispose() self.raiseHandTimer?.invalidate() + self.silenceTimer?.invalidate() } override func selected() { @@ -1035,9 +1037,21 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode { if let wavesColor = strongSelf.wavesColor { audioLevelView.setColor(wavesColor, animated: true) } + + if let silenceTimer = strongSelf.silenceTimer { + silenceTimer.invalidate() + strongSelf.silenceTimer = nil + } } else { - audioLevelView.stopAnimating(duration: 0.5) avatarScale = 1.0 + if strongSelf.silenceTimer == nil { + let silenceTimer = SwiftSignalKit.Timer(timeout: 1.0, repeat: false, completion: { [weak self] in + self?.audioLevelView?.stopAnimating(duration: 0.5) + self?.silenceTimer = nil + }, queue: Queue.mainQueue()) + strongSelf.silenceTimer = silenceTimer + silenceTimer.start() + } } let transition: ContainedViewLayoutTransition = .animated(duration: 0.15, curve: .easeInOut) diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatPeerProfileNode.swift b/submodules/TelegramCallsUI/Sources/VoiceChatPeerProfileNode.swift index e1dc60ed5d..6a67e1265a 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatPeerProfileNode.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatPeerProfileNode.swift @@ -222,7 +222,7 @@ final class VoiceChatPeerProfileNode: ASDisplayNode { self.avatarListContainerNode.cornerRadius = targetRect.width / 2.0 if let videoNode = sourceNode.videoNode { - videoNode.updateLayout(size: targetSize, isLandscape: true, transition: transition) + videoNode.updateLayout(size: targetSize, layoutMode: .fillOrFitToSquare, transition: transition) transition.updateFrame(node: videoNode, frame: CGRect(origin: CGPoint(), size: targetSize)) transition.updateFrame(node: sourceNode.videoContainerNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: targetSize.width, height: targetSize.height + backgroundCornerRadius))) sourceNode.videoContainerNode.cornerRadius = backgroundCornerRadius @@ -301,7 +301,7 @@ final class VoiceChatPeerProfileNode: ASDisplayNode { self.avatarListContainerNode.cornerRadius = targetRect.width / 2.0 if false, let videoNode = sourceNode.videoNode { - videoNode.updateLayout(size: targetSize, isLandscape: true, transition: transition) + videoNode.updateLayout(size: targetSize, layoutMode: .fillOrFitToSquare, transition: transition) transition.updateFrame(node: videoNode, frame: CGRect(origin: CGPoint(), size: targetSize)) transition.updateFrame(node: sourceNode.videoContainerNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: targetSize.width, height: targetSize.height + backgroundCornerRadius))) sourceNode.videoContainerNode.cornerRadius = backgroundCornerRadius @@ -392,7 +392,7 @@ final class VoiceChatPeerProfileNode: ASDisplayNode { } if let videoNode = targetNode.videoNode { - videoNode.updateLayout(size: targetRect.size, isLandscape: true, transition: transition) + videoNode.updateLayout(size: targetRect.size, layoutMode: .fillOrFitToSquare, transition: transition) transition.updateFrame(node: videoNode, frame: targetRect) transition.updateFrame(node: targetNode.videoContainerNode, frame: targetRect) } @@ -445,7 +445,7 @@ final class VoiceChatPeerProfileNode: ASDisplayNode { // } if false, let videoNode = targetNode.videoNode { - videoNode.updateLayout(size: targetRect.size, isLandscape: true, transition: transition) + videoNode.updateLayout(size: targetRect.size, layoutMode: .fillOrFitToSquare, transition: transition) transition.updateFrame(node: videoNode, frame: targetRect) transition.updateFrame(node: targetNode.videoContainerNode, frame: targetRect) } diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatTileItemNode.swift b/submodules/TelegramCallsUI/Sources/VoiceChatTileItemNode.swift index 17049cf8d1..2d74f8c7f8 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatTileItemNode.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatTileItemNode.swift @@ -127,8 +127,10 @@ final class VoiceChatTileItemNode: ASDisplayNode { self.contentNode = ASDisplayNode() self.contentNode.clipsToBounds = true - self.contentNode.cornerRadius = 11.0 - + self.contentNode.cornerRadius = backgroundCornerRadius + if #available(iOS 13.0, *) { + self.contentNode.layer.cornerCurve = .continuous + } self.backgroundNode = ASDisplayNode() self.backgroundNode.backgroundColor = panelBackgroundColor @@ -355,7 +357,7 @@ final class VoiceChatTileItemNode: ASDisplayNode { if self.videoContainerNode.supernode === self.contentNode { if let videoNode = self.videoNode { transition.updateFrame(node: videoNode, frame: bounds) - videoNode.updateLayout(size: size, isLandscape: true, transition: itemTransition) + videoNode.updateLayout(size: size, layoutMode: .fillOrFitToSquare, transition: itemTransition) } transition.updateFrame(node: self.videoContainerNode, frame: bounds) } @@ -429,10 +431,10 @@ final class VoiceChatTileItemNode: ASDisplayNode { } }) - self.videoNode?.updateLayout(size: self.bounds.size, isLandscape: true, transition: transition) + self.videoNode?.updateLayout(size: self.bounds.size, layoutMode: .fillOrFitToSquare, transition: transition) self.videoNode?.frame = self.bounds } else if !initialAnimate { - self.videoNode?.updateLayout(size: self.bounds.size, isLandscape: true, transition: .immediate) + self.videoNode?.updateLayout(size: self.bounds.size, layoutMode: .fillOrFitToSquare, transition: .immediate) self.videoNode?.frame = self.bounds sourceNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration, timingFunction: timingFunction, removeOnCompletion: false, completion: { [weak sourceNode] _ in diff --git a/submodules/TelegramUI/Sources/ChatMediaInputNode.swift b/submodules/TelegramUI/Sources/ChatMediaInputNode.swift index d3abdc20c6..c8d5e99b65 100644 --- a/submodules/TelegramUI/Sources/ChatMediaInputNode.swift +++ b/submodules/TelegramUI/Sources/ChatMediaInputNode.swift @@ -1133,8 +1133,8 @@ final class ChatMediaInputNode: ChatInputNode { 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 +// strongSelf.controllerInteraction.sendSticker(.standalone(media: item.file), nil, false, node, rect) f(.default) - }))) } @@ -1147,7 +1147,6 @@ final class ChatMediaInputNode: ChatInputNode { } } } - 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) @@ -1189,8 +1188,6 @@ final class ChatMediaInputNode: ChatInputNode { } } }))) - - return (itemNode, StickerPreviewPeekContent(account: strongSelf.context.account, item: item, menu: menuItems)) } else { return nil diff --git a/submodules/TelegramUI/Sources/FeaturedStickersScreen.swift b/submodules/TelegramUI/Sources/FeaturedStickersScreen.swift index 2554f0722f..6c75ea74ea 100644 --- a/submodules/TelegramUI/Sources/FeaturedStickersScreen.swift +++ b/submodules/TelegramUI/Sources/FeaturedStickersScreen.swift @@ -213,6 +213,8 @@ private final class FeaturedStickersScreenNode: ViewControllerTracingNode { private var searchNode: FeaturedPaneSearchContentNode? + private weak var peekController: PeekController? + private let _ready = Promise() var ready: Promise { return self._ready @@ -464,9 +466,10 @@ private final class FeaturedStickersScreenNode: ViewControllerTracingNode { var menuItems: [ContextMenuItem] = [] menuItems = [ .action(ContextMenuActionItem(text: strongSelf.presentationData.strings.StickerPack_Send, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Resend"), color: theme.contextMenu.primaryColor) }, action: { _, f in + if let strongSelf = self, let peekController = strongSelf.peekController, let animationNode = (peekController.contentNode as? StickerPreviewPeekContentNode)?.animationNode { + let _ = strongSelf.sendSticker?(.standalone(media: item.file), animationNode, animationNode.bounds) + } f(.default) - -// let _ = strongSelf.sendSticker?(.standalone(media: item.file), node, rect) })), .action(ContextMenuActionItem(text: isStarred ? strongSelf.presentationData.strings.Stickers_RemoveFromFavorites : strongSelf.presentationData.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) @@ -527,9 +530,10 @@ private final class FeaturedStickersScreenNode: ViewControllerTracingNode { var menuItems: [ContextMenuItem] = [] menuItems = [ .action(ContextMenuActionItem(text: strongSelf.presentationData.strings.StickerPack_Send, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Resend"), color: theme.contextMenu.primaryColor) }, action: { _, f in + if let strongSelf = self, let peekController = strongSelf.peekController, let animationNode = (peekController.contentNode as? StickerPreviewPeekContentNode)?.animationNode { + let _ = strongSelf.sendSticker?(.standalone(media: item.file), animationNode, animationNode.bounds) + } f(.default) - -// let _ = strongSelf.sendSticker?(.standalone(media: item.file), node, rect) })), .action(ContextMenuActionItem(text: isStarred ? strongSelf.presentationData.strings.Stickers_RemoveFromFavorites : strongSelf.presentationData.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) @@ -581,6 +585,7 @@ private final class FeaturedStickersScreenNode: ViewControllerTracingNode { let controller = PeekController(presentationData: strongSelf.presentationData, content: content, sourceNode: { return sourceNode }) + strongSelf.peekController = controller strongSelf.controller?.presentInGlobalOverlay(controller) return controller } diff --git a/submodules/TelegramUI/Sources/HorizontalStickersChatContextPanelNode.swift b/submodules/TelegramUI/Sources/HorizontalStickersChatContextPanelNode.swift index 844ef3932d..a3c8bd6b53 100755 --- a/submodules/TelegramUI/Sources/HorizontalStickersChatContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/HorizontalStickersChatContextPanelNode.swift @@ -178,9 +178,9 @@ final class HorizontalStickersChatContextPanelNode: ChatInputContextPanelNode { 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) + f(.default) - let _ = controllerInteraction.sendSticker(.standalone(media: item.file), nil, true, itemNode, itemNode.bounds) + let _ = controllerInteraction.sendSticker(.standalone(media: item.file), nil, true, itemNode, itemNode.bounds) })), .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)