mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-23 22:55:00 +00:00
Video Chats Improvements
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user