diff --git a/submodules/TelegramCallsUI/Sources/GroupVideoNode.swift b/submodules/TelegramCallsUI/Sources/GroupVideoNode.swift index 0b09a00aaa..8f460fa659 100644 --- a/submodules/TelegramCallsUI/Sources/GroupVideoNode.swift +++ b/submodules/TelegramCallsUI/Sources/GroupVideoNode.swift @@ -147,7 +147,23 @@ final class GroupVideoNode: ASDisplayNode { } var aspectRatio: CGFloat { - return self.videoView.getAspect() + let orientation = self.videoView.getOrientation() + var aspect = self.videoView.getAspect() + if aspect <= 0.01 { + aspect = 3.0 / 4.0 + } + let rotatedAspect: CGFloat + switch orientation { + case .rotation0: + rotatedAspect = 1.0 / aspect + case .rotation90: + rotatedAspect = aspect + case .rotation180: + rotatedAspect = 1.0 / aspect + case .rotation270: + rotatedAspect = aspect + } + return rotatedAspect } var keepBackdropSize = false @@ -247,7 +263,7 @@ final class GroupVideoNode: ASDisplayNode { } if let backdropEffectView = self.backdropEffectView { - let maxSide = max(bounds.width, bounds.height) + let maxSide = max(bounds.width, bounds.height) * 2.0 let squareBounds = CGRect(x: (bounds.width - maxSide) / 2.0, y: (bounds.width - maxSide) / 2.0, width: maxSide, height: maxSide) transition.animateView { backdropEffectView.frame = squareBounds diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift index e48e2a0cd2..a62b5b4804 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift @@ -701,8 +701,9 @@ public final class VoiceChatController: ViewController { fileprivate let actionButton: VoiceChatActionButton private let leftBorderNode: ASDisplayNode private let rightBorderNode: ASDisplayNode - private let mainStageNode: VoiceChatMainStageNode private let mainStageContainerNode: ASDisplayNode + private let mainStageBackgroundNode: ASDisplayNode + private let mainStageNode: VoiceChatMainStageNode private let transitionContainerNode: ASDisplayNode private var isScheduling = false @@ -807,7 +808,7 @@ public final class VoiceChatController: ViewController { private var endpointToPeerId: [String: PeerId] = [:] private var peerIdToEndpoint: [PeerId: String] = [:] - private var currentDominantSpeaker: PeerId? + private var currentDominantSpeaker: (PeerId, Double)? private var currentForcedSpeaker: PeerId? private var effectiveSpeaker: (PeerId, String?)? @@ -969,13 +970,18 @@ public final class VoiceChatController: ViewController { self.rightBorderNode.isUserInteractionEnabled = false self.rightBorderNode.clipsToBounds = false - self.mainStageNode = VoiceChatMainStageNode(context: self.context, call: self.call) - self.mainStageContainerNode = ASDisplayNode() self.mainStageContainerNode.clipsToBounds = true self.mainStageContainerNode.isUserInteractionEnabled = false self.mainStageContainerNode.isHidden = true + self.mainStageBackgroundNode = ASDisplayNode() +// self.mainStageBackgroundNode.backgroundColor = .black + self.mainStageBackgroundNode.alpha = 0.0 + self.mainStageBackgroundNode.isUserInteractionEnabled = false + + self.mainStageNode = VoiceChatMainStageNode(context: self.context, call: self.call) + self.transitionContainerNode = ASDisplayNode() self.transitionContainerNode.clipsToBounds = true self.transitionContainerNode.isUserInteractionEnabled = false @@ -1032,13 +1038,13 @@ public final class VoiceChatController: ViewController { }, switchToPeer: { [weak self] peerId, videoEndpointId, expand in if let strongSelf = self { if expand, let videoEndpointId = videoEndpointId { - strongSelf.currentDominantSpeaker = peerId + strongSelf.currentDominantSpeaker = (peerId, CACurrentMediaTime()) strongSelf.effectiveSpeaker = (peerId, videoEndpointId) strongSelf.updateDisplayMode(.fullscreen(controlsHidden: false)) } else { strongSelf.currentForcedSpeaker = nil - if peerId != strongSelf.currentDominantSpeaker { - strongSelf.currentDominantSpeaker = peerId + if peerId != strongSelf.currentDominantSpeaker?.0 { + strongSelf.currentDominantSpeaker = (peerId, CACurrentMediaTime()) } strongSelf.updateMainVideo(waitForFullSize: false, updateMembers: true, force: true) } @@ -1657,6 +1663,7 @@ public final class VoiceChatController: ViewController { self.contentContainer.addSubnode(self.fullscreenListNode) self.addSubnode(self.transitionContainerNode) + self.mainStageContainerNode.addSubnode(self.mainStageBackgroundNode) self.mainStageContainerNode.addSubnode(self.mainStageNode) self.updateDecorationsColors() @@ -1801,7 +1808,7 @@ public final class VoiceChatController: ViewController { if maxLevelWithVideo == nil { if let peerId = strongSelf.currentDominantSpeaker { - maxLevelWithVideo = (peerId, 0.0) + maxLevelWithVideo = (peerId.0, 0.0) } else if strongSelf.peerIdToEndpoint.count > 0 { for entry in strongSelf.currentEntries { if case let .peer(peerEntry) = entry { @@ -1816,7 +1823,10 @@ public final class VoiceChatController: ViewController { if case .fullscreen = strongSelf.displayMode { if let (peerId, _) = maxLevelWithVideo { - strongSelf.currentDominantSpeaker = peerId + if let peer = strongSelf.currentDominantSpeaker, CACurrentMediaTime() - peer.1 < 5.0 { + } else { + strongSelf.currentDominantSpeaker = (peerId, CACurrentMediaTime()) + } strongSelf.updateMainVideo(waitForFullSize: false) } } @@ -1962,7 +1972,7 @@ public final class VoiceChatController: ViewController { self.mainStageNode.togglePin = { [weak self] in if let strongSelf = self { if let peerId = strongSelf.currentForcedSpeaker { - strongSelf.currentDominantSpeaker = peerId + strongSelf.currentDominantSpeaker = (peerId, CACurrentMediaTime()) strongSelf.currentForcedSpeaker = nil } else { strongSelf.currentForcedSpeaker = strongSelf.effectiveSpeaker?.0 @@ -3137,6 +3147,7 @@ public final class VoiceChatController: ViewController { let videoBottomEdgeY = self.isLandscape ? layout.size.height : layout.size.height - layout.intrinsicInsets.bottom - 84.0 let videoFrame = CGRect(x: 0.0, y: videoTopEdgeY, width: isLandscape ? layout.size.width - layout.safeInsets.right - 84.0 : layout.size.width, height: videoBottomEdgeY - videoTopEdgeY) transition.updateFrame(node: self.mainStageContainerNode, frame: videoFrame) + transition.updateFrame(node: self.mainStageBackgroundNode, frame: CGRect(origin: CGPoint(), size: videoFrame.size)) if !self.mainStageNode.animating { transition.updateFrame(node: self.mainStageNode, frame: CGRect(origin: CGPoint(), size: videoFrame.size)) } @@ -4331,26 +4342,12 @@ public final class VoiceChatController: ViewController { if !validSources.contains(videoEndpointId) { self.videoNodes[videoEndpointId] = nil self.readyVideoDisposables.set(nil, forKey: videoEndpointId) - -// loop: for j in 0 ..< self.currentFullscreenEntries.count { -// let fullscreenEntry = self.currentFullscreenEntries[j] -// switch fullscreenEntry { -// case let .peer(peerEntry): -// if peerEntry.effectiveVideoEndpointId == videoEndpointId { -// let presentationData = self.presentationData.withUpdated(theme: self.darkTheme) -// self.fullscreenListNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [ListViewUpdateItem(index: j, previousIndex: j, item: fullscreenEntry.item(context: self.context, presentationData: presentationData, interaction: self.itemInteraction!), directionHint: nil)], options: [.Synchronous], updateOpaqueState: nil) -// break loop -// } -// default: -// break -// } -// } } } } private func updateMainVideo(waitForFullSize: Bool, updateMembers: Bool = true, force: Bool = false, completion: (() -> Void)? = nil) { - let effectiveMainSpeaker = self.currentForcedSpeaker ?? self.currentDominantSpeaker + let effectiveMainSpeaker = self.currentForcedSpeaker ?? self.currentDominantSpeaker?.0 guard effectiveMainSpeaker != self.effectiveSpeaker?.0 || force else { return } @@ -4473,42 +4470,46 @@ public final class VoiceChatController: ViewController { if self.isScheduled && translation < 0.0 { return } - - if case let .modal(isExpanded, previousIsFilled) = self.effectiveDisplayMode { - var topInset: CGFloat = 0.0 - if let (currentTopInset, currentPanOffset) = self.panGestureArguments { - topInset = currentTopInset - - if case let .known(value) = contentOffset, value <= 0.5 { + + switch self.effectiveDisplayMode { + case let .modal(isExpanded, previousIsFilled): + var topInset: CGFloat = 0.0 + if let (currentTopInset, currentPanOffset) = self.panGestureArguments { + topInset = currentTopInset + + if case let .known(value) = contentOffset, value <= 0.5 { + } else { + translation = currentPanOffset + if self.isExpanded { + recognizer.setTranslation(CGPoint(), in: self.contentContainer.view) + } + } + + self.panGestureArguments = (currentTopInset, translation) + } + + let currentOffset = topInset + translation + + var isFilled = previousIsFilled + if currentOffset < 20.0 { + isFilled = true + } else if currentOffset > 40.0 { + isFilled = false + } + if isFilled != previousIsFilled { + self.displayMode = .modal(isExpanded: isExpanded, isFilled: isFilled) + self.updateDecorationsColors() + } + + if self.isExpanded { } else { - translation = currentPanOffset - if self.isExpanded { - recognizer.setTranslation(CGPoint(), in: self.contentContainer.view) + if currentOffset > 0.0 { + self.listNode.scroller.panGestureRecognizer.setTranslation(CGPoint(), in: self.listNode.scroller) } } + case let .fullscreen(controlsHidden): + break - self.panGestureArguments = (currentTopInset, translation) - } - - let currentOffset = topInset + translation - - var isFilled = previousIsFilled - if currentOffset < 20.0 { - isFilled = true - } else if currentOffset > 40.0 { - isFilled = false - } - if isFilled != previousIsFilled { - self.displayMode = .modal(isExpanded: isExpanded, isFilled: isFilled) - self.updateDecorationsColors() - } - - if self.isExpanded { - } else { - if currentOffset > 0.0 { - self.listNode.scroller.panGestureRecognizer.setTranslation(CGPoint(), in: self.listNode.scroller) - } - } } if let (layout, navigationHeight) = self.validLayout { @@ -5051,6 +5052,9 @@ public final class VoiceChatController: ViewController { let effectiveSpeakerPeerId = self.effectiveSpeaker?.0 if let effectiveSpeakerPeerId = effectiveSpeakerPeerId, let otherItemNode = verticalItemNodes[effectiveSpeakerPeerId] { self.mainStageNode.animateTransitionIn(from: otherItemNode, transition: transition) + + self.mainStageBackgroundNode.alpha = 1.0 + self.mainStageBackgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } self.fullscreenListNode.forEachItemNode { itemNode in @@ -5125,6 +5129,8 @@ public final class VoiceChatController: ViewController { self?.mainStageContainerNode.isHidden = true }) + self.mainStageBackgroundNode.alpha = 0.0 + self.mainStageBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2) if let (layout, navigationHeight) = self.validLayout { self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: transition) diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatMainStageNode.swift b/submodules/TelegramCallsUI/Sources/VoiceChatMainStageNode.swift index 1b09aeb721..db97496aa9 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatMainStageNode.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatMainStageNode.swift @@ -253,12 +253,15 @@ final class VoiceChatMainStageNode: ASDisplayNode { transition.updateFrame(view: snapshotView, frame: infoFrame) } + sourceNode.alpha = 0.0 + self.animatingIn = true let startLocalFrame = sourceNode.view.convert(sourceNode.bounds, to: self.supernode?.view) 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: isLandscape, force: true, transition: transition) transition.updateFrame(node: self, frame: targetFrame, completion: { [weak self] _ in + sourceNode.alpha = 1.0 self?.animatingIn = false }) } @@ -300,12 +303,15 @@ final class VoiceChatMainStageNode: ASDisplayNode { transition.updateFrame(view: snapshotView, frame: CGRect(origin: CGPoint(), size: targetFrame.size)) } + targetNode.alpha = 0.0 + 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() infoView?.removeFromSuperview() + targetNode.alpha = 1.0 strongSelf.animatingOut = false strongSelf.frame = initialFrame strongSelf.update(size: initialFrame.size, sideInset: sideInset, bottomInset: bottomInset, isLandscape: isLandscape, transition: .immediate) diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatShareScreenContextItem.swift b/submodules/TelegramCallsUI/Sources/VoiceChatShareScreenContextItem.swift new file mode 100644 index 0000000000..0ad3a7f341 --- /dev/null +++ b/submodules/TelegramCallsUI/Sources/VoiceChatShareScreenContextItem.swift @@ -0,0 +1,184 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import SwiftSignalKit +import TelegramPresentationData +import AppBundle +import ContextUI +import TelegramStringFormatting + +final class VoiceChatShareScreenContextItem: ContextMenuCustomItem { + fileprivate let text: String + fileprivate let icon: (PresentationTheme) -> UIImage? + fileprivate let action: (ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void + + init(text: String, icon: @escaping (PresentationTheme) -> UIImage?, action: @escaping (ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void) { + self.text = text + self.icon = icon + self.action = action + } + + func node(presentationData: PresentationData, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) -> ContextMenuCustomNode { + return VoiceChatShareScreenContextItemNode(presentationData: presentationData, item: self, getController: getController, actionSelected: actionSelected) + } +} + +private let textFont = Font.regular(17.0) + +private final class VoiceChatShareScreenContextItemNode: ASDisplayNode, ContextMenuCustomNode { + private let item: VoiceChatShareScreenContextItem + private let presentationData: PresentationData + private let getController: () -> ContextControllerProtocol? + private let actionSelected: (ContextMenuActionResult) -> Void + + private let backgroundNode: ASDisplayNode + private let highlightedBackgroundNode: ASDisplayNode + private let textNode: ImmediateTextNode + private let iconNode: VoiceChatRecordingIconNode + private let buttonNode: HighlightTrackingButtonNode + + private var timer: SwiftSignalKit.Timer? + + private var pointerInteraction: PointerInteraction? + + init(presentationData: PresentationData, item: VoiceChatShareScreenContextItem, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) { + self.item = item + self.presentationData = presentationData + self.getController = getController + self.actionSelected = actionSelected + + let textFont = Font.regular(presentationData.listsFontSize.baseDisplaySize) + let subtextFont = Font.regular(presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0) + + self.backgroundNode = ASDisplayNode() + self.backgroundNode.isAccessibilityElement = false + self.backgroundNode.backgroundColor = presentationData.theme.contextMenu.itemBackgroundColor + self.highlightedBackgroundNode = ASDisplayNode() + self.highlightedBackgroundNode.isAccessibilityElement = false + self.highlightedBackgroundNode.backgroundColor = presentationData.theme.contextMenu.itemHighlightedBackgroundColor + self.highlightedBackgroundNode.alpha = 0.0 + + self.textNode = ImmediateTextNode() + self.textNode.isAccessibilityElement = false + self.textNode.isUserInteractionEnabled = false + self.textNode.displaysAsynchronously = false + self.textNode.attributedText = NSAttributedString(string: item.text, font: textFont, textColor: presentationData.theme.contextMenu.primaryColor) + + self.textNode.maximumNumberOfLines = 1 + + self.buttonNode = HighlightTrackingButtonNode() + self.buttonNode.isAccessibilityElement = true + self.buttonNode.accessibilityLabel = presentationData.strings.VoiceChat_StopRecording + + self.iconNode = VoiceChatRecordingIconNode(hasBackground: true) + + super.init() + + self.addSubnode(self.backgroundNode) + self.addSubnode(self.highlightedBackgroundNode) + self.addSubnode(self.textNode) + self.addSubnode(self.iconNode) + self.addSubnode(self.buttonNode) + + self.buttonNode.highligthedChanged = { [weak self] highligted in + guard let strongSelf = self else { + return + } + if highligted { + strongSelf.highlightedBackgroundNode.alpha = 1.0 + } else { + strongSelf.highlightedBackgroundNode.alpha = 0.0 + strongSelf.highlightedBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3) + } + } + self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) + } + + deinit { + self.timer?.invalidate() + } + + override func didLoad() { + super.didLoad() + + self.pointerInteraction = PointerInteraction(node: self.buttonNode, style: .hover, willEnter: { [weak self] in + if let strongSelf = self { + strongSelf.highlightedBackgroundNode.alpha = 0.75 + } + }, willExit: { [weak self] in + if let strongSelf = self { + strongSelf.highlightedBackgroundNode.alpha = 0.0 + } + }) + } + + private var validLayout: CGSize? + func updateLayout(constrainedWidth: CGFloat) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void) { + let sideInset: CGFloat = 16.0 + let iconSideInset: CGFloat = 12.0 + let verticalInset: CGFloat = 12.0 + + let iconSide = 16.0 + (1.0 + UIScreenPixel) * 2.0 + let iconSize: CGSize = CGSize(width: iconSide, height: iconSide) + + let standardIconWidth: CGFloat = 32.0 + var rightTextInset: CGFloat = sideInset + if !iconSize.width.isZero { + rightTextInset = max(iconSize.width, standardIconWidth) + iconSideInset + sideInset + } + + let textSize = self.textNode.updateLayout(CGSize(width: constrainedWidth - sideInset - rightTextInset, height: .greatestFiniteMagnitude)) + + + let verticalSpacing: CGFloat = 2.0 + let combinedTextHeight = textSize.height + verticalSpacing + return (CGSize(width: textSize.width + sideInset + rightTextInset, height: verticalInset * 2.0 + combinedTextHeight), { size, transition in + let hadLayout = self.validLayout != nil + self.validLayout = size + + let verticalOrigin = floor((size.height - combinedTextHeight) / 2.0) + let textFrame = CGRect(origin: CGPoint(x: sideInset, y: verticalOrigin), size: textSize) + transition.updateFrameAdditive(node: self.textNode, frame: textFrame) + + if !iconSize.width.isZero { + transition.updateFrameAdditive(node: self.iconNode, frame: CGRect(origin: CGPoint(x: size.width - standardIconWidth - iconSideInset + floor((standardIconWidth - iconSize.width) / 2.0), y: floor((size.height - iconSize.height) / 2.0)), size: iconSize)) + } + + transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height))) + transition.updateFrame(node: self.highlightedBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height))) + transition.updateFrame(node: self.buttonNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height))) + }) + } + + func updateTheme(presentationData: PresentationData) { + self.backgroundNode.backgroundColor = presentationData.theme.contextMenu.itemBackgroundColor + self.highlightedBackgroundNode.backgroundColor = presentationData.theme.contextMenu.itemHighlightedBackgroundColor + + let textFont = Font.regular(presentationData.listsFontSize.baseDisplaySize) + let subtextFont = Font.regular(presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0) + + self.textNode.attributedText = NSAttributedString(string: self.textNode.attributedText?.string ?? "", font: textFont, textColor: presentationData.theme.contextMenu.primaryColor) + } + + @objc private func buttonPressed() { + self.performAction() + } + + func performAction() { + guard let controller = self.getController() else { + return + } + self.item.action(controller, { [weak self] result in + self?.actionSelected(result) + }) + } + + func setIsHighlighted(_ value: Bool) { + if value { + self.highlightedBackgroundNode.alpha = 1.0 + } else { + self.highlightedBackgroundNode.alpha = 0.0 + } + } +}