Video Chats Improvements

This commit is contained in:
Ilya Laktyushin
2021-04-21 03:07:13 +03:00
parent d9f3dba292
commit 6cbe2f8f35
10 changed files with 840 additions and 260 deletions

View File

@@ -77,6 +77,11 @@ final class GroupVideoNode: ASDisplayNode {
var tapped: (() -> Void)?
private let readyPromise = ValuePromise(false)
var ready: Signal<Bool, NoError> {
return self.readyPromise.get()
}
init(videoView: PresentationCallVideoView) {
self.videoViewContainer = UIView()
self.videoView = videoView
@@ -95,6 +100,7 @@ final class GroupVideoNode: ASDisplayNode {
guard let strongSelf = self else {
return
}
strongSelf.readyPromise.set(true)
if let (size, isLandscape) = strongSelf.validLayout {
strongSelf.updateLayout(size: size, isLandscape: isLandscape, transition: .immediate)
}
@@ -170,8 +176,13 @@ final class GroupVideoNode: ASDisplayNode {
rotatedVideoFrame.size.width = ceil(rotatedVideoFrame.size.width)
rotatedVideoFrame.size.height = ceil(rotatedVideoFrame.size.height)
var videoSize = CGSize(width: 1203, height: 677)
transition.updatePosition(layer: self.videoView.view.layer, position: rotatedVideoFrame.center)
transition.updateBounds(layer: self.videoView.view.layer, bounds: CGRect(origin: CGPoint(), size: rotatedVideoFrame.size))
transition.updateBounds(layer: self.videoView.view.layer, bounds: CGRect(origin: CGPoint(), size: videoSize))
let scale = rotatedVideoFrame.width / videoSize.width
transition.updateTransformScale(layer: self.videoView.view.layer, scale: scale)
let transition: ContainedViewLayoutTransition = .immediate
transition.updateTransformRotation(view: self.videoView.view, angle: angle)
@@ -187,6 +198,7 @@ private final class MainVideoContainerNode: ASDisplayNode {
private let topCornersNode: ASImageNode
private let bottomCornersNode: ASImageNode
private let bottomEdgeNode: ASDisplayNode
private let fadeNode: ASImageNode
private var currentPeer: (PeerId, UInt32)?
private var validLayout: (CGSize, CGFloat, Bool)?
@@ -208,12 +220,27 @@ private final class MainVideoContainerNode: ASDisplayNode {
self.bottomEdgeNode = ASDisplayNode()
self.bottomEdgeNode.backgroundColor = UIColor(rgb: 0x000000)
self.fadeNode = ASImageNode()
self.fadeNode.displaysAsynchronously = false
self.fadeNode.displayWithoutProcessing = true
self.fadeNode.contentMode = .scaleToFill
self.fadeNode.image = generateImage(CGSize(width: 1.0, height: 50.0), rotatedContext: { size, context in
let bounds = CGRect(origin: CGPoint(), size: size)
context.clear(bounds)
let colorsArray = [UIColor(rgb: 0x000000, alpha: 0.0).cgColor, UIColor(rgb: 0x000000, alpha: 0.7).cgColor] as CFArray
var locations: [CGFloat] = [0.0, 1.0]
let gradient = CGGradient(colorsSpace: deviceColorSpace, colors: colorsArray, locations: &locations)!
context.drawLinearGradient(gradient, start: CGPoint(), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions())
})
super.init()
self.clipsToBounds = true
self.backgroundColor = UIColor(rgb: 0x1c1c1e)
self.addSubnode(self.topCornersNode)
self.addSubnode(self.fadeNode)
self.addSubnode(self.bottomCornersNode)
self.addSubnode(self.bottomEdgeNode)
}
@@ -295,6 +322,12 @@ private final class MainVideoContainerNode: ASDisplayNode {
transition.updateFrame(node: self.topCornersNode, frame: CGRect(x: sideInset, y: 0.0, width: size.width - sideInset * 2.0, height: 50.0))
transition.updateFrame(node: self.bottomCornersNode, frame: CGRect(x: sideInset, y: size.height - 6.0 - 50.0, width: size.width - sideInset * 2.0, height: 50.0))
transition.updateFrame(node: self.bottomEdgeNode, frame: CGRect(x: sideInset, y: size.height - 6.0, width: size.width - sideInset * 2.0, height: 6.0))
var fadeHeight: CGFloat = 50.0
if size.width < size.height {
fadeHeight = 140.0
}
transition.updateFrame(node: self.fadeNode, frame: CGRect(x: sideInset, y: size.height - 6.0 - fadeHeight, width: size.width - sideInset * 2.0, height: fadeHeight))
}
}
@@ -318,7 +351,7 @@ public final class VoiceChatController: ViewController {
private final class Interaction {
let updateIsMuted: (PeerId, Bool) -> Void
let openPeer: (PeerId) -> Void
let pinPeer: (PeerId, UInt32?) -> Void
let openInvite: () -> Void
let peerContextAction: (PeerEntry, ASDisplayNode, ContextGesture?) -> Void
let setPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void
@@ -331,14 +364,14 @@ public final class VoiceChatController: ViewController {
init(
updateIsMuted: @escaping (PeerId, Bool) -> Void,
openPeer: @escaping (PeerId) -> Void,
pinPeer: @escaping (PeerId, UInt32?) -> Void,
openInvite: @escaping () -> Void,
peerContextAction: @escaping (PeerEntry, ASDisplayNode, ContextGesture?) -> Void,
setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void,
getPeerVideo: @escaping (UInt32, Bool) -> GroupVideoNode?
) {
self.updateIsMuted = updateIsMuted
self.openPeer = openPeer
self.pinPeer = pinPeer
self.openInvite = openInvite
self.peerContextAction = peerContextAction
self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions
@@ -403,6 +436,7 @@ public final class VoiceChatController: ViewController {
var volume: Int32?
var raisedHand: Bool
var displayRaisedHandStatus: Bool
var pinned: Bool
var stableId: PeerId {
return self.peer.id
@@ -448,6 +482,9 @@ public final class VoiceChatController: ViewController {
if lhs.displayRaisedHandStatus != rhs.displayRaisedHandStatus {
return false
}
if lhs.pinned != rhs.pinned {
return false
}
return true
}
@@ -532,7 +569,7 @@ public final class VoiceChatController: ViewController {
}
}
func item(context: AccountContext, presentationData: PresentationData, interaction: Interaction, style: VoiceChatParticipantItem.LayoutStyle) -> ListViewItem {
func item(context: AccountContext, presentationData: PresentationData, interaction: Interaction, style: VoiceChatParticipantItem.LayoutStyle, transparent: Bool) -> ListViewItem {
switch self {
case let .invite(_, _, text, isLink):
return VoiceChatActionItem(presentationData: ItemListPresentationData(presentationData), title: text, icon: .generic(UIImage(bundleImageName: isLink ? "Chat/Context Menu/Link" : "Chat/Context Menu/AddUser")!), action: {
@@ -611,17 +648,23 @@ public final class VoiceChatController: ViewController {
let revealOptions: [VoiceChatParticipantItem.RevealOption] = []
return VoiceChatParticipantItem(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, context: context, peer: peer, ssrc: peerEntry.ssrc, presence: peerEntry.presence, text: text, expandedText: expandedText, icon: icon, style: style, enabled: true, transparent: false, selectable: true, getAudioLevel: { return interaction.getAudioLevel(peer.id) }, getVideo: {
return VoiceChatParticipantItem(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, context: context, peer: peer, ssrc: peerEntry.ssrc, presence: peerEntry.presence, text: text, expandedText: expandedText, icon: icon, style: style, enabled: true, transparent: transparent, pinned: peerEntry.pinned, selectable: true, getAudioLevel: { return interaction.getAudioLevel(peer.id) }, getVideo: {
if let ssrc = peerEntry.ssrc {
return interaction.getPeerVideo(ssrc, style == .tile)
return interaction.getPeerVideo(ssrc, style != .list)
} else {
return nil
}
}, revealOptions: revealOptions, revealed: peerEntry.revealed, setPeerIdWithRevealedOptions: { peerId, fromPeerId in
interaction.setPeerIdWithRevealedOptions(peerId, fromPeerId)
}, action: { node in
interaction.peerContextAction(peerEntry, node, nil)
}, contextAction: nil, getIsExpanded: {
if case .list = style {
interaction.peerContextAction(peerEntry, node, nil)
} else {
interaction.pinPeer(peer.id, peerEntry.ssrc)
}
}, contextAction: style != .list ? { node, gesture in
interaction.peerContextAction(peerEntry, node, gesture)
} : nil, getIsExpanded: {
return interaction.isExpanded
}, getUpdatingAvatar: {
return interaction.updateAvatarPromise.get()
@@ -634,8 +677,8 @@ public final class VoiceChatController: ViewController {
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, interaction: interaction, style: style), directionHint: nil) }
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, interaction: interaction, style: style), directionHint: nil) }
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, interaction: interaction, style: style, transparent: false), directionHint: nil) }
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, interaction: interaction, style: style, transparent: false), directionHint: nil) }
return ListTransition(deletions: deletions, insertions: insertions, updates: updates, isLoading: isLoading, isEmpty: isEmpty, canInvite: canInvite, crossFade: crossFade, count: toEntries.count, animated: animated)
}
@@ -655,6 +698,7 @@ public final class VoiceChatController: ViewController {
private let backgroundNode: ASDisplayNode
private let mainVideoClippingNode: ASDisplayNode
private var mainVideoContainerNode: MainVideoContainerNode?
private var mainParticipantNode: VoiceChatParticipantItemNode
private let listNode: ListView
private let horizontalListNode: ListView
private let topPanelNode: ASDisplayNode
@@ -664,6 +708,7 @@ public final class VoiceChatController: ViewController {
private var optionsButtonIsAvatar = false
private let closeButton: VoiceChatHeaderButton
private let topCornersNode: ASImageNode
private let bottomPanelCoverNode: ASDisplayNode
fileprivate let bottomPanelNode: ASDisplayNode
private let bottomPanelBackgroundNode: ASDisplayNode
private let bottomCornersNode: ASImageNode
@@ -674,6 +719,7 @@ public final class VoiceChatController: ViewController {
fileprivate let actionButton: VoiceChatActionButton
private let leftBorderNode: ASDisplayNode
private let rightBorderNode: ASDisplayNode
private let transitionContainerNode: ASDisplayNode
private var isScheduling = false
private let timerNode: VoiceChatTimerNode
@@ -697,6 +743,11 @@ public final class VoiceChatController: ViewController {
private var isFirstTime = true
private var topInset: CGFloat?
private var animatingInsertion = false
private var animatingExpansion = false
private var animatingAppearance = false
private var panGestureArguments: (topInset: CGFloat, offset: CGFloat)?
private var peer: Peer?
private var currentTitle: String = ""
private var currentTitleIsCustom = false
@@ -762,7 +813,10 @@ public final class VoiceChatController: ViewController {
private var requestedVideoSources = Set<UInt32>()
private var videoNodes: [(PeerId, UInt32, GroupVideoNode)] = []
private var currentDominantSpeakerWithVideo: (PeerId, UInt32)?
private var currentForcedSpeakerWithVideo: (PeerId, UInt32)?
private var effectiveSpeakerWithVideo: (PeerId, UInt32)?
private var updateAvatarDisposable = MetaDisposable()
private let updateAvatarPromise = Promise<(TelegramMediaImageRepresentation, Float)?>(nil)
@@ -809,6 +863,8 @@ public final class VoiceChatController: ViewController {
self.mainVideoContainerNode = MainVideoContainerNode(context: call.accountContext, call: call)
}
self.mainParticipantNode = VoiceChatParticipantItemNode()
self.listNode = ListView()
self.listNode.alpha = self.isScheduling ? 0.0 : 1.0
self.listNode.isUserInteractionEnabled = !self.isScheduling
@@ -854,6 +910,9 @@ public final class VoiceChatController: ViewController {
self.topCornersNode.displayWithoutProcessing = true
self.topCornersNode.image = cornersImage(top: true, bottom: false, dark: false)
self.bottomPanelCoverNode = ASDisplayNode()
self.bottomPanelCoverNode.backgroundColor = fullscreenBackgroundColor
self.bottomPanelNode = ASDisplayNode()
self.bottomPanelNode.clipsToBounds = false
@@ -890,6 +949,10 @@ public final class VoiceChatController: ViewController {
self.rightBorderNode.isUserInteractionEnabled = false
self.rightBorderNode.clipsToBounds = false
self.transitionContainerNode = ASDisplayNode()
self.transitionContainerNode.clipsToBounds = true
self.transitionContainerNode.isUserInteractionEnabled = false
self.scheduleTextNode = ImmediateTextNode()
self.scheduleTextNode.isHidden = !self.isScheduling
self.scheduleTextNode.isUserInteractionEnabled = false
@@ -945,27 +1008,33 @@ public final class VoiceChatController: ViewController {
self.itemInteraction = Interaction(updateIsMuted: { [weak self] peerId, isMuted in
let _ = self?.call.updateMuteState(peerId: peerId, isMuted: isMuted)
}, openPeer: { [weak self] peerId in
}, pinPeer: { [weak self] peerId, source in
if let strongSelf = self {
for entry in strongSelf.currentEntries {
switch entry {
case let .peer(peer):
if peer.peer.id == peerId {
if let source = peer.ssrc {
if strongSelf.currentDominantSpeakerWithVideo?.0 != peerId || strongSelf.currentDominantSpeakerWithVideo?.1 != source {
strongSelf.currentDominantSpeakerWithVideo = (peerId, source)
strongSelf.call.setFullSizeVideo(peerId: peerId)
strongSelf.mainVideoContainerNode?.updatePeer(peer: (peerId: peerId, source: source), waitForFullSize: false)
} else {
strongSelf.currentDominantSpeakerWithVideo = nil
strongSelf.call.setFullSizeVideo(peerId: nil)
strongSelf.mainVideoContainerNode?.updatePeer(peer: nil, waitForFullSize: false)
}
}
}
default:
break
if peerId != strongSelf.currentForcedSpeakerWithVideo?.0, let source = source {
strongSelf.currentForcedSpeakerWithVideo = (peerId, source)
} else {
strongSelf.currentForcedSpeakerWithVideo = nil
}
strongSelf.updatePinnedParticipant()
var updateLayout = false
if strongSelf.effectiveSpeakerWithVideo != nil && !strongSelf.isExpanded {
strongSelf.isExpanded = true
updateLayout = true
} else if strongSelf.effectiveSpeakerWithVideo == nil && strongSelf.isExpanded {
strongSelf.isExpanded = false
updateLayout = true
}
if updateLayout {
strongSelf.updateIsFullscreen(strongSelf.isExpanded)
strongSelf.animatingExpansion = true
if let (layout, navigationHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .easeInOut))
}
strongSelf.updateFloatingHeaderOffset(offset: strongSelf.currentContentOffset ?? 0.0, transition: .animated(duration: 0.3, curve: .easeInOut), completion: {
strongSelf.animatingExpansion = false
})
}
}
}, openInvite: { [weak self] in
@@ -1277,18 +1346,20 @@ public final class VoiceChatController: ViewController {
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Tip"), color: theme.actionSheet.primaryTextColor)
}), true))
}
if strongSelf.context.sharedContext.immediateExperimentalUISettings.demoVideoChats {
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_PinVideo, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Pin"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in
guard let strongSelf = self else {
return
}
strongSelf.itemInteraction?.openPeer(peer.id)
f(.default)
})))
for (peerId, _, _) in strongSelf.videoNodes {
if peerId == peer.id {
items.append(.action(ContextMenuActionItem(text: strongSelf.currentForcedSpeakerWithVideo?.0 == peer.id ? strongSelf.presentationData.strings.VoiceChat_UnpinVideo : strongSelf.presentationData.strings.VoiceChat_PinVideo, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Pin"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in
guard let strongSelf = self else {
return
}
strongSelf.itemInteraction?.pinPeer(peer.id, entry.ssrc)
f(.default)
})))
break
}
}
if peer.id == strongSelf.callState?.myPeerId {
@@ -1588,16 +1659,18 @@ public final class VoiceChatController: ViewController {
if let mainVideoContainer = self.mainVideoContainerNode {
self.contentContainer.addSubnode(self.mainVideoClippingNode)
self.mainVideoClippingNode.addSubnode(mainVideoContainer)
self.mainVideoClippingNode.addSubnode(self.mainParticipantNode)
}
self.contentContainer.addSubnode(self.listNode)
self.contentContainer.addSubnode(self.topPanelNode)
self.contentContainer.addSubnode(self.leftBorderNode)
self.contentContainer.addSubnode(self.rightBorderNode)
self.contentContainer.addSubnode(self.bottomPanelCoverNode)
self.contentContainer.addSubnode(self.bottomPanelNode)
self.contentContainer.addSubnode(self.timerNode)
self.contentContainer.addSubnode(self.scheduleTextNode)
self.contentContainer.addSubnode(self.horizontalListNode)
self.addSubnode(self.transitionContainerNode)
let invitedPeers: Signal<[Peer], NoError> = self.call.invitedPeers
|> mapToSignal { ids -> Signal<[Peer], NoError> in
@@ -1814,6 +1887,16 @@ public final class VoiceChatController: ViewController {
}
}
self.memberEventsDisposable.set((self.call.memberEvents
|> deliverOnMainQueue).start(next: { [weak self] event in
guard let strongSelf = self else {
return
}
if event.joined {
strongSelf.presentUndoOverlay(content: .invitedToVoiceChat(context: strongSelf.context, peer: event.peer, text: strongSelf.presentationData.strings.VoiceChat_PeerJoinedText(event.peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)).0), action: { _ in return false })
}
}))
self.reconnectedAsEventsDisposable.set((self.call.reconnectedAsEvents
|> deliverOnMainQueue).start(next: { [weak self] peer in
guard let strongSelf = self else {
@@ -1840,7 +1923,7 @@ public final class VoiceChatController: ViewController {
}
let videoNode = GroupVideoNode(videoView: videoView)
strongSelf.videoNodes.append((peerId, source, videoNode))
//strongSelf.addSubnode(videoNode)
if let (layout, navigationHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .immediate)
@@ -1850,8 +1933,8 @@ public final class VoiceChatController: ViewController {
case let .peer(peerEntry):
if peerEntry.ssrc == source {
let presentationData = strongSelf.presentationData.withUpdated(theme: strongSelf.darkTheme)
strongSelf.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [ListViewUpdateItem(index: i, previousIndex: i, item: entry.item(context: strongSelf.context, presentationData: presentationData, interaction: strongSelf.itemInteraction!, style: .list), directionHint: nil)], options: [.Synchronous], updateOpaqueState: nil)
strongSelf.horizontalListNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [ListViewUpdateItem(index: i, previousIndex: i, item: entry.item(context: strongSelf.context, presentationData: presentationData, interaction: strongSelf.itemInteraction!, style: .tile), directionHint: nil)], options: [.Synchronous], updateOpaqueState: nil)
strongSelf.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [ListViewUpdateItem(index: i, previousIndex: i, item: entry.item(context: strongSelf.context, presentationData: presentationData, interaction: strongSelf.itemInteraction!, style: .list, transparent: false), directionHint: nil)], options: [.Synchronous], updateOpaqueState: nil)
strongSelf.horizontalListNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [ListViewUpdateItem(index: i, previousIndex: i, item: entry.item(context: strongSelf.context, presentationData: presentationData, interaction: strongSelf.itemInteraction!, style: .tile(isLandscape: strongSelf.isLandscape), transparent: false), directionHint: nil)], options: [.Synchronous], updateOpaqueState: nil)
break loop
}
default:
@@ -1876,25 +1959,27 @@ public final class VoiceChatController: ViewController {
case let .peer(peerEntry):
if peerEntry.ssrc == ssrc {
let presentationData = strongSelf.presentationData.withUpdated(theme: strongSelf.darkTheme)
strongSelf.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [ListViewUpdateItem(index: i, previousIndex: i, item: entry.item(context: strongSelf.context, presentationData: presentationData, interaction: strongSelf.itemInteraction!, style: .list), directionHint: nil)], options: [.Synchronous], updateOpaqueState: nil)
strongSelf.horizontalListNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [ListViewUpdateItem(index: i, previousIndex: i, item: entry.item(context: strongSelf.context, presentationData: presentationData, interaction: strongSelf.itemInteraction!, style: .tile), directionHint: nil)], options: [.Synchronous], updateOpaqueState: nil)
strongSelf.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [ListViewUpdateItem(index: i, previousIndex: i, item: entry.item(context: strongSelf.context, presentationData: presentationData, interaction: strongSelf.itemInteraction!, style: .list, transparent: false), directionHint: nil)], options: [.Synchronous], updateOpaqueState: nil)
strongSelf.horizontalListNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [ListViewUpdateItem(index: i, previousIndex: i, item: entry.item(context: strongSelf.context, presentationData: presentationData, interaction: strongSelf.itemInteraction!, style: .tile(isLandscape: strongSelf.isLandscape), transparent: false), directionHint: nil)], options: [.Synchronous], updateOpaqueState: nil)
break loop
}
default:
break
}
}
//strongSelf.videoNodes[i].2.removeFromSupernode()
updated = true
}
}
if let (_, source) = strongSelf.currentDominantSpeakerWithVideo {
if let (peerId, source) = strongSelf.effectiveSpeakerWithVideo {
if !validSources.contains(source) {
strongSelf.currentDominantSpeakerWithVideo = nil
strongSelf.call.setFullSizeVideo(peerId: nil)
strongSelf.mainVideoContainerNode?.updatePeer(peer: nil, waitForFullSize: false)
if peerId == strongSelf.currentForcedSpeakerWithVideo?.0 {
strongSelf.currentForcedSpeakerWithVideo = nil
}
if peerId == strongSelf.currentDominantSpeakerWithVideo?.0 {
strongSelf.currentDominantSpeakerWithVideo = nil
}
strongSelf.updatePinnedParticipant()
}
}
@@ -1943,10 +2028,7 @@ public final class VoiceChatController: ViewController {
case .default:
strongSelf.displayMode = .fullscreen(controlsHidden: false)
case let .fullscreen(controlsHidden):
if true {
strongSelf.displayMode = .fullscreen(controlsHidden: !controlsHidden)
}
else if controlsHidden && !isLandscape {
if controlsHidden && !isLandscape {
strongSelf.displayMode = .default
} else {
strongSelf.displayMode = .fullscreen(controlsHidden: true)
@@ -1956,35 +2038,110 @@ public final class VoiceChatController: ViewController {
if case .default = effectiveDisplayMode, case .fullscreen = strongSelf.displayMode {
strongSelf.horizontalListNode.isHidden = false
var minimalVisiblePeerid: (PeerId, CGFloat)?
var verticalItemNodes: [PeerId: VoiceChatParticipantItemNode] = [:]
strongSelf.listNode.forEachItemNode { itemNode in
if let itemNode = itemNode as? VoiceChatParticipantItemNode, let item = itemNode.item {
let convertedFrame = itemNode.view.convert(itemNode.bounds, to: strongSelf.transitionContainerNode.view)
if let (_, y) = minimalVisiblePeerid {
if convertedFrame.minY >= 0.0 && convertedFrame.minY < y {
minimalVisiblePeerid = (item.peer.id, convertedFrame.minY)
}
} else {
if convertedFrame.minY >= 0.0 {
minimalVisiblePeerid = (item.peer.id, convertedFrame.minY)
}
}
verticalItemNodes[item.peer.id] = itemNode
}
}
strongSelf.horizontalListNode.forEachVisibleItemNode { itemNode in
if let itemNode = itemNode as? VoiceChatParticipantItemNode, let item = itemNode.item, let otherItemNode = verticalItemNodes[item.peer.id] {
itemNode.transitionIn(from: otherItemNode, containerNode: strongSelf)
strongSelf.animatingExpansion = true
let completion = {
strongSelf.horizontalListNode.forEachItemNode { itemNode in
if let itemNode = itemNode as? VoiceChatParticipantItemNode, let item = itemNode.item, let otherItemNode = verticalItemNodes[item.peer.id] {
itemNode.transitionIn(from: otherItemNode, containerNode: strongSelf)
}
}
strongSelf.updateIsFullscreen(strongSelf.isFullscreen, force: true)
if let (layout, navigationHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .easeInOut))
strongSelf.updateFloatingHeaderOffset(offset: strongSelf.currentContentOffset ?? 0.0, transition: .animated(duration: 0.3, curve: .easeInOut))
}
}
if false, let (peerId, _) = minimalVisiblePeerid {
var index = 0
for item in strongSelf.currentEntries {
if case let .peer(entry) = item, entry.peer.id == peerId {
break
} else {
index += 1
}
}
strongSelf.horizontalListNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: index, position: .top(0.0), animated: false, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in
completion()
})
} else {
completion()
}
} else if case .fullscreen = effectiveDisplayMode, case .default = strongSelf.displayMode {
var minimalVisiblePeerid: (PeerId, CGFloat)?
var horizontalItemNodes: [PeerId: VoiceChatParticipantItemNode] = [:]
strongSelf.horizontalListNode.forEachItemNode { itemNode in
if let itemNode = itemNode as? VoiceChatParticipantItemNode, let item = itemNode.item {
let convertedFrame = itemNode.view.convert(itemNode.bounds, to: strongSelf.transitionContainerNode.view)
if let (_, x) = minimalVisiblePeerid {
if convertedFrame.minX >= 0.0 && convertedFrame.minX < x {
minimalVisiblePeerid = (item.peer.id, convertedFrame.minX)
}
} else if convertedFrame.minX >= 0.0 {
minimalVisiblePeerid = (item.peer.id, convertedFrame.minX)
}
horizontalItemNodes[item.peer.id] = itemNode
}
}
strongSelf.listNode.forEachVisibleItemNode { itemNode in
if let itemNode = itemNode as? VoiceChatParticipantItemNode, let item = itemNode.item, let otherItemNode = horizontalItemNodes[item.peer.id] {
itemNode.transitionIn(from: otherItemNode, containerNode: strongSelf)
strongSelf.animatingExpansion = true
let completion = {
strongSelf.listNode.forEachItemNode { itemNode in
if let itemNode = itemNode as? VoiceChatParticipantItemNode, let item = itemNode.item, let otherItemNode = horizontalItemNodes[item.peer.id] {
itemNode.transitionIn(from: otherItemNode, containerNode: strongSelf.transitionContainerNode)
}
}
strongSelf.updateIsFullscreen(strongSelf.isFullscreen, force: true)
if let (layout, navigationHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .easeInOut))
strongSelf.updateFloatingHeaderOffset(offset: strongSelf.currentContentOffset ?? 0.0, transition: .animated(duration: 0.3, curve: .easeInOut))
}
}
}
if let (layout, navigationHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .easeInOut))
if false, let (peerId, _) = minimalVisiblePeerid {
var index = 0
for item in strongSelf.currentEntries {
if case let .peer(entry) = item, entry.peer.id == peerId {
break
} else {
index += 1
}
}
strongSelf.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: index, position: .top(0.0), animated: false, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in
completion()
})
} else {
completion()
}
} else if case .fullscreen = strongSelf.displayMode {
strongSelf.updateIsFullscreen(strongSelf.isFullscreen, force: true)
if let (layout, navigationHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .easeInOut))
strongSelf.updateFloatingHeaderOffset(offset: strongSelf.currentContentOffset ?? 0.0, transition: .animated(duration: 0.3, curve: .easeInOut))
}
}
}
}
@@ -2993,6 +3150,14 @@ public final class VoiceChatController: ViewController {
self.call.switchVideoCamera()
}
private var isLandscape: Bool {
if let (layout, _) = self.validLayout, layout.size.width > layout.size.height, case .compact = layout.metrics.widthClass {
return true
} else {
return false
}
}
private var effectiveBottomAreaHeight: CGFloat {
switch self.displayMode {
case .default:
@@ -3001,7 +3166,7 @@ public final class VoiceChatController: ViewController {
return controlsHidden ? 0.0 : fullscreenBottomAreaHeight
}
}
private var bringVideoToBackOnCompletion = false
private func updateFloatingHeaderOffset(offset: CGFloat, transition: ContainedViewLayoutTransition, completion: (() -> Void)? = nil) {
guard let (layout, _) = self.validLayout else {
@@ -3073,12 +3238,14 @@ public final class VoiceChatController: ViewController {
let videoClippingFrame: CGRect
let videoContainerFrame: CGRect
let videoInset: CGFloat
let videoHeight: CGFloat
var isFullscreen = false
if isLandscape {
videoInset = 0.0
videoClippingFrame = CGRect(x: layout.safeInsets.left, y: 0.0, width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - fullscreenBottomAreaHeight, height: layout.size.height + 6.0)
videoContainerFrame = CGRect(origin: CGPoint(), size: videoClippingFrame.size)
videoHeight = videoClippingFrame.height
} else {
let videoHeight: CGFloat
let videoY: CGFloat
switch effectiveDisplayMode {
case .default:
@@ -3089,16 +3256,34 @@ public final class VoiceChatController: ViewController {
videoInset = 0.0
videoHeight = layout.size.height - (layout.statusBarHeight ?? 0.0) - layout.intrinsicInsets.bottom - fullscreenBottomAreaHeight - 6.0
videoY = layout.statusBarHeight ?? 20.0
isFullscreen = true
}
videoClippingFrame = CGRect(origin: CGPoint(x: videoInset, y: videoY), size: CGSize(width: layout.size.width - videoInset * 2.0, height: self.isFullscreen ? videoHeight : 0.0))
videoContainerFrame = CGRect(origin: CGPoint(x: -videoInset, y: 0.0), size: CGSize(width: layout.size.width, height: videoHeight))
}
let topEdgeY = topPanelFrame.maxY + min(mainVideoHeight, layout.size.width)
let bottomEdgeY = isFullscreen ? layout.size.height : layout.size.height - bottomAreaHeight - layout.intrinsicInsets.bottom
transition.updateFrame(node: self.transitionContainerNode, frame: CGRect(x: sideInset, y: topEdgeY, width: layout.size.width - sideInset * 2.0, height: max(0.0, bottomEdgeY - topEdgeY)))
let offset: CGFloat
if case let .fullscreen(controlsHidden) = effectiveDisplayMode, !isLandscape {
offset = controlsHidden ? 66.0 : 140.0
} else {
offset = 56.0 + 6.0
}
transition.updateFrame(node: self.mainParticipantNode, frame: CGRect(x: 0.0, y: videoClippingFrame.height - offset, width: videoClippingFrame.width, height: 56.0))
transition.updateFrame(node: self.mainVideoClippingNode, frame: videoClippingFrame)
transition.updateFrame(node: mainVideoContainer, frame: videoContainerFrame, completion: { [weak self] _ in
if let strongSelf = self, strongSelf.bringVideoToBackOnCompletion {
strongSelf.bringVideoToBackOnCompletion = false
strongSelf.contentContainer.insertSubnode(strongSelf.mainVideoClippingNode, belowSubnode: strongSelf.horizontalListNode)
if let strongSelf = self {
strongSelf.animatingExpansion = false
if strongSelf.bringVideoToBackOnCompletion {
strongSelf.horizontalListNode.isHidden = true
strongSelf.bringVideoToBackOnCompletion = false
strongSelf.contentContainer.insertSubnode(strongSelf.mainVideoClippingNode, belowSubnode: strongSelf.horizontalListNode)
}
}
})
mainVideoContainer.update(size: videoContainerFrame.size, sideInset: videoInset, isLandscape: isLandscape, transition: transition)
@@ -3145,8 +3330,9 @@ public final class VoiceChatController: ViewController {
let listMaxY = listTopInset + listSize.height
let bottomOffset: CGFloat = min(0.0, bottomEdge - listMaxY)
let bottomDelta = self.effectiveBottomAreaHeight - bottomAreaHeight
let bottomCornersFrame = CGRect(origin: CGPoint(x: sideInset, y: -50.0 + bottomOffset), size: CGSize(width: size.width - sideInset * 2.0, height: 50.0))
let bottomCornersFrame = CGRect(origin: CGPoint(x: sideInset, y: -50.0 + bottomOffset + bottomDelta), size: CGSize(width: size.width - sideInset * 2.0, height: 50.0))
let previousBottomCornersFrame = self.bottomCornersNode.frame
if !bottomCornersFrame.equalTo(previousBottomCornersFrame) {
self.bottomCornersNode.frame = bottomCornersFrame
@@ -3185,9 +3371,21 @@ public final class VoiceChatController: ViewController {
topEdgeFrame = CGRect(x: 0.0, y: 0.0, width: size.width, height: topPanelHeight)
}
var isScheduled = false
if self.isScheduling || self.callState?.scheduleTimestamp != nil {
isScheduled = true
var effectiveDisplayMode = self.displayMode
if case .compact = layout.metrics.widthClass, layout.size.width > layout.size.height {
if case .fullscreen = effectiveDisplayMode {
} else {
effectiveDisplayMode = .fullscreen(controlsHidden: false)
}
}
let backgroundColor: UIColor
if case .fullscreen = effectiveDisplayMode {
backgroundColor = fullscreenBackgroundColor
} else if self.isScheduling || self.callState?.scheduleTimestamp != nil {
backgroundColor = panelBackgroundColor
} else {
backgroundColor = isFullscreen ? panelBackgroundColor : secondaryPanelBackgroundColor
}
let transition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .linear)
@@ -3195,7 +3393,7 @@ public final class VoiceChatController: ViewController {
transition.updateCornerRadius(node: self.topPanelEdgeNode, cornerRadius: isFullscreen ? layout.deviceMetrics.screenCornerRadius - 0.5 : 12.0)
transition.updateBackgroundColor(node: self.topPanelBackgroundNode, color: isFullscreen ? fullscreenBackgroundColor : panelBackgroundColor)
transition.updateBackgroundColor(node: self.topPanelEdgeNode, color: isFullscreen ? fullscreenBackgroundColor : panelBackgroundColor)
transition.updateBackgroundColor(node: self.backgroundNode, color: isFullscreen || isScheduled ? panelBackgroundColor : secondaryPanelBackgroundColor)
transition.updateBackgroundColor(node: self.backgroundNode, color: backgroundColor)
transition.updateBackgroundColor(node: self.bottomPanelBackgroundNode, color: isFullscreen ? fullscreenBackgroundColor : panelBackgroundColor)
transition.updateBackgroundColor(node: self.leftBorderNode, color: isFullscreen ? fullscreenBackgroundColor : panelBackgroundColor)
transition.updateBackgroundColor(node: self.rightBorderNode, color: isFullscreen ? fullscreenBackgroundColor : panelBackgroundColor)
@@ -3399,7 +3597,7 @@ public final class VoiceChatController: ViewController {
effectiveDisplayMode = .fullscreen(controlsHidden: false)
}
}
if let videoIndex = self.contentContainer.subnodes?.firstIndex(where: { $0 === self.mainVideoClippingNode }), let listIndex = self.contentContainer.subnodes?.firstIndex(where: { $0 === self.listNode }) {
switch effectiveDisplayMode {
case .default:
@@ -3481,12 +3679,21 @@ public final class VoiceChatController: ViewController {
self.horizontalListNode.bounds = CGRect(x: 0.0, y: 0.0, width: horizontalListHeight, height: layout.size.width - layout.safeInsets.left - layout.safeInsets.right)
let horizontalListY = isLandscape ? layout.size.height - layout.intrinsicInsets.bottom - 42.0 : layout.size.height - min(bottomPanelHeight, fullscreenBottomAreaHeight + layout.intrinsicInsets.bottom) - 42.0
transition.updatePosition(node: self.horizontalListNode, position: CGPoint(x: layout.safeInsets.left + layout.size.width / 2.0, y: horizontalListY))
if isLandscape {
transition.updatePosition(node: self.horizontalListNode, position: CGPoint(x: layout.safeInsets.left + layout.size.width / 2.0, y: horizontalListY))
self.horizontalListNode.transform = CATransform3DMakeRotation(0.0, 0.0, 0.0, 1.0)
} else {
transition.updatePosition(node: self.horizontalListNode, position: CGPoint(x: layout.safeInsets.left + layout.size.width / 2.0, y: horizontalListY))
self.horizontalListNode.transform = CATransform3DMakeRotation(-CGFloat(CGFloat.pi / 2.0), 0.0, 0.0, 1.0)
}
self.horizontalListNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: CGSize(width: horizontalListHeight, height: layout.size.width), insets: UIEdgeInsets(top: 16.0, left: 0.0, bottom: 16.0, right: 0.0), duration: duration, curve: curve), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
transition.updateFrame(node: self.topCornersNode, frame: CGRect(origin: CGPoint(x: sideInset, y: topCornersY), size: CGSize(width: size.width - sideInset * 2.0, height: 50.0)))
var bottomPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - bottomPanelHeight), size: CGSize(width: size.width, height: bottomPanelHeight))
let bottomPanelCoverHeight = bottomAreaHeight + layout.intrinsicInsets.bottom
let bottomPanelCoverFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - bottomPanelCoverHeight), size: CGSize(width: size.width, height: bottomPanelCoverHeight))
if isLandscape {
transition.updateAlpha(node: self.closeButton, alpha: 0.0)
transition.updateAlpha(node: self.optionsButton, alpha: 0.0)
@@ -3497,6 +3704,7 @@ public final class VoiceChatController: ViewController {
transition.updateAlpha(node: self.optionsButton, alpha: self.optionsButton.isUserInteractionEnabled ? 1.0 : 0.0)
transition.updateAlpha(node: self.titleNode, alpha: 1.0)
}
transition.updateFrame(node: self.bottomPanelCoverNode, frame: bottomPanelCoverFrame)
transition.updateFrame(node: self.bottomPanelNode, frame: bottomPanelFrame)
if let pickerView = self.pickerView {
@@ -3663,23 +3871,6 @@ public final class VoiceChatController: ViewController {
self.updateButtons(animated: !isFirstTime)
/*var currentVideoOrigin = CGPoint(x: 4.0, y: (layout.statusBarHeight ?? 0.0) + 4.0)
for (_, _, videoNode) in self.videoNodes {
let videoSize = CGSize(width: 300.0, height: 500.0)
if currentVideoOrigin.x + videoSize.width > layout.size.width {
currentVideoOrigin.x = 0.0
currentVideoOrigin.y += videoSize.height
}
videoNode.frame = CGRect(origin: currentVideoOrigin, size: videoSize)
videoNode.updateLayout(size: videoSize, transition: .immediate)
if videoNode.supernode == nil {
self.contentContainer.addSubnode(videoNode)
}
currentVideoOrigin.x += videoSize.width + 4.0
}*/
if self.audioButton.supernode === self.bottomPanelNode {
transition.updateFrame(node: self.cameraButton, frame: firstButtonFrame)
transition.updateFrame(node: self.switchCameraButton, frame: firstButtonFrame)
@@ -3926,6 +4117,12 @@ public final class VoiceChatController: ViewController {
entries.append(.invite(self.presentationData.theme, self.presentationData.strings, inviteIsLink ? self.presentationData.strings.VoiceChat_Share : self.presentationData.strings.VoiceChat_InviteMember, inviteIsLink))
}
if let _ = self.effectiveSpeakerWithVideo {
index += 1
}
var pinnedEntry: ListEntry?
for member in callMembers.0 {
if processedPeerIds.contains(member.peer.id) {
continue
@@ -3977,8 +4174,8 @@ public final class VoiceChatController: ViewController {
if member.peer.id == self.callState?.myPeerId, let user = memberPeer as? TelegramUser, let photo = self.currentUpdatingAvatar {
memberPeer = user.withUpdatedPhoto([photo])
}
entries.append(.peer(PeerEntry(
let entry: ListEntry = .peer(PeerEntry(
peer: memberPeer,
about: member.about,
isMyPeer: self.callState?.myPeerId == member.peer.id,
@@ -3990,11 +4187,31 @@ public final class VoiceChatController: ViewController {
canManageCall: self.callState?.canManageCall ?? false,
volume: member.volume,
raisedHand: member.hasRaiseHand,
displayRaisedHandStatus: self.displayedRaisedHands.contains(member.peer.id)
)))
displayRaisedHandStatus: self.displayedRaisedHands.contains(member.peer.id),
pinned: memberPeer.id == self.effectiveSpeakerWithVideo?.0
))
entries.append(entry)
index += 1
if memberPeer.id == self.effectiveSpeakerWithVideo?.0 {
pinnedEntry = .peer(PeerEntry(
peer: memberPeer,
about: member.about,
isMyPeer: self.callState?.myPeerId == member.peer.id,
ssrc: member.ssrc,
presence: nil,
activityTimestamp: Int32.max - 1 - index,
state: memberState,
muteState: memberMuteState,
canManageCall: self.callState?.canManageCall ?? false,
volume: member.volume,
raisedHand: member.hasRaiseHand,
displayRaisedHandStatus: self.displayedRaisedHands.contains(member.peer.id),
pinned: true
))
}
}
for peer in invitedPeers {
if processedPeerIds.contains(peer.id) {
continue
@@ -4013,7 +4230,8 @@ public final class VoiceChatController: ViewController {
canManageCall: false,
volume: nil,
raisedHand: false,
displayRaisedHandStatus: false
displayRaisedHandStatus: false,
pinned: false
)))
index += 1
}
@@ -4021,6 +4239,26 @@ public final class VoiceChatController: ViewController {
guard self.didSetDataReady else {
return
}
if let entry = pinnedEntry, let interaction = self.itemInteraction {
self.mainParticipantNode.isHidden = false
let item = entry.item(context: self.context, presentationData: self.presentationData, interaction: interaction, style: .list, transparent: true)
let itemNode = self.mainParticipantNode
item.updateNode(async: { $0() }, node: {
return itemNode
}, params: ListViewItemLayoutParams(width: self.bounds.width, leftInset: 0.0, rightInset: 0.0, availableHeight: self.bounds.height), previousItem: nil, nextItem: nil, animation: .Crossfade, completion: { (layout, apply) in
// let nodeFrame = CGRect(origin: itemNode.frame.origin, size: CGSize(width: width, height: layout.size.height))
//
itemNode.contentSize = layout.contentSize
itemNode.insets = layout.insets
// itemNode.frame = nodeFrame
itemNode.isUserInteractionEnabled = false
apply(ListViewItemApply(isOnScreen: true))
})
} else {
self.mainParticipantNode.isHidden = true
}
let previousEntries = self.currentEntries
self.currentEntries = entries
@@ -4033,6 +4271,9 @@ public final class VoiceChatController: ViewController {
if lhsPeer.isMyPeer != rhsPeer.isMyPeer {
allEqual = false
break
} else if lhsPeer.pinned || rhsPeer.pinned {
allEqual = false
break
}
} else {
allEqual = false
@@ -4051,10 +4292,35 @@ public final class VoiceChatController: ViewController {
let transition = preparedTransition(from: previousEntries, to: entries, isLoading: false, isEmpty: false, canInvite: canInvite, crossFade: false, animated: !disableAnimation, context: self.context, presentationData: presentationData, interaction: self.itemInteraction!, style: .list)
self.enqueueTransition(transition)
let horizontalTransition = preparedTransition(from: previousEntries, to: entries, isLoading: false, isEmpty: false, canInvite: canInvite, crossFade: false, animated: !disableAnimation, context: self.context, presentationData: presentationData, interaction: self.itemInteraction!, style: .tile)
let horizontalTransition = preparedTransition(from: previousEntries, to: entries, isLoading: false, isEmpty: false, canInvite: canInvite, crossFade: false, animated: !disableAnimation, context: self.context, presentationData: presentationData, interaction: self.itemInteraction!, style: .tile(isLandscape: self.isLandscape))
self.enqueueHorizontalTransition(horizontalTransition)
}
private func updatePinnedParticipant() {
let effectivePinnedParticipant = self.currentForcedSpeakerWithVideo ?? self.currentDominantSpeakerWithVideo
guard effectivePinnedParticipant?.0 != self.effectiveSpeakerWithVideo?.0 || effectivePinnedParticipant?.1 != self.effectiveSpeakerWithVideo?.1 else {
return
}
if let (peerId, _) = effectivePinnedParticipant {
for entry in self.currentEntries {
switch entry {
case let .peer(peer):
if peer.peer.id == peerId, let source = peer.ssrc {
self.effectiveSpeakerWithVideo = (peerId, source)
self.call.setFullSizeVideo(peerId: peerId)
self.mainVideoContainerNode?.updatePeer(peer: (peerId: peerId, source: source), waitForFullSize: false)
}
default:
break
}
}
} else {
self.effectiveSpeakerWithVideo = nil
}
self.updateMembers(muteState: self.effectiveMuteState, callMembers: self.currentCallMembers ?? ([], nil), invitedPeers: self.currentInvitedPeers ?? [], speakingPeers: self.currentSpeakingPeers ?? Set())
}
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer is UILongPressGestureRecognizer {
return !self.isScheduling
@@ -4087,12 +4353,7 @@ public final class VoiceChatController: ViewController {
self.itemInteraction?.isExpanded = self.isExpanded
}
}
private var animatingInsertion = false
private var animatingExpansion = false
private var animatingAppearance = false
private var panGestureArguments: (topInset: CGFloat, offset: CGFloat)?
@objc func panGesture(_ recognizer: UIPanGestureRecognizer) {
let contentOffset = self.listNode.visibleContentOffset()
let isScheduling = self.isScheduling || self.callState?.scheduleTimestamp != nil
@@ -4276,15 +4537,6 @@ public final class VoiceChatController: ViewController {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let result = super.hitTest(point, with: event)
if let result = result {
for (_, _, videoNode) in self.videoNodes {
if videoNode.view === result || result.isDescendant(of: videoNode.view) {
return result
}
}
}
if result === self.topPanelNode.view {
return self.view
}