diff --git a/submodules/TelegramCallsUI/Sources/VideoChatExpandedParticipantThumbnailsComponent.swift b/submodules/TelegramCallsUI/Sources/VideoChatExpandedParticipantThumbnailsComponent.swift index 355c75d06f..ff93749b66 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatExpandedParticipantThumbnailsComponent.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatExpandedParticipantThumbnailsComponent.swift @@ -11,6 +11,7 @@ import SwiftSignalKit import MetalEngine import CallScreen import AvatarNode +import ContextUI final class VideoChatParticipantThumbnailComponent: Component { let call: PresentationGroupCall @@ -21,6 +22,7 @@ final class VideoChatParticipantThumbnailComponent: Component { let isSpeaking: Bool let interfaceOrientation: UIInterfaceOrientation let action: (() -> Void)? + let contextAction: ((EnginePeer, ContextExtractedContentContainingView, ContextGesture) -> Void)? init( call: PresentationGroupCall, @@ -30,7 +32,8 @@ final class VideoChatParticipantThumbnailComponent: Component { isSelected: Bool, isSpeaking: Bool, interfaceOrientation: UIInterfaceOrientation, - action: (() -> Void)? + action: (() -> Void)?, + contextAction: ((EnginePeer, ContextExtractedContentContainingView, ContextGesture) -> Void)? ) { self.call = call self.theme = theme @@ -40,6 +43,7 @@ final class VideoChatParticipantThumbnailComponent: Component { self.isSpeaking = isSpeaking self.interfaceOrientation = interfaceOrientation self.action = action + self.contextAction = contextAction } static func ==(lhs: VideoChatParticipantThumbnailComponent, rhs: VideoChatParticipantThumbnailComponent) -> Bool { @@ -64,6 +68,12 @@ final class VideoChatParticipantThumbnailComponent: Component { if lhs.interfaceOrientation != rhs.interfaceOrientation { return false } + if (lhs.action == nil) != (rhs.action == nil) { + return false + } + if (lhs.contextAction == nil) != (rhs.contextAction == nil) { + return false + } return true } @@ -79,7 +89,7 @@ final class VideoChatParticipantThumbnailComponent: Component { } } - final class View: HighlightTrackingButton { + final class View: ContextControllerSourceView { private static let selectedBorderImage: UIImage? = { return generateStretchableFilledCircleImage(diameter: 20.0, color: nil, strokeColor: UIColor.white, strokeWidth: 2.0)?.withRenderingMode(.alwaysTemplate) }() @@ -88,6 +98,10 @@ final class VideoChatParticipantThumbnailComponent: Component { private weak var componentState: EmptyComponentState? private var isUpdating: Bool = false + private let extractedContainerView: ContextExtractedContentContainingView + + private let backgroundLayer: SimpleLayer + private var avatarNode: AvatarNode? private let title = ComponentView() private let muteStatus = ComponentView() @@ -101,13 +115,30 @@ final class VideoChatParticipantThumbnailComponent: Component { private var videoSpec: VideoSpec? override init(frame: CGRect) { + self.extractedContainerView = ContextExtractedContentContainingView() + + self.backgroundLayer = SimpleLayer() + self.backgroundLayer.backgroundColor = UIColor(rgb: 0x1C1C1E).cgColor + super.init(frame: frame) - //TODO:release optimize - self.clipsToBounds = true - self.layer.cornerRadius = 10.0 + self.addSubview(self.extractedContainerView) + self.targetViewForActivationProgress = self.extractedContainerView.contentView - self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside) + self.extractedContainerView.contentView.layer.addSublayer(self.backgroundLayer) + + self.extractedContainerView.contentView.clipsToBounds = true + self.extractedContainerView.contentView.layer.cornerRadius = 10.0 + + self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))) + + self.activated = { [weak self] gesture, _ in + guard let self, let component = self.component else { + gesture.cancel() + return + } + component.contextAction?(EnginePeer(component.participant.peer), self.extractedContainerView, gesture) + } } required init?(coder: NSCoder) { @@ -118,11 +149,13 @@ final class VideoChatParticipantThumbnailComponent: Component { self.videoDisposable?.dispose() } - @objc private func pressed() { - guard let component = self.component, let action = component.action else { - return + @objc private func tapGesture(_ recognizer: UITapGestureRecognizer) { + if case .ended = recognizer.state { + guard let component = self.component, let action = component.action else { + return + } + action() } - action() } func update(component: VideoChatParticipantThumbnailComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { @@ -131,10 +164,6 @@ final class VideoChatParticipantThumbnailComponent: Component { self.isUpdating = false } - if self.component == nil { - self.backgroundColor = UIColor(rgb: 0x1C1C1E) - } - let previousComponent = self.component let wasSpeaking = previousComponent?.isSpeaking ?? false @@ -156,6 +185,14 @@ final class VideoChatParticipantThumbnailComponent: Component { self.component = component self.componentState = state + transition.setFrame(layer: self.backgroundLayer, frame: CGRect(origin: CGPoint(), size: availableSize)) + + transition.setPosition(view: self.extractedContainerView, position: CGRect(origin: CGPoint(), size: availableSize).center) + transition.setBounds(view: self.extractedContainerView, bounds: CGRect(origin: CGPoint(), size: availableSize)) + transition.setPosition(view: self.extractedContainerView.contentView, position: CGRect(origin: CGPoint(), size: availableSize).center) + transition.setBounds(view: self.extractedContainerView.contentView, bounds: CGRect(origin: CGPoint(), size: availableSize)) + self.extractedContainerView.contentRect = CGRect(origin: CGPoint(), size: availableSize) + let avatarNode: AvatarNode if let current = self.avatarNode { avatarNode = current @@ -163,7 +200,7 @@ final class VideoChatParticipantThumbnailComponent: Component { avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 17.0)) avatarNode.isUserInteractionEnabled = false self.avatarNode = avatarNode - self.addSubview(avatarNode.view) + self.extractedContainerView.contentView.addSubview(avatarNode.view) } let avatarSize = CGSize(width: 50.0, height: 50.0) @@ -188,7 +225,7 @@ final class VideoChatParticipantThumbnailComponent: Component { let muteStatusFrame = CGRect(origin: CGPoint(x: availableSize.width + 5.0 - muteStatusSize.width, y: availableSize.height + 5.0 - muteStatusSize.height), size: muteStatusSize) if let muteStatusView = self.muteStatus.view as? VideoChatMuteIconComponent.View { if muteStatusView.superview == nil { - self.addSubview(muteStatusView) + self.extractedContainerView.contentView.addSubview(muteStatusView) } transition.setPosition(view: muteStatusView, position: muteStatusFrame.center) transition.setBounds(view: muteStatusView, bounds: CGRect(origin: CGPoint(), size: muteStatusFrame.size)) @@ -208,7 +245,7 @@ final class VideoChatParticipantThumbnailComponent: Component { if titleView.superview == nil { titleView.layer.anchorPoint = CGPoint() titleView.isUserInteractionEnabled = false - self.addSubview(titleView) + self.extractedContainerView.contentView.addSubview(titleView) } transition.setPosition(view: titleView, position: titleFrame.origin) titleView.bounds = CGRect(origin: CGPoint(), size: titleFrame.size) @@ -222,7 +259,7 @@ final class VideoChatParticipantThumbnailComponent: Component { videoBackgroundLayer = SimpleLayer() videoBackgroundLayer.backgroundColor = UIColor(white: 0.1, alpha: 1.0).cgColor self.videoBackgroundLayer = videoBackgroundLayer - self.layer.insertSublayer(videoBackgroundLayer, above: avatarNode.layer) + self.extractedContainerView.contentView.layer.insertSublayer(videoBackgroundLayer, above: avatarNode.layer) videoBackgroundLayer.isHidden = true } @@ -232,8 +269,8 @@ final class VideoChatParticipantThumbnailComponent: Component { } else { videoLayer = PrivateCallVideoLayer() self.videoLayer = videoLayer - self.layer.insertSublayer(videoLayer.blurredLayer, above: videoBackgroundLayer) - self.layer.insertSublayer(videoLayer, above: videoLayer.blurredLayer) + self.extractedContainerView.contentView.layer.insertSublayer(videoLayer.blurredLayer, above: videoBackgroundLayer) + self.extractedContainerView.contentView.layer.insertSublayer(videoLayer, above: videoLayer.blurredLayer) videoLayer.blurredLayer.opacity = 0.25 @@ -346,7 +383,7 @@ final class VideoChatParticipantThumbnailComponent: Component { selectedBorderView = UIImageView() self.selectedBorderView = selectedBorderView selectedBorderView.alpha = 0.0 - self.addSubview(selectedBorderView) + self.extractedContainerView.contentView.addSubview(selectedBorderView) selectedBorderView.image = View.selectedBorderImage selectedBorderView.frame = CGRect(origin: CGPoint(), size: availableSize) @@ -438,6 +475,7 @@ final class VideoChatExpandedParticipantThumbnailsComponent: Component { let speakingParticipants: Set let interfaceOrientation: UIInterfaceOrientation let updateSelectedParticipant: (Participant.Key) -> Void + let contextAction: ((EnginePeer, ContextExtractedContentContainingView, ContextGesture) -> Void)? init( call: PresentationGroupCall, @@ -446,7 +484,8 @@ final class VideoChatExpandedParticipantThumbnailsComponent: Component { selectedParticipant: Participant.Key?, speakingParticipants: Set, interfaceOrientation: UIInterfaceOrientation, - updateSelectedParticipant: @escaping (Participant.Key) -> Void + updateSelectedParticipant: @escaping (Participant.Key) -> Void, + contextAction: ((EnginePeer, ContextExtractedContentContainingView, ContextGesture) -> Void)? ) { self.call = call self.theme = theme @@ -455,6 +494,7 @@ final class VideoChatExpandedParticipantThumbnailsComponent: Component { self.speakingParticipants = speakingParticipants self.interfaceOrientation = interfaceOrientation self.updateSelectedParticipant = updateSelectedParticipant + self.contextAction = contextAction } static func ==(lhs: VideoChatExpandedParticipantThumbnailsComponent, rhs: VideoChatExpandedParticipantThumbnailsComponent) -> Bool { @@ -476,6 +516,9 @@ final class VideoChatExpandedParticipantThumbnailsComponent: Component { if lhs.interfaceOrientation != rhs.interfaceOrientation { return false } + if (lhs.contextAction == nil) != (rhs.contextAction == nil) { + return false + } return true } @@ -617,7 +660,8 @@ final class VideoChatExpandedParticipantThumbnailsComponent: Component { return } component.updateSelectedParticipant(participantKey) - } + }, + contextAction: component.contextAction )), environment: {}, containerSize: itemFrame.size diff --git a/submodules/TelegramCallsUI/Sources/VideoChatParticipantVideoComponent.swift b/submodules/TelegramCallsUI/Sources/VideoChatParticipantVideoComponent.swift index 787908ccba..fa08eaca1a 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatParticipantVideoComponent.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatParticipantVideoComponent.swift @@ -205,7 +205,7 @@ final class VideoChatParticipantVideoComponent: Component { super.init(frame: frame) self.addSubview(self.extractedContainerView) - self.targetViewForActivationProgress = self.extractedContainerView + self.targetViewForActivationProgress = self.extractedContainerView.contentView self.extractedContainerView.contentView.addSubview(self.pinchContainerNode.view) self.pinchContainerNode.contentNode.view.addSubview(self.backgroundGradientView) @@ -276,6 +276,8 @@ final class VideoChatParticipantVideoComponent: Component { transition.setPosition(view: self.extractedContainerView, position: CGRect(origin: CGPoint(), size: availableSize).center) transition.setBounds(view: self.extractedContainerView, bounds: CGRect(origin: CGPoint(), size: availableSize)) + transition.setPosition(view: self.extractedContainerView.contentView, position: CGRect(origin: CGPoint(), size: availableSize).center) + transition.setBounds(view: self.extractedContainerView.contentView, bounds: CGRect(origin: CGPoint(), size: availableSize)) self.extractedContainerView.contentRect = CGRect(origin: CGPoint(), size: availableSize) transition.setFrame(view: self.pinchContainerNode.contentNode.view, frame: CGRect(origin: CGPoint(), size: availableSize)) diff --git a/submodules/TelegramCallsUI/Sources/VideoChatParticipantsComponent.swift b/submodules/TelegramCallsUI/Sources/VideoChatParticipantsComponent.swift index e4d904eb72..49b478b9d3 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatParticipantsComponent.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatParticipantsComponent.swift @@ -648,6 +648,8 @@ final class VideoChatParticipantsComponent: Component { private var isPinchToZoomActive: Bool = false + private var stopRequestingNonCentralVideo: Bool = false + private var stopRequestingNonCentralVideoTimer: Foundation.Timer? private var currentLoadMoreToken: String? private var mainScrollViewEventCycleState: EventCycleState? @@ -722,6 +724,10 @@ final class VideoChatParticipantsComponent: Component { fatalError("init(coder:) has not been implemented") } + deinit { + self.stopRequestingNonCentralVideoTimer?.invalidate() + } + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { guard let component = self.component else { return nil @@ -1381,6 +1387,12 @@ final class VideoChatParticipantsComponent: Component { return } component.updateMainParticipant(VideoParticipantKey(id: key.id, isPresentation: key.isPresentation), nil) + }, + contextAction: { [weak self] peer, sourceView, gesture in + guard let self, let component = self.component else { + return + } + component.openParticipantContextMenu(peer.id, sourceView, gesture) } )), environment: {}, @@ -1599,9 +1611,33 @@ final class VideoChatParticipantsComponent: Component { self.isUpdating = false } + let previousComponent = self.component self.component = component self.state = state + if let expandedVideoState = component.expandedVideoState, expandedVideoState.isUIHidden { + if self.stopRequestingNonCentralVideoTimer == nil || previousComponent?.expandedVideoState != expandedVideoState { + self.stopRequestingNonCentralVideoTimer?.invalidate() + + self.stopRequestingNonCentralVideoTimer = Foundation.Timer.scheduledTimer(withTimeInterval: 5.0, repeats: false, block: { [weak self] _ in + guard let self else { + return + } + self.stopRequestingNonCentralVideo = true + self.stopRequestingNonCentralVideoTimer = nil + if !self.isUpdating { + self.state?.updated(transition: .immediate, isLocal: true) + } + }) + } + } else { + self.stopRequestingNonCentralVideo = false + if let stopRequestingNonCentralVideoTimer = self.stopRequestingNonCentralVideoTimer { + self.stopRequestingNonCentralVideoTimer = nil + stopRequestingNonCentralVideoTimer.invalidate() + } + } + let measureListItemSize = self.measureListItemView.update( transition: .immediate, component: AnyComponent(PeerListItemComponent( @@ -1749,13 +1785,19 @@ final class VideoChatParticipantsComponent: Component { } if let videoChannel = participant.requestedVideoChannel(minQuality: .thumbnail, maxQuality: maxVideoQuality) { - if !requestedVideo.contains(videoChannel) { - requestedVideo.append(videoChannel) + if self.stopRequestingNonCentralVideo && component.expandedVideoState != nil && maxVideoQuality != .full { + } else { + if !requestedVideo.contains(videoChannel) { + requestedVideo.append(videoChannel) + } } } if let videoChannel = participant.requestedPresentationVideoChannel(minQuality: .thumbnail, maxQuality: maxPresentationQuality) { - if !requestedVideo.contains(videoChannel) { - requestedVideo.append(videoChannel) + if self.stopRequestingNonCentralVideo && component.expandedVideoState != nil && maxPresentationQuality != .full { + } else { + if !requestedVideo.contains(videoChannel) { + requestedVideo.append(videoChannel) + } } } }