mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-08 19:10:53 +00:00
Video Chats Improvements
This commit is contained in:
parent
d9f3dba292
commit
6cbe2f8f35
@ -289,10 +289,16 @@ public struct PresentationGroupCallMembers: Equatable {
|
||||
|
||||
public final class PresentationGroupCallMemberEvent {
|
||||
public let peer: Peer
|
||||
public let isContact: Bool
|
||||
public let isInChatList: Bool
|
||||
public let canUnmute: Bool
|
||||
public let joined: Bool
|
||||
|
||||
public init(peer: Peer, joined: Bool) {
|
||||
public init(peer: Peer, isContact: Bool, isInChatList: Bool, canUnmute: Bool, joined: Bool) {
|
||||
self.peer = peer
|
||||
self.isContact = isContact
|
||||
self.isInChatList = isInChatList
|
||||
self.canUnmute = canUnmute
|
||||
self.joined = joined
|
||||
}
|
||||
}
|
||||
|
@ -168,6 +168,7 @@ public final class PinchSourceContainerNode: ASDisplayNode, UIGestureRecognizerD
|
||||
public var scaleUpdated: ((CGFloat, ContainedViewLayoutTransition) -> Void)?
|
||||
public var animatedOut: (() -> Void)?
|
||||
var deactivate: (() -> Void)?
|
||||
public var deactivated: (() -> Void)?
|
||||
var updated: ((CGFloat, CGPoint, CGPoint) -> Void)?
|
||||
|
||||
override public init() {
|
||||
@ -196,6 +197,7 @@ public final class PinchSourceContainerNode: ASDisplayNode, UIGestureRecognizerD
|
||||
|
||||
strongSelf.isActive = false
|
||||
strongSelf.deactivate?()
|
||||
strongSelf.deactivated?()
|
||||
}
|
||||
|
||||
self.gesture.updated = { [weak self] scale, pinchLocation, offset in
|
||||
|
@ -78,12 +78,33 @@ private class PeerInfoAvatarListLoadingStripNode: ASImageNode {
|
||||
}
|
||||
}
|
||||
|
||||
private struct CustomListItemResourceId: MediaResourceId {
|
||||
public var uniqueId: String {
|
||||
return "customNode"
|
||||
}
|
||||
|
||||
public var hashValue: Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
public func isEqual(to: MediaResourceId) -> Bool {
|
||||
if to is CustomListItemResourceId {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum PeerInfoAvatarListItem: Equatable {
|
||||
case custom(ASDisplayNode)
|
||||
case topImage([ImageRepresentationWithReference], [VideoRepresentationWithReference], Data?)
|
||||
case image(TelegramMediaImageReference?, [ImageRepresentationWithReference], [VideoRepresentationWithReference], Data?)
|
||||
|
||||
var id: WrappedMediaResourceId {
|
||||
switch self {
|
||||
case .custom:
|
||||
return WrappedMediaResourceId(CustomListItemResourceId())
|
||||
case let .topImage(representations, _, _):
|
||||
let representation = largestImageRepresentation(representations.map { $0.representation }) ?? representations[representations.count - 1].representation
|
||||
return WrappedMediaResourceId(representation.resource.id)
|
||||
@ -95,6 +116,8 @@ public enum PeerInfoAvatarListItem: Equatable {
|
||||
|
||||
var videoRepresentations: [VideoRepresentationWithReference] {
|
||||
switch self {
|
||||
case .custom:
|
||||
return []
|
||||
case let .topImage(_, videoRepresentations, _):
|
||||
return videoRepresentations
|
||||
case let .image(_, _, videoRepresentations, _):
|
||||
@ -330,6 +353,11 @@ public final class PeerInfoAvatarListItemNode: ASDisplayNode {
|
||||
let immediateThumbnailData: Data?
|
||||
var id: Int64
|
||||
switch item {
|
||||
case let .custom(node):
|
||||
id = 0
|
||||
representations = []
|
||||
videoRepresentations = []
|
||||
immediateThumbnailData = nil
|
||||
case let .topImage(topRepresentations, videoRepresentationsValue, immediateThumbnail):
|
||||
representations = topRepresentations
|
||||
videoRepresentations = videoRepresentationsValue
|
||||
|
@ -22,7 +22,7 @@ public struct WrappedMediaResourceId: Hashable {
|
||||
// }
|
||||
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(id.hashValue)
|
||||
hasher.combine(self.id.hashValue)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1905,7 +1905,9 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
|> mapToSignal { event -> Signal<PresentationGroupCallMemberEvent, NoError> in
|
||||
return postbox.transaction { transaction -> Signal<PresentationGroupCallMemberEvent, NoError> in
|
||||
if let peer = transaction.getPeer(event.peerId) {
|
||||
return .single(PresentationGroupCallMemberEvent(peer: peer, joined: event.joined))
|
||||
let isContact = transaction.isPeerContact(peerId: event.peerId)
|
||||
let isInChatList = transaction.getPeerChatListIndex(event.peerId) != nil
|
||||
return .single(PresentationGroupCallMemberEvent(peer: peer, isContact: isContact, isInChatList: isInChatList, canUnmute: event.canUnmute, joined: event.joined))
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
@ -1913,13 +1915,20 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
|> switchToLatest
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { [weak self] event in
|
||||
guard let strongSelf = self else {
|
||||
guard let strongSelf = self, event.peer.id != strongSelf.stateValue.myPeerId else {
|
||||
return
|
||||
}
|
||||
if event.peer.id == strongSelf.stateValue.myPeerId {
|
||||
return
|
||||
var skip = false
|
||||
if let participantsCount = strongSelf.participantsContext?.immediateState?.totalCount, participantsCount >= 250 {
|
||||
if event.peer.isVerified || event.isContact || event.isInChatList || (strongSelf.stateValue.defaultParticipantMuteState == .muted && event.canUnmute) {
|
||||
skip = false
|
||||
} else {
|
||||
skip = true
|
||||
}
|
||||
}
|
||||
if !skip {
|
||||
strongSelf.memberEventsPipe.putNext(event)
|
||||
}
|
||||
strongSelf.memberEventsPipe.putNext(event)
|
||||
}))
|
||||
|
||||
if let isCurrentlyConnecting = self.isCurrentlyConnecting, isCurrentlyConnecting {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -20,9 +20,9 @@ import AudioBlob
|
||||
import PeerInfoAvatarListNode
|
||||
|
||||
final class VoiceChatParticipantItem: ListViewItem {
|
||||
enum LayoutStyle {
|
||||
enum LayoutStyle: Equatable {
|
||||
case list
|
||||
case tile
|
||||
case tile(isLandscape: Bool)
|
||||
}
|
||||
|
||||
enum ParticipantText {
|
||||
@ -77,6 +77,7 @@ final class VoiceChatParticipantItem: ListViewItem {
|
||||
let style: LayoutStyle
|
||||
let enabled: Bool
|
||||
let transparent: Bool
|
||||
let pinned: Bool
|
||||
public let selectable: Bool
|
||||
let getAudioLevel: (() -> Signal<Float, NoError>)?
|
||||
let getVideo: () -> GroupVideoNode?
|
||||
@ -88,7 +89,7 @@ final class VoiceChatParticipantItem: ListViewItem {
|
||||
let getIsExpanded: () -> Bool
|
||||
let getUpdatingAvatar: () -> Signal<(TelegramMediaImageRepresentation, Float)?, NoError>
|
||||
|
||||
public init(presentationData: ItemListPresentationData, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, context: AccountContext, peer: Peer, ssrc: UInt32?, presence: PeerPresence?, text: ParticipantText, expandedText: ParticipantText?, icon: Icon, style: LayoutStyle, enabled: Bool, transparent: Bool, selectable: Bool, getAudioLevel: (() -> Signal<Float, NoError>)?, getVideo: @escaping () -> GroupVideoNode?, revealOptions: [RevealOption], revealed: Bool?, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, action: ((ASDisplayNode) -> Void)?, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? = nil, getIsExpanded: @escaping () -> Bool, getUpdatingAvatar: @escaping () -> Signal<(TelegramMediaImageRepresentation, Float)?, NoError>) {
|
||||
public init(presentationData: ItemListPresentationData, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, context: AccountContext, peer: Peer, ssrc: UInt32?, presence: PeerPresence?, text: ParticipantText, expandedText: ParticipantText?, icon: Icon, style: LayoutStyle, enabled: Bool, transparent: Bool, pinned: Bool, selectable: Bool, getAudioLevel: (() -> Signal<Float, NoError>)?, getVideo: @escaping () -> GroupVideoNode?, revealOptions: [RevealOption], revealed: Bool?, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, action: ((ASDisplayNode) -> Void)?, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? = nil, getIsExpanded: @escaping () -> Bool, getUpdatingAvatar: @escaping () -> Signal<(TelegramMediaImageRepresentation, Float)?, NoError>) {
|
||||
self.presentationData = presentationData
|
||||
self.dateTimeFormat = dateTimeFormat
|
||||
self.nameDisplayOrder = nameDisplayOrder
|
||||
@ -102,6 +103,7 @@ final class VoiceChatParticipantItem: ListViewItem {
|
||||
self.style = style
|
||||
self.enabled = enabled
|
||||
self.transparent = transparent
|
||||
self.pinned = pinned
|
||||
self.selectable = selectable
|
||||
self.getAudioLevel = getAudioLevel
|
||||
self.getVideo = getVideo
|
||||
@ -160,6 +162,7 @@ final class VoiceChatParticipantItem: ListViewItem {
|
||||
private let avatarFont = avatarPlaceholderFont(size: floor(40.0 * 16.0 / 37.0))
|
||||
private let tileSize = CGSize(width: 84.0, height: 84.0)
|
||||
private let backgroundCornerRadius: CGFloat = 14.0
|
||||
private let avatarSize: CGFloat = 40.0
|
||||
|
||||
class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
||||
private let topStripeNode: ASDisplayNode
|
||||
@ -178,6 +181,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
||||
private var extractedVerticalOffset: CGFloat?
|
||||
|
||||
fileprivate let avatarNode: AvatarNode
|
||||
private let pinIconNode: ASImageNode
|
||||
private let contentWrapperNode: ASDisplayNode
|
||||
private let titleNode: TextNode
|
||||
private let statusIconNode: ASImageNode
|
||||
@ -187,7 +191,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
||||
|
||||
private var avatarTransitionNode: ASImageNode?
|
||||
private var avatarListContainerNode: ASDisplayNode?
|
||||
private var avatarListWrapperNode: ASDisplayNode?
|
||||
private var avatarListWrapperNode: PinchSourceContainerNode?
|
||||
private var avatarListNode: PeerInfoAvatarListContainerNode?
|
||||
|
||||
private let actionContainerNode: ASDisplayNode
|
||||
@ -207,8 +211,11 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
||||
private var isExtracted = false
|
||||
private var wavesColor: UIColor?
|
||||
|
||||
private var videoContainerNode: ASDisplayNode
|
||||
private let videoContainerNode: ASDisplayNode
|
||||
private let fadeNode: ASImageNode
|
||||
private var videoNode: GroupVideoNode?
|
||||
private let videoReadyDisposable = MetaDisposable()
|
||||
private var videoReadyDelayed = false
|
||||
|
||||
private var raiseHandTimer: SwiftSignalKit.Timer?
|
||||
|
||||
@ -243,11 +250,32 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
||||
self.avatarNode = AvatarNode(font: avatarFont)
|
||||
self.avatarNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 40.0, height: 40.0))
|
||||
|
||||
self.pinIconNode = ASImageNode()
|
||||
self.pinIconNode.alpha = 0.65
|
||||
self.pinIconNode.displaysAsynchronously = false
|
||||
self.pinIconNode.displayWithoutProcessing = true
|
||||
self.pinIconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Pin"), color: UIColor(rgb: 0xffffff))
|
||||
|
||||
self.contentWrapperNode = ASDisplayNode()
|
||||
|
||||
self.videoContainerNode = ASDisplayNode()
|
||||
self.videoContainerNode.clipsToBounds = true
|
||||
|
||||
self.fadeNode = ASImageNode()
|
||||
self.fadeNode.displaysAsynchronously = false
|
||||
self.fadeNode.displayWithoutProcessing = true
|
||||
self.fadeNode.contentMode = .scaleToFill
|
||||
self.fadeNode.image = generateImage(CGSize(width: 1.0, height: 30.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())
|
||||
})
|
||||
self.videoContainerNode.addSubnode(fadeNode)
|
||||
|
||||
self.titleNode = TextNode()
|
||||
self.titleNode.isUserInteractionEnabled = false
|
||||
self.titleNode.contentMode = .left
|
||||
@ -291,7 +319,8 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
||||
self.contentWrapperNode.addSubnode(self.statusNode)
|
||||
self.contentWrapperNode.addSubnode(self.expandedStatusNode)
|
||||
self.contentWrapperNode.addSubnode(self.actionContainerNode)
|
||||
self.contentWrapperNode.addSubnode(self.actionButtonNode)
|
||||
self.actionContainerNode.addSubnode(self.actionButtonNode)
|
||||
self.offsetContainerNode.addSubnode(self.pinIconNode)
|
||||
self.offsetContainerNode.addSubnode(self.avatarNode)
|
||||
self.containerNode.targetNodeForActivationProgress = self.contextSourceNode.contentNode
|
||||
|
||||
@ -390,11 +419,38 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
||||
let initialScale = avatarInitialRect.width / targetRect.width
|
||||
avatarInitialRect.origin.y += backgroundCornerRadius / 2.0 * initialScale
|
||||
|
||||
let avatarListWrapperNode = ASDisplayNode()
|
||||
let avatarListWrapperNode = PinchSourceContainerNode()
|
||||
avatarListWrapperNode.clipsToBounds = true
|
||||
avatarListWrapperNode.frame = CGRect(x: targetRect.minX, y: targetRect.minY, width: targetRect.width, height: targetRect.height + backgroundCornerRadius)
|
||||
|
||||
avatarListWrapperNode.cornerRadius = backgroundCornerRadius
|
||||
|
||||
avatarListWrapperNode.activate = { [weak self] sourceNode in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.avatarListNode?.controlsContainerNode.alpha = 0.0
|
||||
let pinchController = PinchController(sourceNode: sourceNode, getContentAreaInScreenSpace: {
|
||||
return UIScreen.main.bounds
|
||||
})
|
||||
item.context.sharedContext.mainWindow?.presentInGlobalOverlay(pinchController)
|
||||
}
|
||||
avatarListWrapperNode.deactivated = { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.avatarListWrapperNode?.contentNode.layer.animate(from: 0.0 as NSNumber, to: backgroundCornerRadius as NSNumber, keyPath: "cornerRadius", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.3, completion: { _ in
|
||||
})
|
||||
}
|
||||
avatarListWrapperNode.update(size: targetRect.size, transition: .immediate)
|
||||
avatarListWrapperNode.frame = CGRect(x: targetRect.minX, y: targetRect.minY, width: targetRect.width, height: targetRect.height + backgroundCornerRadius)
|
||||
avatarListWrapperNode.animatedOut = { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.avatarListNode?.controlsContainerNode.alpha = 1.0
|
||||
strongSelf.avatarListNode?.controlsContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
||||
}
|
||||
|
||||
let transitionNode = ASImageNode()
|
||||
transitionNode.clipsToBounds = true
|
||||
transitionNode.displaysAsynchronously = false
|
||||
@ -405,8 +461,9 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
||||
radiusTransition.updateCornerRadius(node: transitionNode, cornerRadius: 0.0)
|
||||
|
||||
strongSelf.avatarNode.isHidden = true
|
||||
strongSelf.videoContainerNode.isHidden = true
|
||||
|
||||
avatarListWrapperNode.addSubnode(transitionNode)
|
||||
avatarListWrapperNode.contentNode.addSubnode(transitionNode)
|
||||
strongSelf.avatarTransitionNode = transitionNode
|
||||
|
||||
let avatarListContainerNode = ASDisplayNode()
|
||||
@ -420,6 +477,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
||||
radiusTransition.updateCornerRadius(node: avatarListContainerNode, cornerRadius: 0.0)
|
||||
|
||||
let avatarListNode = PeerInfoAvatarListContainerNode(context: item.context)
|
||||
avatarListWrapperNode.contentNode.clipsToBounds = true
|
||||
avatarListNode.backgroundColor = .clear
|
||||
avatarListNode.peer = item.peer
|
||||
avatarListNode.firstFullSizeOnly = true
|
||||
@ -434,7 +492,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
||||
|
||||
avatarListContainerNode.addSubnode(avatarListNode)
|
||||
avatarListContainerNode.addSubnode(avatarListNode.controlsClippingOffsetNode)
|
||||
avatarListWrapperNode.addSubnode(avatarListContainerNode)
|
||||
avatarListWrapperNode.contentNode.addSubnode(avatarListContainerNode)
|
||||
|
||||
avatarListNode.update(size: targetRect.size, peer: item.peer, additionalEntry: item.getUpdatingAvatar(), isExpanded: true, transition: .immediate)
|
||||
strongSelf.offsetContainerNode.supernode?.addSubnode(avatarListWrapperNode)
|
||||
@ -461,6 +519,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
||||
avatarListContainerNode?.removeFromSupernode()
|
||||
})
|
||||
|
||||
strongSelf.videoContainerNode.isHidden = false
|
||||
avatarListWrapperNode.layer.animate(from: 1.0 as NSNumber, to: targetScale as NSNumber, keyPath: "transform.scale", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.2, removeOnCompletion: false)
|
||||
avatarListWrapperNode.layer.animate(from: NSValue(cgPoint: avatarListWrapperNode.position), to: NSValue(cgPoint: avatarInitialRect.center), keyPath: "position", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.2, removeOnCompletion: false, completion: { [weak transitionNode, weak self] _ in
|
||||
transitionNode?.removeFromSupernode()
|
||||
@ -556,6 +615,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.videoReadyDisposable.dispose()
|
||||
self.audioLevelDisposable.dispose()
|
||||
self.raiseHandTimer?.invalidate()
|
||||
}
|
||||
@ -565,60 +625,161 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
||||
self.layoutParams?.0.action?(self.contextSourceNode)
|
||||
}
|
||||
|
||||
func transitionIn(from otherNode: VoiceChatParticipantItemNode, containerNode: ASDisplayNode) {
|
||||
guard let otherItem = otherNode.item, otherItem.style != self.item?.style else {
|
||||
func transitionIn(from sourceNode: VoiceChatParticipantItemNode, containerNode: ASDisplayNode) {
|
||||
guard let item = self.item, let sourceItem = sourceNode.item, sourceItem.style != self.item?.style else {
|
||||
return
|
||||
}
|
||||
|
||||
switch otherItem.style {
|
||||
switch sourceItem.style {
|
||||
case .list:
|
||||
otherNode.avatarNode.alpha = 0.0
|
||||
|
||||
let startContainerPosition = otherNode.avatarNode.view.convert(otherNode.avatarNode.bounds, to: containerNode.view).center.offsetBy(dx: 0.0, dy: 9.0)
|
||||
|
||||
let initialPosition = self.contextSourceNode.position
|
||||
let targetContainerPosition = self.contextSourceNode.view.convert(self.contextSourceNode.bounds, to: containerNode.view).center
|
||||
|
||||
self.contextSourceNode.position = targetContainerPosition
|
||||
containerNode.addSubnode(self.contextSourceNode)
|
||||
|
||||
self.contextSourceNode.layer.animatePosition(from: startContainerPosition, to: targetContainerPosition, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, completion: { [weak self] _ in
|
||||
if let strongSelf = self {
|
||||
strongSelf.contextSourceNode.position = initialPosition
|
||||
strongSelf.containerNode.addSubnode(strongSelf.contextSourceNode)
|
||||
}
|
||||
})
|
||||
|
||||
if let videoNode = otherNode.videoNode {
|
||||
self.avatarNode.alpha = 0.0
|
||||
|
||||
otherNode.videoNode = nil
|
||||
self.videoNode = videoNode
|
||||
|
||||
let initialPosition = videoNode.position
|
||||
videoNode.position = CGPoint(x: self.videoContainerNode.frame.width / 2.0, y: self.videoContainerNode.frame.width / 2.0)
|
||||
videoNode.layer.animatePosition(from: initialPosition, to: videoNode.position, duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue)
|
||||
self.videoContainerNode.addSubnode(videoNode)
|
||||
|
||||
self.videoContainerNode.layer.animateFrame(from: self.avatarNode.frame, to: self.videoContainerNode.frame, duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue)
|
||||
self.videoContainerNode.layer.animate(from: (self.avatarNode.frame.width / 2.0) as NSNumber, to: backgroundCornerRadius as NSNumber, keyPath: "cornerRadius", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.2, removeOnCompletion: false, completion: { [weak self] value in
|
||||
})
|
||||
var startContainerPosition = sourceNode.avatarNode.view.convert(sourceNode.avatarNode.bounds, to: containerNode.view).center
|
||||
var animate = true
|
||||
if startContainerPosition.y > containerNode.frame.height - 238.0 {
|
||||
animate = false
|
||||
}
|
||||
|
||||
self.backgroundImageNode.layer.animateScale(from: 0.001, to: 1.0, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue)
|
||||
self.backgroundImageNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue)
|
||||
self.contentWrapperNode.layer.animateScale(from: 0.001, to: 1.0, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue)
|
||||
self.contentWrapperNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue)
|
||||
case .tile:
|
||||
if let otherVideoNode = otherNode.videoNode {
|
||||
otherNode.videoNode = nil
|
||||
self.videoNode = otherVideoNode
|
||||
if let videoNode = sourceNode.videoNode {
|
||||
if item.transparent {
|
||||
} else {
|
||||
if item.pinned {
|
||||
self.avatarNode.alpha = 1.0
|
||||
videoNode.alpha = 0.0
|
||||
} else {
|
||||
self.avatarNode.alpha = 0.0
|
||||
}
|
||||
}
|
||||
|
||||
let initialPosition = otherVideoNode.position
|
||||
otherNode.position = CGPoint(x: self.videoContainerNode.frame.width / 2.0, y: self.videoContainerNode.frame.width / 2.0)
|
||||
self.videoContainerNode.addSubnode(otherVideoNode)
|
||||
sourceNode.videoNode = nil
|
||||
self.videoNode = videoNode
|
||||
|
||||
if animate {
|
||||
self.videoContainerNode.layer.animateScale(from: avatarSize / tileSize.width, to: 1.0, duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue)
|
||||
}
|
||||
self.videoContainerNode.insertSubnode(videoNode, at: 0)
|
||||
|
||||
if animate {
|
||||
self.videoContainerNode.layer.animate(from: (tileSize.width / 2.0) as NSNumber, to: backgroundCornerRadius as NSNumber, keyPath: "cornerRadius", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.2, removeOnCompletion: false, completion: { _ in
|
||||
})
|
||||
}
|
||||
} else {
|
||||
self.avatarNode.alpha = 1.0
|
||||
startContainerPosition = startContainerPosition.offsetBy(dx: 0.0, dy: 9.0)
|
||||
}
|
||||
|
||||
if animate {
|
||||
sourceNode.avatarNode.alpha = 0.0
|
||||
|
||||
let initialPosition = self.contextSourceNode.position
|
||||
let targetContainerPosition = self.contextSourceNode.view.convert(self.contextSourceNode.bounds, to: containerNode.view).center
|
||||
|
||||
self.contextSourceNode.position = targetContainerPosition
|
||||
containerNode.addSubnode(self.contextSourceNode)
|
||||
|
||||
self.contextSourceNode.layer.animatePosition(from: startContainerPosition, to: targetContainerPosition, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, completion: { [weak self, weak sourceNode] _ in
|
||||
if let strongSelf = self {
|
||||
sourceNode?.avatarNode.alpha = 1.0
|
||||
strongSelf.contextSourceNode.position = initialPosition
|
||||
strongSelf.containerNode.addSubnode(strongSelf.contextSourceNode)
|
||||
}
|
||||
})
|
||||
|
||||
self.fadeNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue)
|
||||
|
||||
self.backgroundImageNode.layer.animateScale(from: 0.001, to: 1.0, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue)
|
||||
self.backgroundImageNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue)
|
||||
self.contentWrapperNode.layer.animateScale(from: 0.001, to: 1.0, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue)
|
||||
self.contentWrapperNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue)
|
||||
}
|
||||
case .tile:
|
||||
let startContainerAvatarPosition = sourceNode.avatarNode.view.convert(sourceNode.avatarNode.bounds, to: containerNode.view).center
|
||||
var animate = true
|
||||
if startContainerAvatarPosition.x < -tileSize.width || startContainerAvatarPosition.x > containerNode.frame.width + tileSize.width {
|
||||
animate = false
|
||||
}
|
||||
|
||||
if let videoNode = sourceNode.videoNode {
|
||||
if item.transparent {
|
||||
} else {
|
||||
self.avatarNode.alpha = 0.0
|
||||
}
|
||||
sourceNode.videoNode = nil
|
||||
self.videoNode = videoNode
|
||||
self.videoContainerNode.insertSubnode(videoNode, at: 0)
|
||||
|
||||
videoNode.alpha = 1.0
|
||||
}
|
||||
|
||||
if animate {
|
||||
sourceNode.avatarNode.alpha = 0.0
|
||||
sourceNode.fadeNode.alpha = 0.0
|
||||
|
||||
let initialAvatarPosition = self.avatarNode.position
|
||||
let targetContainerAvatarPosition = self.avatarNode.view.convert(self.avatarNode.bounds, to: containerNode.view).center
|
||||
|
||||
let startContainerBackgroundPosition = sourceNode.backgroundImageNode.view.convert(sourceNode.backgroundImageNode.bounds, to: containerNode.view).center
|
||||
let startContainerContentPosition = sourceNode.contentWrapperNode.view.convert(sourceNode.contentWrapperNode.bounds, to: containerNode.view).center
|
||||
let startContainerVideoPosition = sourceNode.videoContainerNode.view.convert(sourceNode.videoContainerNode.bounds, to: containerNode.view).center
|
||||
|
||||
let initialBackgroundPosition = sourceNode.backgroundImageNode.position
|
||||
let initialContentPosition = sourceNode.contentWrapperNode.position
|
||||
|
||||
sourceNode.backgroundImageNode.position = targetContainerAvatarPosition
|
||||
sourceNode.contentWrapperNode.position = targetContainerAvatarPosition
|
||||
containerNode.addSubnode(sourceNode.backgroundImageNode)
|
||||
containerNode.addSubnode(sourceNode.contentWrapperNode)
|
||||
|
||||
if self.videoNode != nil {
|
||||
sourceNode.backgroundImageNode.alpha = 0.0
|
||||
}
|
||||
|
||||
sourceNode.backgroundImageNode.layer.animatePosition(from: startContainerBackgroundPosition, to: targetContainerAvatarPosition, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, completion: { [weak sourceNode] _ in
|
||||
if let sourceNode = sourceNode {
|
||||
sourceNode.backgroundImageNode.alpha = 1.0
|
||||
sourceNode.backgroundImageNode.position = initialBackgroundPosition
|
||||
sourceNode.contextSourceNode.contentNode.insertSubnode(sourceNode.backgroundImageNode, at: 0)
|
||||
}
|
||||
})
|
||||
|
||||
sourceNode.contentWrapperNode.layer.animatePosition(from: startContainerContentPosition, to: targetContainerAvatarPosition, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, completion: { [weak sourceNode] _ in
|
||||
if let sourceNode = sourceNode {
|
||||
sourceNode.avatarNode.alpha = 1.0
|
||||
sourceNode.fadeNode.alpha = 1.0
|
||||
sourceNode.contentWrapperNode.position = initialContentPosition
|
||||
sourceNode.offsetContainerNode.insertSubnode(sourceNode.contentWrapperNode, aboveSubnode: sourceNode.videoContainerNode)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
self.avatarNode.position = targetContainerAvatarPosition
|
||||
containerNode.addSubnode(self.avatarNode)
|
||||
|
||||
self.avatarNode.layer.animatePosition(from: startContainerAvatarPosition, to: targetContainerAvatarPosition, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, completion: { [weak self] _ in
|
||||
if let strongSelf = self {
|
||||
strongSelf.avatarNode.position = initialAvatarPosition
|
||||
strongSelf.offsetContainerNode.addSubnode(strongSelf.avatarNode)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
self.videoContainerNode.position = targetContainerAvatarPosition
|
||||
containerNode.addSubnode(self.videoContainerNode)
|
||||
|
||||
self.videoContainerNode.layer.animatePosition(from: startContainerVideoPosition, to: targetContainerAvatarPosition, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, completion: { [weak self] _ in
|
||||
if let strongSelf = self {
|
||||
strongSelf.videoContainerNode.position = initialAvatarPosition
|
||||
strongSelf.offsetContainerNode.insertSubnode(strongSelf.videoContainerNode, belowSubnode: strongSelf.contentWrapperNode)
|
||||
}
|
||||
})
|
||||
|
||||
self.videoContainerNode.layer.animateScale(from: 1.0, to: avatarSize / tileSize.width, duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue)
|
||||
self.videoContainerNode.layer.animate(from: backgroundCornerRadius as NSNumber, to: (tileSize.width / 2.0) as NSNumber, keyPath: "cornerRadius", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.2, removeOnCompletion: false, completion: { _ in
|
||||
})
|
||||
|
||||
self.fadeNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue)
|
||||
|
||||
sourceNode.backgroundImageNode.layer.animateScale(from: 1.0, to: 0.001, duration: 0.35, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue)
|
||||
sourceNode.backgroundImageNode.layer.animateAlpha(from: sourceNode.backgroundImageNode.alpha, to: 0.0, duration: 0.35, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue)
|
||||
sourceNode.contentWrapperNode.layer.animateScale(from: 1.0, to: 0.001, duration: 0.35, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue)
|
||||
sourceNode.contentWrapperNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.35, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -634,12 +795,11 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
||||
|
||||
return { item, params, first, last in
|
||||
var updatedTheme: PresentationTheme?
|
||||
var updatedName = false
|
||||
if currentItem?.presentationData.theme !== item.presentationData.theme {
|
||||
updatedTheme = item.presentationData.theme
|
||||
}
|
||||
|
||||
let titleFont = item.style == .tile ? Font.regular(12.0) : Font.regular(17.0)
|
||||
var titleFont = item.style == .list ? Font.regular(17.0) : Font.regular(12.0)
|
||||
let statusFont = Font.regular(14.0)
|
||||
|
||||
var titleAttributedString: NSAttributedString?
|
||||
@ -648,34 +808,39 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
||||
|
||||
let rightInset: CGFloat = params.rightInset
|
||||
|
||||
let titleColor = item.presentationData.theme.list.itemPrimaryTextColor
|
||||
var titleColor = item.presentationData.theme.list.itemPrimaryTextColor
|
||||
if item.transparent && item.style == .list {
|
||||
titleFont = Font.semibold(17.0)
|
||||
titleColor = UIColor(rgb: 0xffffff, alpha: 0.65)
|
||||
}
|
||||
let currentBoldFont: UIFont = titleFont
|
||||
|
||||
var updatedTitle = false
|
||||
if let user = item.peer as? TelegramUser {
|
||||
if let firstName = user.firstName, let lastName = user.lastName, !firstName.isEmpty, !lastName.isEmpty {
|
||||
if item.style == .tile {
|
||||
let textColor: UIColor
|
||||
switch item.icon {
|
||||
case .wantsToSpeak:
|
||||
textColor = item.presentationData.theme.list.itemAccentColor
|
||||
default:
|
||||
textColor = titleColor
|
||||
}
|
||||
titleAttributedString = NSAttributedString(string: firstName, font: titleFont, textColor: textColor)
|
||||
} else {
|
||||
let string = NSMutableAttributedString()
|
||||
switch item.nameDisplayOrder {
|
||||
case .firstLast:
|
||||
string.append(NSAttributedString(string: firstName, font: titleFont, textColor: titleColor))
|
||||
string.append(NSAttributedString(string: " ", font: titleFont, textColor: titleColor))
|
||||
string.append(NSAttributedString(string: lastName, font: currentBoldFont, textColor: titleColor))
|
||||
case .lastFirst:
|
||||
string.append(NSAttributedString(string: lastName, font: currentBoldFont, textColor: titleColor))
|
||||
string.append(NSAttributedString(string: " ", font: titleFont, textColor: titleColor))
|
||||
string.append(NSAttributedString(string: firstName, font: titleFont, textColor: titleColor))
|
||||
}
|
||||
titleAttributedString = string
|
||||
switch item.style {
|
||||
case .list:
|
||||
let string = NSMutableAttributedString()
|
||||
switch item.nameDisplayOrder {
|
||||
case .firstLast:
|
||||
string.append(NSAttributedString(string: firstName, font: titleFont, textColor: titleColor))
|
||||
string.append(NSAttributedString(string: " ", font: titleFont, textColor: titleColor))
|
||||
string.append(NSAttributedString(string: lastName, font: currentBoldFont, textColor: titleColor))
|
||||
case .lastFirst:
|
||||
string.append(NSAttributedString(string: lastName, font: currentBoldFont, textColor: titleColor))
|
||||
string.append(NSAttributedString(string: " ", font: titleFont, textColor: titleColor))
|
||||
string.append(NSAttributedString(string: firstName, font: titleFont, textColor: titleColor))
|
||||
}
|
||||
titleAttributedString = string
|
||||
case .tile:
|
||||
let textColor: UIColor
|
||||
switch item.icon {
|
||||
case .wantsToSpeak:
|
||||
textColor = item.presentationData.theme.list.itemAccentColor
|
||||
default:
|
||||
textColor = titleColor
|
||||
}
|
||||
titleAttributedString = NSAttributedString(string: firstName, font: titleFont, textColor: textColor)
|
||||
}
|
||||
} else if let firstName = user.firstName, !firstName.isEmpty {
|
||||
titleAttributedString = NSAttributedString(string: firstName, font: currentBoldFont, textColor: titleColor)
|
||||
@ -712,7 +877,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
||||
statusAttributedString = NSAttributedString(string: item.presentationData.strings.LastSeen_Offline, font: statusFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor)
|
||||
}
|
||||
case let .text(text, textColor):
|
||||
let textColorValue: UIColor
|
||||
var textColorValue: UIColor
|
||||
switch textColor {
|
||||
case .generic:
|
||||
textColorValue = item.presentationData.theme.list.itemSecondaryTextColor
|
||||
@ -725,6 +890,9 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
||||
textColorValue = UIColor(rgb: 0xff3b30)
|
||||
wavesColor = textColorValue
|
||||
}
|
||||
if item.transparent && item.style == .list {
|
||||
textColorValue = UIColor(rgb: 0xffffff, alpha: 0.65)
|
||||
}
|
||||
statusAttributedString = NSAttributedString(string: text, font: statusFont, textColor: textColorValue)
|
||||
case .none:
|
||||
break
|
||||
@ -747,10 +915,9 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
||||
expandedStatusAttributedString = statusAttributedString
|
||||
}
|
||||
|
||||
let leftInset: CGFloat = 65.0 + params.leftInset
|
||||
let leftInset: CGFloat = 58.0 + params.leftInset
|
||||
let verticalInset: CGFloat = 8.0
|
||||
let verticalOffset: CGFloat = 0.0
|
||||
let avatarSize: CGFloat = 40.0
|
||||
|
||||
var titleIconsWidth: CGFloat = 0.0
|
||||
var currentCredibilityIconImage: UIImage?
|
||||
@ -786,7 +953,6 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
||||
let (statusLayout, statusApply) = makeStatusLayout(TextNodeLayoutArguments(attributedString: statusAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 8.0 - rightInset - 30.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
let (expandedStatusLayout, expandedStatusApply) = makeExpandedStatusLayout(TextNodeLayoutArguments(attributedString: expandedStatusAttributedString, backgroundColor: nil, maximumNumberOfLines: 6, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 8.0 - rightInset - expandedRightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
|
||||
let titleSpacing: CGFloat = statusLayout.size.height == 0.0 ? 0.0 : 1.0
|
||||
|
||||
let minHeight: CGFloat = titleLayout.size.height + verticalInset * 2.0
|
||||
@ -797,7 +963,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
||||
switch item.style {
|
||||
case .list:
|
||||
contentSize = CGSize(width: params.width, height: max(minHeight, rawHeight))
|
||||
insets = UIEdgeInsets()
|
||||
insets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: item.transparent ? 6.0 : 0.0, right: 0.0)
|
||||
case .tile:
|
||||
contentSize = tileSize
|
||||
insets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: !last ? 6.0 : 0.0, right: 0.0)
|
||||
@ -870,14 +1036,14 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
||||
switch item.style {
|
||||
case .list:
|
||||
nonExtractedRect = CGRect(origin: CGPoint(x: 16.0, y: 0.0), size: CGSize(width: layout.contentSize.width - 32.0, height: layout.contentSize.height))
|
||||
avatarFrame = CGRect(origin: CGPoint(x: params.leftInset + 15.0, y: floorToScreenPixels((layout.contentSize.height - avatarSize) / 2.0)), size: CGSize(width: avatarSize, height: avatarSize))
|
||||
avatarFrame = CGRect(origin: CGPoint(x: params.leftInset + 8.0, y: floorToScreenPixels((layout.contentSize.height - avatarSize) / 2.0)), size: CGSize(width: avatarSize, height: avatarSize))
|
||||
animationSize = CGSize(width: 36.0, height: 36.0)
|
||||
animationScale = 1.0
|
||||
animationFrame = CGRect(x: params.width - animationSize.width - 6.0 - params.rightInset, y: floor((layout.contentSize.height - animationSize.height) / 2.0) + 1.0, width: animationSize.width, height: animationSize.height)
|
||||
titleFrame = CGRect(origin: CGPoint(x: leftInset, y: verticalInset + verticalOffset), size: titleLayout.size)
|
||||
case .tile:
|
||||
case let .tile(isLandscape):
|
||||
nonExtractedRect = CGRect(origin: CGPoint(), size: layout.contentSize)
|
||||
strongSelf.containerNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
|
||||
strongSelf.containerNode.transform = CATransform3DMakeRotation(isLandscape ? 0.0 : CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
|
||||
strongSelf.statusNode.isHidden = true
|
||||
strongSelf.expandedStatusNode.isHidden = true
|
||||
avatarFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - avatarSize) / 2.0), y: 13.0), size: CGSize(width: avatarSize, height: avatarSize))
|
||||
@ -1003,9 +1169,9 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
||||
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 1)
|
||||
}
|
||||
|
||||
strongSelf.topStripeNode.isHidden = first || item.style == .tile
|
||||
strongSelf.bottomStripeNode.isHidden = last || item.style == .tile
|
||||
|
||||
strongSelf.topStripeNode.isHidden = first || item.style != .list || item.transparent
|
||||
strongSelf.bottomStripeNode.isHidden = last || item.style != .list || item.transparent
|
||||
|
||||
transition.updateFrame(node: strongSelf.topStripeNode, frame: CGRect(origin: CGPoint(x: leftInset, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight)))
|
||||
transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: leftInset, y: contentSize.height + -separatorHeight), size: CGSize(width: layoutSize.width - leftInset, height: separatorHeight)))
|
||||
|
||||
@ -1065,7 +1231,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
||||
audioLevelView.layer.mask = playbackMaskLayer
|
||||
|
||||
audioLevelView.setColor(wavesColor)
|
||||
audioLevelView.alpha = strongSelf.isExtracted ? 0.0 : 1.0
|
||||
audioLevelView.alpha = strongSelf.isExtracted || (strongSelf.item?.transparent == true) ? 0.0 : 1.0
|
||||
|
||||
strongSelf.audioLevelView = audioLevelView
|
||||
strongSelf.offsetContainerNode.view.insertSubview(audioLevelView, at: 0)
|
||||
@ -1124,9 +1290,12 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
||||
nodeToAnimateIn = animationNode
|
||||
}
|
||||
var color = color
|
||||
if color.rgb == 0x979797 && item.style == .tile {
|
||||
if item.transparent {
|
||||
color = UIColor(rgb: 0xffffff)
|
||||
} else if color.rgb == 0x979797 && item.style != .list {
|
||||
color = UIColor(rgb: 0xffffff)
|
||||
}
|
||||
animationNode.alpha = item.transparent && item.style == .list ? 0.65 : 1.0
|
||||
animationNode.update(state: VoiceChatMicrophoneNode.State(muted: muted, filled: false, color: color), animated: true)
|
||||
strongSelf.actionButtonNode.isUserInteractionEnabled = false
|
||||
} else if let animationNode = strongSelf.animationNode {
|
||||
@ -1204,33 +1373,121 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
||||
}
|
||||
|
||||
let videoSize = tileSize
|
||||
|
||||
let videoNode = item.getVideo()
|
||||
if let current = strongSelf.videoNode, current !== videoNode {
|
||||
current.removeFromSupernode()
|
||||
strongSelf.videoReadyDisposable.set(nil)
|
||||
}
|
||||
|
||||
|
||||
let videoNodeUpdated = strongSelf.videoNode !== videoNode
|
||||
strongSelf.videoNode = videoNode
|
||||
|
||||
strongSelf.fadeNode.frame = CGRect(x: 0.0, y: tileSize.height - 30.0, width: tileSize.width, height: 30.0)
|
||||
strongSelf.videoContainerNode.bounds = CGRect(origin: CGPoint(), size: tileSize)
|
||||
switch item.style {
|
||||
case .list:
|
||||
strongSelf.videoContainerNode.frame = strongSelf.avatarNode.frame
|
||||
strongSelf.videoContainerNode.cornerRadius = avatarSize / 2.0
|
||||
strongSelf.fadeNode.alpha = 0.0
|
||||
strongSelf.videoContainerNode.position = strongSelf.avatarNode.position
|
||||
strongSelf.videoContainerNode.cornerRadius = tileSize.width / 2.0
|
||||
strongSelf.videoContainerNode.transform = CATransform3DMakeScale(avatarSize / tileSize.width, avatarSize / tileSize.width, 1.0)
|
||||
case .tile:
|
||||
strongSelf.videoContainerNode.frame = CGRect(origin: CGPoint(), size: tileSize)
|
||||
strongSelf.fadeNode.alpha = 1.0
|
||||
strongSelf.videoContainerNode.position = CGPoint(x: tileSize.width / 2.0, y: tileSize.height / 2.0)
|
||||
strongSelf.videoContainerNode.cornerRadius = backgroundCornerRadius
|
||||
strongSelf.videoContainerNode.transform = CATransform3DMakeScale(1.0, 1.0, 1.0)
|
||||
}
|
||||
|
||||
if let videoNode = videoNode {
|
||||
strongSelf.avatarNode.alpha = 0.0
|
||||
if case .tile = item.style {
|
||||
if currentItem != nil {
|
||||
let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut)
|
||||
if item.pinned {
|
||||
transition.updateAlpha(node: videoNode, alpha: 0.0)
|
||||
transition.updateAlpha(node: strongSelf.fadeNode, alpha: 0.0)
|
||||
strongSelf.videoContainerNode.layer.animateScale(from: 1.0, to: 0.001, duration: 0.2)
|
||||
transition.updateAlpha(node: strongSelf.avatarNode, alpha: 1.0)
|
||||
strongSelf.avatarNode.layer.animateScale(from: 0.0, to: 1.0, duration: 0.2)
|
||||
} else {
|
||||
transition.updateAlpha(node: videoNode, alpha: 1.0)
|
||||
transition.updateAlpha(node: strongSelf.fadeNode, alpha: 1.0)
|
||||
strongSelf.videoContainerNode.layer.animateScale(from: 0.001, to: 1.0, duration: 0.2)
|
||||
transition.updateAlpha(node: strongSelf.avatarNode, alpha: 0.0)
|
||||
strongSelf.avatarNode.layer.animateScale(from: 1.0, to: 0.001, duration: 0.2)
|
||||
}
|
||||
} else {
|
||||
if item.pinned {
|
||||
videoNode.alpha = 0.0
|
||||
strongSelf.avatarNode.alpha = 1.0
|
||||
} else {
|
||||
videoNode.alpha = 1.0
|
||||
strongSelf.avatarNode.alpha = 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
videoNode.updateLayout(size: videoSize, isLandscape: false, transition: .immediate)
|
||||
if videoNode.supernode !== strongSelf.avatarNode {
|
||||
if videoNode.supernode !== strongSelf.videoContainerNode {
|
||||
videoNode.clipsToBounds = true
|
||||
strongSelf.videoContainerNode.addSubnode(videoNode)
|
||||
}
|
||||
|
||||
videoNode.position = CGPoint(x: strongSelf.videoContainerNode.frame.width / 2.0, y: strongSelf.videoContainerNode.frame.height / 2.0)
|
||||
videoNode.position = CGPoint(x: videoSize.width / 2.0, y: videoSize.height / 2.0)
|
||||
videoNode.bounds = CGRect(origin: CGPoint(), size: videoSize)
|
||||
|
||||
if videoNodeUpdated {
|
||||
strongSelf.videoReadyDelayed = false
|
||||
strongSelf.videoReadyDisposable.set((videoNode.ready
|
||||
|> deliverOnMainQueue).start(next: { [weak self] ready in
|
||||
if let strongSelf = self {
|
||||
if !ready {
|
||||
strongSelf.videoReadyDelayed = true
|
||||
}
|
||||
if let videoNode = strongSelf.videoNode, ready && (strongSelf.item?.transparent != true) {
|
||||
if strongSelf.videoReadyDelayed {
|
||||
Queue.mainQueue().after(0.15) {
|
||||
switch item.style {
|
||||
case .list:
|
||||
strongSelf.avatarNode.alpha = 0.0
|
||||
strongSelf.avatarNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2)
|
||||
case .tile:
|
||||
if item.pinned {
|
||||
strongSelf.avatarNode.alpha = 1.0
|
||||
videoNode.alpha = 0.0
|
||||
} else {
|
||||
strongSelf.avatarNode.alpha = 0.0
|
||||
strongSelf.avatarNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2)
|
||||
videoNode.layer.animateScale(from: 0.01, to: 1.0, duration: 0.2)
|
||||
videoNode.alpha = 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if case .tile = item.style, item.pinned {
|
||||
strongSelf.avatarNode.alpha = 1.0
|
||||
} else {
|
||||
strongSelf.avatarNode.alpha = 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
if item.style == .list {
|
||||
strongSelf.audioLevelView?.alpha = item.transparent ? 0.0 : 0.0
|
||||
strongSelf.avatarNode.isHidden = item.transparent
|
||||
strongSelf.videoContainerNode.isHidden = item.transparent
|
||||
strongSelf.pinIconNode.isHidden = !item.transparent
|
||||
} else {
|
||||
strongSelf.pinIconNode.isHidden = true
|
||||
strongSelf.videoContainerNode.isHidden = item.transparent
|
||||
if item.transparent {
|
||||
strongSelf.avatarNode.alpha = 1.0
|
||||
}
|
||||
}
|
||||
if let image = strongSelf.pinIconNode.image {
|
||||
strongSelf.pinIconNode.frame = CGRect(origin: CGPoint(x: 16.0, y: 17.0), size: image.size)
|
||||
}
|
||||
|
||||
strongSelf.iconNode?.frame = CGRect(origin: CGPoint(), size: animationSize)
|
||||
@ -1255,36 +1512,51 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
||||
|
||||
var isHighlighted = false
|
||||
func updateIsHighlighted(transition: ContainedViewLayoutTransition) {
|
||||
if self.isHighlighted {
|
||||
self.highlightedBackgroundNode.alpha = 1.0
|
||||
if self.highlightedBackgroundNode.supernode == nil {
|
||||
var anchorNode: ASDisplayNode?
|
||||
if self.bottomStripeNode.supernode != nil {
|
||||
anchorNode = self.bottomStripeNode
|
||||
} else if self.topStripeNode.supernode != nil {
|
||||
anchorNode = self.topStripeNode
|
||||
}
|
||||
if let anchorNode = anchorNode {
|
||||
self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: anchorNode)
|
||||
} else {
|
||||
self.addSubnode(self.highlightedBackgroundNode)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if self.highlightedBackgroundNode.supernode != nil {
|
||||
if transition.isAnimated {
|
||||
self.highlightedBackgroundNode.layer.animateAlpha(from: self.highlightedBackgroundNode.alpha, to: 0.0, duration: 0.4, completion: { [weak self] completed in
|
||||
if let strongSelf = self {
|
||||
if completed {
|
||||
strongSelf.highlightedBackgroundNode.removeFromSupernode()
|
||||
}
|
||||
guard let item = self.item else {
|
||||
return
|
||||
}
|
||||
switch item.style {
|
||||
case .list:
|
||||
if self.isHighlighted {
|
||||
self.highlightedBackgroundNode.alpha = 1.0
|
||||
if self.highlightedBackgroundNode.supernode == nil {
|
||||
var anchorNode: ASDisplayNode?
|
||||
if self.bottomStripeNode.supernode != nil {
|
||||
anchorNode = self.bottomStripeNode
|
||||
} else if self.topStripeNode.supernode != nil {
|
||||
anchorNode = self.topStripeNode
|
||||
}
|
||||
})
|
||||
self.highlightedBackgroundNode.alpha = 0.0
|
||||
if let anchorNode = anchorNode {
|
||||
self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: anchorNode)
|
||||
} else {
|
||||
self.addSubnode(self.highlightedBackgroundNode)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.highlightedBackgroundNode.removeFromSupernode()
|
||||
if self.highlightedBackgroundNode.supernode != nil {
|
||||
if transition.isAnimated {
|
||||
self.highlightedBackgroundNode.layer.animateAlpha(from: self.highlightedBackgroundNode.alpha, to: 0.0, duration: 0.4, completion: { [weak self] completed in
|
||||
if let strongSelf = self {
|
||||
if completed {
|
||||
strongSelf.highlightedBackgroundNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
})
|
||||
self.highlightedBackgroundNode.alpha = 0.0
|
||||
} else {
|
||||
self.highlightedBackgroundNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case .tile:
|
||||
break
|
||||
// if self.isHighlighted {
|
||||
// let transition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .spring)
|
||||
// transition.updateSublayerTransformScale(node: self, scale: 0.9)
|
||||
// } else {
|
||||
// let transition: ContainedViewLayoutTransition = .animated(duration: 0.5, curve: .spring)
|
||||
// transition.updateSublayerTransformScale(node: self, scale: 1.0)
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1172,10 +1172,12 @@ public final class GroupCallParticipantsContext {
|
||||
|
||||
public final class MemberEvent {
|
||||
public let peerId: PeerId
|
||||
public let canUnmute: Bool
|
||||
public let joined: Bool
|
||||
|
||||
public init(peerId: PeerId, joined: Bool) {
|
||||
public init(peerId: PeerId, canUnmute: Bool, joined: Bool) {
|
||||
self.peerId = peerId
|
||||
self.canUnmute = canUnmute
|
||||
self.joined = joined
|
||||
}
|
||||
}
|
||||
@ -1627,7 +1629,7 @@ public final class GroupCallParticipantsContext {
|
||||
if let index = updatedParticipants.firstIndex(where: { $0.peer.id == participantUpdate.peerId }) {
|
||||
updatedParticipants.remove(at: index)
|
||||
updatedTotalCount = max(0, updatedTotalCount - 1)
|
||||
strongSelf.memberEventsPipe.putNext(MemberEvent(peerId: participantUpdate.peerId, joined: false))
|
||||
strongSelf.memberEventsPipe.putNext(MemberEvent(peerId: participantUpdate.peerId, canUnmute: false, joined: false))
|
||||
} else if isVersionUpdate {
|
||||
updatedTotalCount = max(0, updatedTotalCount - 1)
|
||||
}
|
||||
@ -1650,7 +1652,7 @@ public final class GroupCallParticipantsContext {
|
||||
updatedParticipants.remove(at: index)
|
||||
} else if case .joined = participantUpdate.participationStatusChange {
|
||||
updatedTotalCount += 1
|
||||
strongSelf.memberEventsPipe.putNext(MemberEvent(peerId: participantUpdate.peerId, joined: true))
|
||||
strongSelf.memberEventsPipe.putNext(MemberEvent(peerId: participantUpdate.peerId, canUnmute: participantUpdate.muteState?.canUnmute ?? true, joined: true))
|
||||
}
|
||||
|
||||
var activityTimestamp: Double?
|
||||
|
@ -134,7 +134,6 @@ public final class CoveredStickerSet : Equatable {
|
||||
}
|
||||
|
||||
public func installStickerSetInteractively(account: Account, info: StickerPackCollectionInfo, items: [ItemCollectionItem]) -> Signal<InstallStickerSetResult, InstallStickerSetError> {
|
||||
|
||||
return account.network.request(Api.functions.messages.installStickerSet(stickerset: .inputStickerSetID(id: info.id.id, accessHash: info.accessHash), archived: .boolFalse)) |> mapError { _ -> InstallStickerSetError in
|
||||
return .generic
|
||||
} |> mapToSignal { result -> Signal<InstallStickerSetResult, InstallStickerSetError> in
|
||||
|
@ -406,6 +406,11 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
|
||||
let immediateThumbnailData: Data?
|
||||
var id: Int64
|
||||
switch item {
|
||||
case .custom:
|
||||
representations = []
|
||||
videoRepresentations = []
|
||||
immediateThumbnailData = nil
|
||||
id = 0
|
||||
case let .topImage(topRepresentations, videoRepresentationsValue, immediateThumbnail):
|
||||
representations = topRepresentations
|
||||
videoRepresentations = videoRepresentationsValue
|
||||
@ -696,6 +701,11 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode {
|
||||
let immediateThumbnailData: Data?
|
||||
var id: Int64
|
||||
switch item {
|
||||
case .custom:
|
||||
representations = []
|
||||
videoRepresentations = []
|
||||
immediateThumbnailData = nil
|
||||
id = 0
|
||||
case let .topImage(topRepresentations, videoRepresentationsValue, immediateThumbnail):
|
||||
representations = topRepresentations
|
||||
videoRepresentations = videoRepresentationsValue
|
||||
|
Loading…
x
Reference in New Issue
Block a user