mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-07-04 10:30:42 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
df923d4c7a
@ -87,9 +87,9 @@ public func peerAvatarImageData(account: Account, peerReference: PeerReference?,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func peerAvatarCompleteImage(account: Account, peer: Peer, size: CGSize, round: Bool = true, font: UIFont = avatarPlaceholderFont(size: 13.0), drawLetters: Bool = true, fullSize: Bool = false) -> Signal<UIImage?, NoError> {
|
public func peerAvatarCompleteImage(account: Account, peer: Peer, size: CGSize, round: Bool = true, font: UIFont = avatarPlaceholderFont(size: 13.0), drawLetters: Bool = true, fullSize: Bool = false, blurred: Bool = false) -> Signal<UIImage?, NoError> {
|
||||||
let iconSignal: Signal<UIImage?, NoError>
|
let iconSignal: Signal<UIImage?, NoError>
|
||||||
if let signal = peerAvatarImage(account: account, peerReference: PeerReference(peer), authorOfMessage: nil, representation: peer.profileImageRepresentations.first, displayDimensions: size, round: round, inset: 0.0, emptyColor: nil, synchronousLoad: fullSize) {
|
if let signal = peerAvatarImage(account: account, peerReference: PeerReference(peer), authorOfMessage: nil, representation: peer.profileImageRepresentations.first, displayDimensions: size, round: round, blurred: blurred, inset: 0.0, emptyColor: nil, synchronousLoad: fullSize) {
|
||||||
if fullSize, let fullSizeSignal = peerAvatarImage(account: account, peerReference: PeerReference(peer), authorOfMessage: nil, representation: peer.profileImageRepresentations.last, displayDimensions: size, emptyColor: nil, synchronousLoad: true) {
|
if fullSize, let fullSizeSignal = peerAvatarImage(account: account, peerReference: PeerReference(peer), authorOfMessage: nil, representation: peer.profileImageRepresentations.last, displayDimensions: size, emptyColor: nil, synchronousLoad: true) {
|
||||||
iconSignal = combineLatest(.single(nil) |> then(signal), .single(nil) |> then(fullSizeSignal))
|
iconSignal = combineLatest(.single(nil) |> then(signal), .single(nil) |> then(fullSizeSignal))
|
||||||
|> mapToSignal { thumbnailImage, fullSizeImage -> Signal<UIImage?, NoError> in
|
|> mapToSignal { thumbnailImage, fullSizeImage -> Signal<UIImage?, NoError> in
|
||||||
@ -120,6 +120,10 @@ public func peerAvatarCompleteImage(account: Account, peer: Peer, size: CGSize,
|
|||||||
let image = generateImage(size, rotatedContext: { size, context in
|
let image = generateImage(size, rotatedContext: { size, context in
|
||||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
drawPeerAvatarLetters(context: context, size: CGSize(width: size.width, height: size.height), round: round, font: font, letters: displayLetters, peerId: peerId)
|
drawPeerAvatarLetters(context: context, size: CGSize(width: size.width, height: size.height), round: round, font: font, letters: displayLetters, peerId: peerId)
|
||||||
|
if blurred {
|
||||||
|
context.setFillColor(UIColor(rgb: 0x000000, alpha: 0.45).cgColor)
|
||||||
|
context.fill(CGRect(origin: CGPoint(), size: size))
|
||||||
|
}
|
||||||
})?.withRenderingMode(.alwaysOriginal)
|
})?.withRenderingMode(.alwaysOriginal)
|
||||||
|
|
||||||
subscriber.putNext(image)
|
subscriber.putNext(image)
|
||||||
@ -130,7 +134,7 @@ public func peerAvatarCompleteImage(account: Account, peer: Peer, size: CGSize,
|
|||||||
return iconSignal
|
return iconSignal
|
||||||
}
|
}
|
||||||
|
|
||||||
public func peerAvatarImage(account: Account, peerReference: PeerReference?, authorOfMessage: MessageReference?, representation: TelegramMediaImageRepresentation?, displayDimensions: CGSize = CGSize(width: 60.0, height: 60.0), round: Bool = true, inset: CGFloat = 0.0, emptyColor: UIColor? = nil, synchronousLoad: Bool = false, provideUnrounded: Bool = false) -> Signal<(UIImage, UIImage)?, NoError>? {
|
public func peerAvatarImage(account: Account, peerReference: PeerReference?, authorOfMessage: MessageReference?, representation: TelegramMediaImageRepresentation?, displayDimensions: CGSize = CGSize(width: 60.0, height: 60.0), round: Bool = true, blurred: Bool = false, inset: CGFloat = 0.0, emptyColor: UIColor? = nil, synchronousLoad: Bool = false, provideUnrounded: Bool = false) -> Signal<(UIImage, UIImage)?, NoError>? {
|
||||||
if let imageData = peerAvatarImageData(account: account, peerReference: peerReference, authorOfMessage: authorOfMessage, representation: representation, synchronousLoad: synchronousLoad) {
|
if let imageData = peerAvatarImageData(account: account, peerReference: peerReference, authorOfMessage: authorOfMessage, representation: representation, synchronousLoad: synchronousLoad) {
|
||||||
return imageData
|
return imageData
|
||||||
|> mapToSignal { data -> Signal<(UIImage, UIImage)?, NoError> in
|
|> mapToSignal { data -> Signal<(UIImage, UIImage)?, NoError> in
|
||||||
@ -149,11 +153,22 @@ public func peerAvatarImage(account: Account, peerReference: PeerReference?, aut
|
|||||||
context.clip()
|
context.clip()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var shouldBlur = false
|
||||||
if case .blurred = dataType {
|
if case .blurred = dataType {
|
||||||
|
shouldBlur = true
|
||||||
|
} else if blurred {
|
||||||
|
shouldBlur = true
|
||||||
|
}
|
||||||
|
if shouldBlur {
|
||||||
let imageContextSize = CGSize(width: 64.0, height: 64.0)
|
let imageContextSize = CGSize(width: 64.0, height: 64.0)
|
||||||
let imageContext = DrawingContext(size: imageContextSize, scale: 1.0, premultiplied: true, clear: true)
|
let imageContext = DrawingContext(size: imageContextSize, scale: 1.0, premultiplied: true, clear: true)
|
||||||
imageContext.withFlippedContext { c in
|
imageContext.withFlippedContext { c in
|
||||||
c.draw(dataImage, in: CGRect(origin: CGPoint(), size: imageContextSize))
|
c.draw(dataImage, in: CGRect(origin: CGPoint(), size: imageContextSize))
|
||||||
|
|
||||||
|
context.setBlendMode(.saturation)
|
||||||
|
context.setFillColor(UIColor(rgb: 0xffffff, alpha: 1.0).cgColor)
|
||||||
|
context.fill(CGRect(origin: CGPoint(), size: size))
|
||||||
|
context.setBlendMode(.copy)
|
||||||
}
|
}
|
||||||
|
|
||||||
telegramFastBlurMore(Int32(imageContext.size.width * imageContext.scale), Int32(imageContext.size.height * imageContext.scale), Int32(imageContext.bytesPerRow), imageContext.bytes)
|
telegramFastBlurMore(Int32(imageContext.size.width * imageContext.scale), Int32(imageContext.size.height * imageContext.scale), Int32(imageContext.bytesPerRow), imageContext.bytes)
|
||||||
@ -162,6 +177,12 @@ public func peerAvatarImage(account: Account, peerReference: PeerReference?, aut
|
|||||||
}
|
}
|
||||||
|
|
||||||
context.draw(dataImage, in: CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
|
context.draw(dataImage, in: CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
|
||||||
|
if blurred {
|
||||||
|
context.setBlendMode(.normal)
|
||||||
|
context.setFillColor(UIColor(rgb: 0x000000, alpha: 0.45).cgColor)
|
||||||
|
context.fill(CGRect(origin: CGPoint(), size: size))
|
||||||
|
context.setBlendMode(.copy)
|
||||||
|
}
|
||||||
if round {
|
if round {
|
||||||
if displayDimensions.width == 60.0 {
|
if displayDimensions.width == 60.0 {
|
||||||
context.setBlendMode(.destinationOut)
|
context.setBlendMode(.destinationOut)
|
||||||
|
@ -1400,7 +1400,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
|||||||
contentHeight = layout.size.height
|
contentHeight = layout.size.height
|
||||||
} else {
|
} else {
|
||||||
let totalHeight = originalContentFrame.height + originalActionsFrame.height
|
let totalHeight = originalContentFrame.height + originalActionsFrame.height
|
||||||
contentContainerFrame = CGRect(origin: CGPoint(x: originalContentFrame.minX - contentParentNode.contentRect.minX, y: floor((layout.size.height - totalHeight) / 2.0)), size: originalContentFrame.size)
|
contentContainerFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - originalContentFrame.width) / 2.0), y: floor((layout.size.height - totalHeight) / 2.0)), size: originalContentFrame.size)
|
||||||
originalActionsFrame.origin.y = contentContainerFrame.maxY + contentActionsSpacing
|
originalActionsFrame.origin.y = contentContainerFrame.maxY + contentActionsSpacing
|
||||||
}
|
}
|
||||||
} else if keepInPlace {
|
} else if keepInPlace {
|
||||||
|
@ -126,7 +126,7 @@ public class ImageNode: ASDisplayNode {
|
|||||||
private let hasImage: ValuePromise<Bool>?
|
private let hasImage: ValuePromise<Bool>?
|
||||||
private var first = true
|
private var first = true
|
||||||
private let enableEmpty: Bool
|
private let enableEmpty: Bool
|
||||||
private let enableAnimatedTransition: Bool
|
public var enableAnimatedTransition: Bool
|
||||||
|
|
||||||
private let _contentReady = Promise<Bool>()
|
private let _contentReady = Promise<Bool>()
|
||||||
private var didSetReady: Bool = false
|
private var didSetReady: Bool = false
|
||||||
|
@ -365,7 +365,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func tileItem(context: AccountContext, presentationData: PresentationData, interaction: Interaction, videoEndpointId: String) -> VoiceChatTileItem? {
|
func tileItem(context: AccountContext, presentationData: PresentationData, interaction: Interaction, videoEndpointId: String, videoReady: Bool) -> VoiceChatTileItem? {
|
||||||
guard case let .peer(peerEntry, _) = self else {
|
guard case let .peer(peerEntry, _) = self else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -373,6 +373,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
|
|
||||||
let icon: VoiceChatTileItem.Icon
|
let icon: VoiceChatTileItem.Icon
|
||||||
var text: VoiceChatParticipantItem.ParticipantText
|
var text: VoiceChatParticipantItem.ParticipantText
|
||||||
|
var additionalText: VoiceChatParticipantItem.ParticipantText?
|
||||||
var speaking = false
|
var speaking = false
|
||||||
|
|
||||||
var textIcon = VoiceChatParticipantItem.ParticipantText.TextIcon()
|
var textIcon = VoiceChatParticipantItem.ParticipantText.TextIcon()
|
||||||
@ -404,6 +405,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
}
|
}
|
||||||
if let muteState = peerEntry.muteState, muteState.mutedByYou {
|
if let muteState = peerEntry.muteState, muteState.mutedByYou {
|
||||||
icon = .microphone(true)
|
icon = .microphone(true)
|
||||||
|
additionalText = .text(presentationData.strings.VoiceChat_StatusMutedForYou, textIcon, .destructive)
|
||||||
} else {
|
} else {
|
||||||
icon = .microphone(peerEntry.muteState != nil)
|
icon = .microphone(peerEntry.muteState != nil)
|
||||||
}
|
}
|
||||||
@ -411,6 +413,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
if let muteState = peerEntry.muteState, muteState.mutedByYou {
|
if let muteState = peerEntry.muteState, muteState.mutedByYou {
|
||||||
text = .text(presentationData.strings.VoiceChat_StatusMutedForYou, textIcon, .destructive)
|
text = .text(presentationData.strings.VoiceChat_StatusMutedForYou, textIcon, .destructive)
|
||||||
icon = .microphone(true)
|
icon = .microphone(true)
|
||||||
|
additionalText = .text(presentationData.strings.VoiceChat_StatusMutedForYou, textIcon, .destructive)
|
||||||
} else {
|
} else {
|
||||||
if peerEntry.volume != nil {
|
if peerEntry.volume != nil {
|
||||||
textIcon.insert(.volume)
|
textIcon.insert(.volume)
|
||||||
@ -433,7 +436,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
text = .text(about, textIcon, .generic)
|
text = .text(about, textIcon, .generic)
|
||||||
}
|
}
|
||||||
|
|
||||||
return VoiceChatTileItem(peer: peerEntry.peer, videoEndpointId: videoEndpointId, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, speaking: speaking, icon: icon, text: text, action: {
|
return VoiceChatTileItem(account: context.account, peer: peerEntry.peer, videoEndpointId: videoEndpointId, videoReady: videoReady, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, speaking: speaking, icon: icon, text: text, additionalText: additionalText, action: {
|
||||||
interaction.switchToPeer(peer.id, videoEndpointId, true)
|
interaction.switchToPeer(peer.id, videoEndpointId, true)
|
||||||
}, contextAction: { node, gesture in
|
}, contextAction: { node, gesture in
|
||||||
interaction.peerContextAction(peerEntry, node, gesture)
|
interaction.peerContextAction(peerEntry, node, gesture)
|
||||||
@ -528,7 +531,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
text = .text(about, textIcon, .generic)
|
text = .text(about, textIcon, .generic)
|
||||||
}
|
}
|
||||||
|
|
||||||
return VoiceChatFullscreenParticipantItem(presentationData: ItemListPresentationData(presentationData), nameDisplayOrder: presentationData.nameDisplayOrder, context: context, peer: peerEntry.peer, icon: icon, text: text, textColor: textColor, color: color, isLandscape: peerEntry.isLandscape, active: peerEntry.active, getAudioLevel: { return interaction.getAudioLevel(peerEntry.peer.id) }, getVideo: {
|
return VoiceChatFullscreenParticipantItem(presentationData: ItemListPresentationData(presentationData), nameDisplayOrder: presentationData.nameDisplayOrder, context: context, peer: peerEntry.peer, videoEndpointId: peerEntry.effectiveVideoEndpointId, icon: icon, text: text, textColor: textColor, color: color, isLandscape: peerEntry.isLandscape, active: peerEntry.active, getAudioLevel: { return interaction.getAudioLevel(peerEntry.peer.id) }, getVideo: {
|
||||||
if let endpointId = peerEntry.effectiveVideoEndpointId {
|
if let endpointId = peerEntry.effectiveVideoEndpointId {
|
||||||
return interaction.getPeerVideo(endpointId, .list)
|
return interaction.getPeerVideo(endpointId, .list)
|
||||||
} else {
|
} else {
|
||||||
@ -3575,7 +3578,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
let isFirstTime = self.validLayout == nil
|
let isFirstTime = self.validLayout == nil
|
||||||
let previousLayout = self.validLayout?.0
|
let previousLayout = self.validLayout?.0
|
||||||
self.validLayout = (layout, navigationHeight)
|
self.validLayout = (layout, navigationHeight)
|
||||||
|
|
||||||
var size = layout.size
|
var size = layout.size
|
||||||
let contentWidth: CGFloat
|
let contentWidth: CGFloat
|
||||||
if case .regular = layout.metrics.widthClass {
|
if case .regular = layout.metrics.widthClass {
|
||||||
@ -3696,6 +3699,15 @@ public final class VoiceChatController: ViewController {
|
|||||||
self.fullscreenListNode.transform = fullscreenListTransform
|
self.fullscreenListNode.transform = fullscreenListTransform
|
||||||
self.fullscreenListNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: fullscreenListUpdateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
self.fullscreenListNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: fullscreenListUpdateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||||
|
|
||||||
|
|
||||||
|
var childrenLayout = layout
|
||||||
|
var childrenInsets = childrenLayout.intrinsicInsets
|
||||||
|
if !isLandscape, case .fullscreen = effectiveDisplayMode {
|
||||||
|
childrenInsets.bottom += self.effectiveBottomAreaHeight + fullscreenListHeight + 30.0
|
||||||
|
}
|
||||||
|
childrenLayout.intrinsicInsets = childrenInsets
|
||||||
|
self.controller?.presentationContext.containerLayoutUpdated(childrenLayout, transition: transition)
|
||||||
|
|
||||||
transition.updateFrame(node: self.topCornersNode, frame: CGRect(origin: CGPoint(x: sideInset + floorToScreenPixels((size.width - contentWidth) / 2.0), y: topCornersY), size: CGSize(width: contentWidth - sideInset * 2.0, height: 50.0)))
|
transition.updateFrame(node: self.topCornersNode, frame: CGRect(origin: CGPoint(x: sideInset + floorToScreenPixels((size.width - contentWidth) / 2.0), y: topCornersY), size: CGSize(width: contentWidth - 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))
|
var bottomPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - bottomPanelHeight), size: CGSize(width: size.width, height: bottomPanelHeight))
|
||||||
@ -4278,11 +4290,11 @@ public final class VoiceChatController: ViewController {
|
|||||||
|
|
||||||
var isTile = false
|
var isTile = false
|
||||||
if let interaction = self.itemInteraction {
|
if let interaction = self.itemInteraction {
|
||||||
if let videoEndpointId = member.presentationEndpointId, self.readyVideoNodes.contains(videoEndpointId) {
|
if let videoEndpointId = member.presentationEndpointId {
|
||||||
if !self.videoOrder.contains(videoEndpointId) {
|
if !self.videoOrder.contains(videoEndpointId) {
|
||||||
self.videoOrder.append(videoEndpointId)
|
self.videoOrder.append(videoEndpointId)
|
||||||
}
|
}
|
||||||
if let tileItem = ListEntry.peer(peerEntry, 0).tileItem(context: self.context, presentationData: self.presentationData, interaction: interaction, videoEndpointId: videoEndpointId) {
|
if let tileItem = ListEntry.peer(peerEntry, 0).tileItem(context: self.context, presentationData: self.presentationData, interaction: interaction, videoEndpointId: videoEndpointId, videoReady: self.readyVideoNodes.contains(videoEndpointId)) {
|
||||||
isTile = true
|
isTile = true
|
||||||
tileByVideoEndpoint[videoEndpointId] = tileItem
|
tileByVideoEndpoint[videoEndpointId] = tileItem
|
||||||
}
|
}
|
||||||
@ -4290,11 +4302,11 @@ public final class VoiceChatController: ViewController {
|
|||||||
latestWideVideo = videoEndpointId
|
latestWideVideo = videoEndpointId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let videoEndpointId = member.videoEndpointId, self.readyVideoNodes.contains(videoEndpointId) {
|
if let videoEndpointId = member.videoEndpointId {
|
||||||
if !self.videoOrder.contains(videoEndpointId) {
|
if !self.videoOrder.contains(videoEndpointId) {
|
||||||
self.videoOrder.append(videoEndpointId)
|
self.videoOrder.append(videoEndpointId)
|
||||||
}
|
}
|
||||||
if let tileItem = ListEntry.peer(peerEntry, 0).tileItem(context: self.context, presentationData: self.presentationData, interaction: interaction, videoEndpointId: videoEndpointId) {
|
if let tileItem = ListEntry.peer(peerEntry, 0).tileItem(context: self.context, presentationData: self.presentationData, interaction: interaction, videoEndpointId: videoEndpointId, videoReady: self.readyVideoNodes.contains(videoEndpointId)) {
|
||||||
isTile = true
|
isTile = true
|
||||||
tileByVideoEndpoint[videoEndpointId] = tileItem
|
tileByVideoEndpoint[videoEndpointId] = tileItem
|
||||||
}
|
}
|
||||||
@ -4680,7 +4692,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
self.mainStageNode.setControlsHidden(true, animated: true)
|
self.mainStageNode.setControlsHidden(true, animated: true)
|
||||||
|
|
||||||
self.fullscreenListNode.alpha = 0.0
|
self.fullscreenListNode.alpha = 0.0
|
||||||
self.fullscreenListNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, completion: { [weak self] _ in
|
self.fullscreenListNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, completion: { [weak self] finished in
|
||||||
self?.attachTileVideos()
|
self?.attachTileVideos()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -4729,6 +4741,11 @@ public final class VoiceChatController: ViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case .fullscreen:
|
case .fullscreen:
|
||||||
|
if fabs(translation) > 32.0 {
|
||||||
|
if self.fullscreenListNode.layer.animationKeys()?.contains("opacity") == true {
|
||||||
|
self.fullscreenListNode.layer.removeAllAnimations()
|
||||||
|
}
|
||||||
|
}
|
||||||
var bounds = self.mainStageContainerNode.bounds
|
var bounds = self.mainStageContainerNode.bounds
|
||||||
bounds.origin.y = -translation
|
bounds.origin.y = -translation
|
||||||
self.mainStageContainerNode.bounds = bounds
|
self.mainStageContainerNode.bounds = bounds
|
||||||
@ -5264,42 +5281,44 @@ public final class VoiceChatController: ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func attachFullscreenVideos() {
|
private func attachFullscreenVideos() {
|
||||||
var verticalItemNodes: [PeerId: ASDisplayNode] = [:]
|
var verticalItemNodes: [String: ASDisplayNode] = [:]
|
||||||
self.listNode.forEachItemNode { itemNode in
|
self.listNode.forEachItemNode { itemNode in
|
||||||
if let itemNode = itemNode as? VoiceChatTilesGridItemNode {
|
if let itemNode = itemNode as? VoiceChatTilesGridItemNode {
|
||||||
for tileNode in itemNode.tileNodes {
|
for tileNode in itemNode.tileNodes {
|
||||||
if let item = tileNode.item {
|
if let item = tileNode.item {
|
||||||
verticalItemNodes[item.peer.id] = tileNode
|
verticalItemNodes[String(item.peer.id.toInt64()) + "_" + item.videoEndpointId] = tileNode
|
||||||
}
|
}
|
||||||
|
|
||||||
if tileNode.item?.peer.id == self.effectiveSpeaker?.0 {
|
if tileNode.item?.peer.id == self.effectiveSpeaker?.0 && tileNode.item?.videoEndpointId == self.effectiveSpeaker?.1 {
|
||||||
tileNode.isHidden = false
|
tileNode.isHidden = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.fullscreenListNode.forEachItemNode { itemNode in
|
self.fullscreenListNode.forEachItemNode { itemNode in
|
||||||
if let itemNode = itemNode as? VoiceChatFullscreenParticipantItemNode, let item = itemNode.item, let otherItemNode = verticalItemNodes[item.peer.id] {
|
if let itemNode = itemNode as? VoiceChatFullscreenParticipantItemNode, let item = itemNode.item, let otherItemNode = verticalItemNodes[String(item.peer.id.toInt64()) + "_" + (item.videoEndpointId ?? "")] {
|
||||||
itemNode.animateTransitionIn(from: otherItemNode, containerNode: self.transitionContainerNode, transition: .immediate, animate: false)
|
itemNode.animateTransitionIn(from: otherItemNode, containerNode: self.transitionContainerNode, transition: .immediate, animate: false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func attachTileVideos() {
|
private func attachTileVideos() {
|
||||||
var fullscreenItemNodes: [PeerId: VoiceChatFullscreenParticipantItemNode] = [:]
|
var fullscreenItemNodes: [String: VoiceChatFullscreenParticipantItemNode] = [:]
|
||||||
self.fullscreenListNode.forEachItemNode { itemNode in
|
self.fullscreenListNode.forEachItemNode { itemNode in
|
||||||
if let itemNode = itemNode as? VoiceChatFullscreenParticipantItemNode, let item = itemNode.item {
|
if let itemNode = itemNode as? VoiceChatFullscreenParticipantItemNode, let item = itemNode.item {
|
||||||
fullscreenItemNodes[item.peer.id] = itemNode
|
fullscreenItemNodes[String(item.peer.id.toInt64()) + "_" + (item.videoEndpointId ?? "")] = itemNode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.listNode.forEachItemNode { itemNode in
|
self.listNode.forEachItemNode { itemNode in
|
||||||
if let itemNode = itemNode as? VoiceChatTilesGridItemNode {
|
if let itemNode = itemNode as? VoiceChatTilesGridItemNode {
|
||||||
for tileNode in itemNode.tileNodes {
|
for tileNode in itemNode.tileNodes {
|
||||||
if let item = tileNode.item, let otherItemNode = fullscreenItemNodes[item.peer.id] {
|
if let item = tileNode.item {
|
||||||
tileNode.animateTransitionIn(from: otherItemNode, containerNode: self.transitionContainerNode, transition: .immediate, animate: false)
|
if let otherItemNode = fullscreenItemNodes[String(item.peer.id.toInt64()) + "_" + item.videoEndpointId] {
|
||||||
|
tileNode.animateTransitionIn(from: otherItemNode, containerNode: self.transitionContainerNode, transition: .immediate, animate: false)
|
||||||
|
}
|
||||||
|
|
||||||
if tileNode.item?.peer.id == self.effectiveSpeaker?.0 {
|
if tileNode.item?.peer.id == self.effectiveSpeaker?.0 && tileNode.item?.videoEndpointId == self.effectiveSpeaker?.1 {
|
||||||
tileNode.isHidden = true
|
tileNode.isHidden = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -5336,7 +5355,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
self.updateDecorationsLayout(transition: .immediate)
|
self.updateDecorationsLayout(transition: .immediate)
|
||||||
|
|
||||||
var minimalVisiblePeerid: (PeerId, CGPoint)?
|
var minimalVisiblePeerid: (PeerId, CGPoint)?
|
||||||
var verticalItemNodes: [PeerId: ASDisplayNode] = [:]
|
var verticalItemNodes: [String: ASDisplayNode] = [:]
|
||||||
self.listNode.forEachItemNode { itemNode in
|
self.listNode.forEachItemNode { itemNode in
|
||||||
if let itemNode = itemNode as? VoiceChatTilesGridItemNode {
|
if let itemNode = itemNode as? VoiceChatTilesGridItemNode {
|
||||||
for tileNode in itemNode.tileNodes {
|
for tileNode in itemNode.tileNodes {
|
||||||
@ -5351,7 +5370,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
minimalVisiblePeerid = (item.peer.id, convertedFrame.origin)
|
minimalVisiblePeerid = (item.peer.id, convertedFrame.origin)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
verticalItemNodes[item.peer.id] = tileNode
|
verticalItemNodes[String(item.peer.id.toInt64()) + "_" + item.videoEndpointId] = tileNode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if let itemNode = itemNode as? VoiceChatParticipantItemNode, let item = itemNode.item {
|
} else if let itemNode = itemNode as? VoiceChatParticipantItemNode, let item = itemNode.item {
|
||||||
@ -5365,7 +5384,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
minimalVisiblePeerid = (item.peer.id, convertedFrame.origin)
|
minimalVisiblePeerid = (item.peer.id, convertedFrame.origin)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
verticalItemNodes[item.peer.id] = itemNode
|
verticalItemNodes[String(item.peer.id.toInt64()) + "_"] = itemNode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -5373,7 +5392,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
|
|
||||||
let completion = {
|
let completion = {
|
||||||
let effectiveSpeakerPeerId = self.effectiveSpeaker?.0
|
let effectiveSpeakerPeerId = self.effectiveSpeaker?.0
|
||||||
if let effectiveSpeakerPeerId = effectiveSpeakerPeerId, let otherItemNode = verticalItemNodes[effectiveSpeakerPeerId] {
|
if let effectiveSpeakerPeerId = effectiveSpeakerPeerId, let otherItemNode = verticalItemNodes[String(effectiveSpeakerPeerId.toInt64()) + "_" + (self.effectiveSpeaker?.1 ?? "")] {
|
||||||
self.mainStageNode.animateTransitionIn(from: otherItemNode, transition: transition)
|
self.mainStageNode.animateTransitionIn(from: otherItemNode, transition: transition)
|
||||||
|
|
||||||
self.mainStageBackgroundNode.alpha = 1.0
|
self.mainStageBackgroundNode.alpha = 1.0
|
||||||
@ -5382,7 +5401,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
|
|
||||||
self.fullscreenListNode.forEachItemNode { itemNode in
|
self.fullscreenListNode.forEachItemNode { itemNode in
|
||||||
if let itemNode = itemNode as? VoiceChatFullscreenParticipantItemNode, let item = itemNode.item {
|
if let itemNode = itemNode as? VoiceChatFullscreenParticipantItemNode, let item = itemNode.item {
|
||||||
itemNode.animateTransitionIn(from: verticalItemNodes[item.peer.id], containerNode: self.transitionContainerNode, transition: transition, animate: item.peer.id != effectiveSpeakerPeerId)
|
itemNode.animateTransitionIn(from: verticalItemNodes[String(item.peer.id.toInt64()) + "_" + (item.videoEndpointId ?? "")], containerNode: self.transitionContainerNode, transition: transition, animate: item.peer.id != effectiveSpeakerPeerId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -5422,7 +5441,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
})
|
})
|
||||||
} else if case .fullscreen = previousDisplayMode, case .modal = self.displayMode {
|
} else if case .fullscreen = previousDisplayMode, case .modal = self.displayMode {
|
||||||
var minimalVisiblePeerid: (PeerId, CGFloat)?
|
var minimalVisiblePeerid: (PeerId, CGFloat)?
|
||||||
var fullscreenItemNodes: [PeerId: VoiceChatFullscreenParticipantItemNode] = [:]
|
var fullscreenItemNodes: [String: VoiceChatFullscreenParticipantItemNode] = [:]
|
||||||
self.fullscreenListNode.forEachItemNode { itemNode in
|
self.fullscreenListNode.forEachItemNode { itemNode in
|
||||||
if let itemNode = itemNode as? VoiceChatFullscreenParticipantItemNode, let item = itemNode.item {
|
if let itemNode = itemNode as? VoiceChatFullscreenParticipantItemNode, let item = itemNode.item {
|
||||||
let convertedFrame = itemNode.view.convert(itemNode.bounds, to: self.transitionContainerNode.view)
|
let convertedFrame = itemNode.view.convert(itemNode.bounds, to: self.transitionContainerNode.view)
|
||||||
@ -5433,7 +5452,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
} else if convertedFrame.minX >= 0.0 {
|
} else if convertedFrame.minX >= 0.0 {
|
||||||
minimalVisiblePeerid = (item.peer.id, convertedFrame.minX)
|
minimalVisiblePeerid = (item.peer.id, convertedFrame.minX)
|
||||||
}
|
}
|
||||||
fullscreenItemNodes[item.peer.id] = itemNode
|
fullscreenItemNodes[String(item.peer.id.toInt64()) + "_" + (item.videoEndpointId ?? "")] = itemNode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -5448,17 +5467,19 @@ public final class VoiceChatController: ViewController {
|
|||||||
self.listNode.forEachItemNode { itemNode in
|
self.listNode.forEachItemNode { itemNode in
|
||||||
if let itemNode = itemNode as? VoiceChatTilesGridItemNode {
|
if let itemNode = itemNode as? VoiceChatTilesGridItemNode {
|
||||||
for tileNode in itemNode.tileNodes {
|
for tileNode in itemNode.tileNodes {
|
||||||
if let item = tileNode.item, let otherItemNode = fullscreenItemNodes[item.peer.id] {
|
if let item = tileNode.item {
|
||||||
if !fromPan || item.peer.id == effectiveSpeakerPeerId {
|
if let otherItemNode = fullscreenItemNodes[String(item.peer.id.toInt64()) + "_" + item.videoEndpointId] {
|
||||||
tileNode.animateTransitionIn(from: otherItemNode, containerNode: self.transitionContainerNode, transition: transition, animate: item.peer.id != effectiveSpeakerPeerId)
|
if !fromPan || item.peer.id == effectiveSpeakerPeerId {
|
||||||
|
tileNode.animateTransitionIn(from: otherItemNode, containerNode: self.transitionContainerNode, transition: transition, animate: item.peer.id != effectiveSpeakerPeerId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if item.peer.id == effectiveSpeakerPeerId {
|
if item.peer.id == effectiveSpeakerPeerId, item.videoEndpointId == self.effectiveSpeaker?.1 {
|
||||||
targetTileNode = tileNode
|
targetTileNode = tileNode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if let itemNode = itemNode as? VoiceChatParticipantItemNode, let item = itemNode.item, let otherItemNode = fullscreenItemNodes[item.peer.id] {
|
} else if let itemNode = itemNode as? VoiceChatParticipantItemNode, let item = itemNode.item, let otherItemNode = fullscreenItemNodes[String(item.peer.id.toInt64()) + "_"] {
|
||||||
if !fromPan {
|
if !fromPan {
|
||||||
itemNode.animateTransitionIn(from: otherItemNode, containerNode: self.transitionContainerNode, transition: transition)
|
itemNode.animateTransitionIn(from: otherItemNode, containerNode: self.transitionContainerNode, transition: transition)
|
||||||
}
|
}
|
||||||
@ -5643,6 +5664,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
|
|
||||||
super.init(navigationBarPresentationData: nil)
|
super.init(navigationBarPresentationData: nil)
|
||||||
|
|
||||||
|
self.automaticallyControlPresentationContextLayout = false
|
||||||
self.blocksBackgroundWhenInOverlay = true
|
self.blocksBackgroundWhenInOverlay = true
|
||||||
|
|
||||||
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .all)
|
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .all)
|
||||||
|
@ -67,6 +67,7 @@ final class VoiceChatFullscreenParticipantItem: ListViewItem {
|
|||||||
let nameDisplayOrder: PresentationPersonNameOrder
|
let nameDisplayOrder: PresentationPersonNameOrder
|
||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
let peer: Peer
|
let peer: Peer
|
||||||
|
let videoEndpointId: String?
|
||||||
let icon: Icon
|
let icon: Icon
|
||||||
let text: VoiceChatParticipantItem.ParticipantText
|
let text: VoiceChatParticipantItem.ParticipantText
|
||||||
let textColor: Color
|
let textColor: Color
|
||||||
@ -81,11 +82,12 @@ final class VoiceChatFullscreenParticipantItem: ListViewItem {
|
|||||||
|
|
||||||
public let selectable: Bool = true
|
public let selectable: Bool = true
|
||||||
|
|
||||||
public init(presentationData: ItemListPresentationData, nameDisplayOrder: PresentationPersonNameOrder, context: AccountContext, peer: Peer, icon: Icon, text: VoiceChatParticipantItem.ParticipantText, textColor: Color, color: Color, isLandscape: Bool, active: Bool, getAudioLevel: (() -> Signal<Float, NoError>)?, getVideo: @escaping () -> GroupVideoNode?, action: ((ASDisplayNode?) -> Void)?, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? = nil, getUpdatingAvatar: @escaping () -> Signal<(TelegramMediaImageRepresentation, Float)?, NoError>) {
|
public init(presentationData: ItemListPresentationData, nameDisplayOrder: PresentationPersonNameOrder, context: AccountContext, peer: Peer, videoEndpointId: String?, icon: Icon, text: VoiceChatParticipantItem.ParticipantText, textColor: Color, color: Color, isLandscape: Bool, active: Bool, getAudioLevel: (() -> Signal<Float, NoError>)?, getVideo: @escaping () -> GroupVideoNode?, action: ((ASDisplayNode?) -> Void)?, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? = nil, getUpdatingAvatar: @escaping () -> Signal<(TelegramMediaImageRepresentation, Float)?, NoError>) {
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
self.nameDisplayOrder = nameDisplayOrder
|
self.nameDisplayOrder = nameDisplayOrder
|
||||||
self.context = context
|
self.context = context
|
||||||
self.peer = peer
|
self.peer = peer
|
||||||
|
self.videoEndpointId = videoEndpointId
|
||||||
self.icon = icon
|
self.icon = icon
|
||||||
self.text = text
|
self.text = text
|
||||||
self.textColor = textColor
|
self.textColor = textColor
|
||||||
|
@ -24,6 +24,7 @@ private let backArrowImage = NavigationBarTheme.generateBackArrowImage(color: .w
|
|||||||
private let backgroundCornerRadius: CGFloat = 11.0
|
private let backgroundCornerRadius: CGFloat = 11.0
|
||||||
private let fadeColor = UIColor(rgb: 0x000000, alpha: 0.5)
|
private let fadeColor = UIColor(rgb: 0x000000, alpha: 0.5)
|
||||||
private let fadeHeight: CGFloat = 50.0
|
private let fadeHeight: CGFloat = 50.0
|
||||||
|
private let destructiveColor: UIColor = UIColor(rgb: 0xff3b30)
|
||||||
|
|
||||||
final class VoiceChatMainStageNode: ASDisplayNode {
|
final class VoiceChatMainStageNode: ASDisplayNode {
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
@ -503,28 +504,27 @@ final class VoiceChatMainStageNode: ASDisplayNode {
|
|||||||
func update(peerEntry: VoiceChatPeerEntry, pinned: Bool) {
|
func update(peerEntry: VoiceChatPeerEntry, pinned: Bool) {
|
||||||
let previousPeerEntry = self.currentPeerEntry
|
let previousPeerEntry = self.currentPeerEntry
|
||||||
self.currentPeerEntry = peerEntry
|
self.currentPeerEntry = peerEntry
|
||||||
|
|
||||||
|
let peer = peerEntry.peer
|
||||||
|
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||||
if !arePeersEqual(previousPeerEntry?.peer, peerEntry.peer) {
|
if !arePeersEqual(previousPeerEntry?.peer, peerEntry.peer) {
|
||||||
let peer = peerEntry.peer
|
|
||||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
|
||||||
self.backdropAvatarNode.setSignal(peerAvatarCompleteImage(account: self.context.account, peer: peer, size: CGSize(width: 180.0, height: 180.0), round: false, font: avatarPlaceholderFont(size: 78.0), drawLetters: false))
|
self.backdropAvatarNode.setSignal(peerAvatarCompleteImage(account: self.context.account, peer: peer, size: CGSize(width: 180.0, height: 180.0), round: false, font: avatarPlaceholderFont(size: 78.0), drawLetters: false))
|
||||||
self.avatarNode.setSignal(peerAvatarCompleteImage(account: self.context.account, peer: peer, size: CGSize(width: 180.0, height: 180.0), font: avatarPlaceholderFont(size: 78.0), fullSize: true))
|
self.avatarNode.setSignal(peerAvatarCompleteImage(account: self.context.account, peer: peer, size: CGSize(width: 180.0, height: 180.0), font: avatarPlaceholderFont(size: 78.0), fullSize: true))
|
||||||
self.titleNode.attributedText = NSAttributedString(string: peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), font: Font.semibold(15.0), textColor: .white)
|
|
||||||
if let (size, sideInset, bottomInset, isLandscape) = self.validLayout {
|
|
||||||
self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, isLandscape: isLandscape, transition: .immediate)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var gradient: VoiceChatBlobNode.Gradient = .active
|
var gradient: VoiceChatBlobNode.Gradient = .active
|
||||||
var muted = false
|
var muted = false
|
||||||
var state = peerEntry.state
|
var state = peerEntry.state
|
||||||
if let muteState = peerEntry.muteState, case .speaking = state, muteState.mutedByYou || !muteState.canUnmute {
|
if let muteState = peerEntry.muteState, case .speaking = state, muteState.mutedByYou || !muteState.canUnmute {
|
||||||
state = .listening
|
state = .listening
|
||||||
}
|
}
|
||||||
|
var mutedForYou = false
|
||||||
switch state {
|
switch state {
|
||||||
case .listening:
|
case .listening:
|
||||||
if let muteState = peerEntry.muteState, muteState.mutedByYou {
|
if let muteState = peerEntry.muteState, muteState.mutedByYou {
|
||||||
gradient = .muted
|
gradient = .muted
|
||||||
muted = true
|
muted = true
|
||||||
|
mutedForYou = true
|
||||||
} else {
|
} else {
|
||||||
gradient = .active
|
gradient = .active
|
||||||
muted = peerEntry.muteState != nil
|
muted = peerEntry.muteState != nil
|
||||||
@ -533,6 +533,7 @@ final class VoiceChatMainStageNode: ASDisplayNode {
|
|||||||
if let muteState = peerEntry.muteState, muteState.mutedByYou {
|
if let muteState = peerEntry.muteState, muteState.mutedByYou {
|
||||||
gradient = .muted
|
gradient = .muted
|
||||||
muted = true
|
muted = true
|
||||||
|
mutedForYou = true
|
||||||
} else {
|
} else {
|
||||||
gradient = .speaking
|
gradient = .speaking
|
||||||
muted = false
|
muted = false
|
||||||
@ -540,6 +541,21 @@ final class VoiceChatMainStageNode: ASDisplayNode {
|
|||||||
default:
|
default:
|
||||||
muted = true
|
muted = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var microphoneColor = UIColor.white
|
||||||
|
var titleAttributedString = NSAttributedString(string: peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), font: Font.semibold(15.0), textColor: .white)
|
||||||
|
if mutedForYou {
|
||||||
|
microphoneColor = destructiveColor
|
||||||
|
|
||||||
|
let updatedString = NSMutableAttributedString(attributedString: titleAttributedString)
|
||||||
|
updatedString.append(NSAttributedString(string: " \(presentationData.strings.VoiceChat_StatusMutedForYou)", font: Font.regular(15.0), textColor: UIColor.white))
|
||||||
|
titleAttributedString = updatedString
|
||||||
|
}
|
||||||
|
self.titleNode.attributedText = titleAttributedString
|
||||||
|
if let (size, sideInset, bottomInset, isLandscape) = self.validLayout {
|
||||||
|
self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, isLandscape: isLandscape, transition: .immediate)
|
||||||
|
}
|
||||||
|
|
||||||
self.audioLevelNode.updateGlowAndGradientAnimations(type: gradient, animated: true)
|
self.audioLevelNode.updateGlowAndGradientAnimations(type: gradient, animated: true)
|
||||||
|
|
||||||
self.pinButtonTitleNode.isHidden = !pinned
|
self.pinButtonTitleNode.isHidden = !pinned
|
||||||
@ -571,7 +587,7 @@ final class VoiceChatMainStageNode: ASDisplayNode {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
self.microphoneNode.update(state: VoiceChatMicrophoneNode.State(muted: muted, filled: true, color: .white), animated: true)
|
self.microphoneNode.update(state: VoiceChatMicrophoneNode.State(muted: muted, filled: true, color: microphoneColor), animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setAvatarHidden(_ hidden: Bool) {
|
private func setAvatarHidden(_ hidden: Bool) {
|
||||||
@ -598,6 +614,7 @@ final class VoiceChatMainStageNode: ASDisplayNode {
|
|||||||
if previousPeer?.0 == peer?.0 && self.appeared {
|
if previousPeer?.0 == peer?.0 && self.appeared {
|
||||||
delayTransition = true
|
delayTransition = true
|
||||||
}
|
}
|
||||||
|
let appeared = self.appeared
|
||||||
|
|
||||||
if !delayTransition {
|
if !delayTransition {
|
||||||
self.setAvatarHidden(true)
|
self.setAvatarHidden(true)
|
||||||
@ -671,8 +688,10 @@ final class VoiceChatMainStageNode: ASDisplayNode {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if let previousVideoNode = previousVideoNode {
|
Queue.mainQueue().after(0.07) {
|
||||||
previousVideoNode.removeFromSupernode()
|
if let previousVideoNode = previousVideoNode {
|
||||||
|
previousVideoNode.removeFromSupernode()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,8 @@ final class VoiceChatTileGridNode: ASDisplayNode {
|
|||||||
fileprivate var itemNodes: [String: VoiceChatTileItemNode] = [:]
|
fileprivate var itemNodes: [String: VoiceChatTileItemNode] = [:]
|
||||||
private var isFirstTime = true
|
private var isFirstTime = true
|
||||||
|
|
||||||
|
private var absoluteLocation: (CGRect, CGSize)?
|
||||||
|
|
||||||
init(context: AccountContext) {
|
init(context: AccountContext) {
|
||||||
self.context = context
|
self.context = context
|
||||||
|
|
||||||
@ -23,6 +25,16 @@ final class VoiceChatTileGridNode: ASDisplayNode {
|
|||||||
self.clipsToBounds = true
|
self.clipsToBounds = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
|
||||||
|
self.absoluteLocation = (rect, containerSize)
|
||||||
|
for itemNode in self.itemNodes.values {
|
||||||
|
var localRect = rect
|
||||||
|
localRect.origin = localRect.origin.offsetBy(dx: itemNode.frame.minX, dy: itemNode.frame.minY)
|
||||||
|
localRect.size = itemNode.frame.size
|
||||||
|
itemNode.updateAbsoluteRect(localRect, within: containerSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func update(size: CGSize, items: [VoiceChatTileItem], transition: ContainedViewLayoutTransition) -> CGSize {
|
func update(size: CGSize, items: [VoiceChatTileItem], transition: ContainedViewLayoutTransition) -> CGSize {
|
||||||
self.items = items
|
self.items = items
|
||||||
|
|
||||||
@ -72,6 +84,13 @@ final class VoiceChatTileGridNode: ASDisplayNode {
|
|||||||
} else {
|
} else {
|
||||||
transition.updateFrame(node: itemNode, frame: itemFrame)
|
transition.updateFrame(node: itemNode, frame: itemFrame)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let (rect, containerSize) = self.absoluteLocation {
|
||||||
|
var localRect = rect
|
||||||
|
localRect.origin = localRect.origin.offsetBy(dx: itemFrame.minX, dy: itemFrame.minY)
|
||||||
|
localRect.size = itemFrame.size
|
||||||
|
itemNode.updateAbsoluteRect(localRect, within: containerSize)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,6 +166,8 @@ final class VoiceChatTilesGridItemNode: ListViewItemNode {
|
|||||||
let backgroundNode: ASDisplayNode
|
let backgroundNode: ASDisplayNode
|
||||||
let cornersNode: ASImageNode
|
let cornersNode: ASImageNode
|
||||||
|
|
||||||
|
private var absoluteLocation: (CGRect, CGSize)?
|
||||||
|
|
||||||
var tileNodes: [VoiceChatTileItemNode] {
|
var tileNodes: [VoiceChatTileItemNode] {
|
||||||
if let values = self.tileGridNode?.itemNodes.values {
|
if let values = self.tileGridNode?.itemNodes.values {
|
||||||
return Array(values)
|
return Array(values)
|
||||||
@ -208,10 +229,15 @@ final class VoiceChatTilesGridItemNode: ListViewItemNode {
|
|||||||
strongSelf.tileGridNode = tileGridNode
|
strongSelf.tileGridNode = tileGridNode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if let (rect, size) = strongSelf.absoluteLocation {
|
||||||
|
tileGridNode.updateAbsoluteRect(rect, within: size)
|
||||||
|
}
|
||||||
|
|
||||||
let transition: ContainedViewLayoutTransition = currentItem == nil ? .immediate : .animated(duration: 0.3, curve: .easeInOut)
|
let transition: ContainedViewLayoutTransition = currentItem == nil ? .immediate : .animated(duration: 0.3, curve: .easeInOut)
|
||||||
let tileGridSize = tileGridNode.update(size: CGSize(width: params.width - params.leftInset - params.rightInset, height: CGFloat.greatestFiniteMagnitude), items: item.tiles, transition: transition)
|
let tileGridSize = tileGridNode.update(size: CGSize(width: params.width - params.leftInset - params.rightInset, height: CGFloat.greatestFiniteMagnitude), items: item.tiles, transition: transition)
|
||||||
if currentItem == nil {
|
if currentItem == nil {
|
||||||
tileGridNode.frame = CGRect(x: params.leftInset, y: 0.0, width: tileGridSize.width, height: 0.0)
|
tileGridNode.frame = CGRect(x: params.leftInset, y: 0.0, width: tileGridSize.width, height: tileGridSize.height)
|
||||||
strongSelf.backgroundNode.frame = tileGridNode.frame
|
strongSelf.backgroundNode.frame = tileGridNode.frame
|
||||||
strongSelf.cornersNode.frame = CGRect(x: params.leftInset, y: layout.size.height, width: tileGridSize.width, height: 50.0)
|
strongSelf.cornersNode.frame = CGRect(x: params.leftInset, y: layout.size.height, width: tileGridSize.width, height: 50.0)
|
||||||
} else {
|
} else {
|
||||||
@ -223,4 +249,9 @@ final class VoiceChatTilesGridItemNode: ListViewItemNode {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override public func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
|
||||||
|
self.absoluteLocation = (rect, containerSize)
|
||||||
|
self.tileGridNode?.updateAbsoluteRect(rect, within: containerSize)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,10 +9,13 @@ import TelegramCore
|
|||||||
import AccountContext
|
import AccountContext
|
||||||
import TelegramUIPreferences
|
import TelegramUIPreferences
|
||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
|
import AvatarNode
|
||||||
|
|
||||||
private let backgroundCornerRadius: CGFloat = 11.0
|
private let backgroundCornerRadius: CGFloat = 11.0
|
||||||
private let borderLineWidth: CGFloat = 2.0
|
private let borderLineWidth: CGFloat = 2.0
|
||||||
|
|
||||||
|
private let destructiveColor: UIColor = UIColor(rgb: 0xff3b30)
|
||||||
|
|
||||||
final class VoiceChatTileItem: Equatable {
|
final class VoiceChatTileItem: Equatable {
|
||||||
enum Icon: Equatable {
|
enum Icon: Equatable {
|
||||||
case none
|
case none
|
||||||
@ -20,12 +23,15 @@ final class VoiceChatTileItem: Equatable {
|
|||||||
case presentation
|
case presentation
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let account: Account
|
||||||
let peer: Peer
|
let peer: Peer
|
||||||
let videoEndpointId: String
|
let videoEndpointId: String
|
||||||
|
let videoReady: Bool
|
||||||
let strings: PresentationStrings
|
let strings: PresentationStrings
|
||||||
let nameDisplayOrder: PresentationPersonNameOrder
|
let nameDisplayOrder: PresentationPersonNameOrder
|
||||||
let icon: Icon
|
let icon: Icon
|
||||||
let text: VoiceChatParticipantItem.ParticipantText
|
let text: VoiceChatParticipantItem.ParticipantText
|
||||||
|
let additionalText: VoiceChatParticipantItem.ParticipantText?
|
||||||
let speaking: Bool
|
let speaking: Bool
|
||||||
let action: () -> Void
|
let action: () -> Void
|
||||||
let contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?
|
let contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?
|
||||||
@ -36,13 +42,16 @@ final class VoiceChatTileItem: Equatable {
|
|||||||
return self.videoEndpointId
|
return self.videoEndpointId
|
||||||
}
|
}
|
||||||
|
|
||||||
init(peer: Peer, videoEndpointId: String, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, speaking: Bool, icon: Icon, text: VoiceChatParticipantItem.ParticipantText, action: @escaping () -> Void, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?, getVideo: @escaping () -> GroupVideoNode?, getAudioLevel: (() -> Signal<Float, NoError>)?) {
|
init(account: Account, peer: Peer, videoEndpointId: String, videoReady: Bool, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, speaking: Bool, icon: Icon, text: VoiceChatParticipantItem.ParticipantText, additionalText: VoiceChatParticipantItem.ParticipantText?, action: @escaping () -> Void, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?, getVideo: @escaping () -> GroupVideoNode?, getAudioLevel: (() -> Signal<Float, NoError>)?) {
|
||||||
|
self.account = account
|
||||||
self.peer = peer
|
self.peer = peer
|
||||||
self.videoEndpointId = videoEndpointId
|
self.videoEndpointId = videoEndpointId
|
||||||
|
self.videoReady = videoReady
|
||||||
self.strings = strings
|
self.strings = strings
|
||||||
self.nameDisplayOrder = nameDisplayOrder
|
self.nameDisplayOrder = nameDisplayOrder
|
||||||
self.icon = icon
|
self.icon = icon
|
||||||
self.text = text
|
self.text = text
|
||||||
|
self.additionalText = additionalText
|
||||||
self.speaking = speaking
|
self.speaking = speaking
|
||||||
self.action = action
|
self.action = action
|
||||||
self.contextAction = contextAction
|
self.contextAction = contextAction
|
||||||
@ -57,9 +66,18 @@ final class VoiceChatTileItem: Equatable {
|
|||||||
if lhs.videoEndpointId != rhs.videoEndpointId {
|
if lhs.videoEndpointId != rhs.videoEndpointId {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.videoReady != rhs.videoReady {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.speaking != rhs.speaking {
|
if lhs.speaking != rhs.speaking {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.text != rhs.text {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.additionalText != rhs.additionalText {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.icon != rhs.icon {
|
if lhs.icon != rhs.icon {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -93,6 +111,7 @@ final class VoiceChatTileItemNode: ASDisplayNode {
|
|||||||
var videoNode: GroupVideoNode?
|
var videoNode: GroupVideoNode?
|
||||||
let infoNode: ASDisplayNode
|
let infoNode: ASDisplayNode
|
||||||
let fadeNode: ASDisplayNode
|
let fadeNode: ASDisplayNode
|
||||||
|
private var shimmerNode: VoiceChatTileShimmeringNode?
|
||||||
private let titleNode: ImmediateTextNode
|
private let titleNode: ImmediateTextNode
|
||||||
private let iconNode: ASImageNode
|
private let iconNode: ASImageNode
|
||||||
private var animationNode: VoiceChatMicrophoneNode?
|
private var animationNode: VoiceChatMicrophoneNode?
|
||||||
@ -197,7 +216,9 @@ final class VoiceChatTileItemNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc private func tap() {
|
@objc private func tap() {
|
||||||
self.item?.action()
|
if let item = self.item, item.videoReady {
|
||||||
|
item.action()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateIsExtracted(_ isExtracted: Bool, transition: ContainedViewLayoutTransition) {
|
private func updateIsExtracted(_ isExtracted: Bool, transition: ContainedViewLayoutTransition) {
|
||||||
@ -232,6 +253,14 @@ final class VoiceChatTileItemNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var absoluteLocation: (CGRect, CGSize)?
|
||||||
|
func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
|
||||||
|
self.absoluteLocation = (rect, containerSize)
|
||||||
|
if let shimmerNode = self.shimmerNode {
|
||||||
|
shimmerNode.updateAbsoluteRect(rect, within: containerSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func update(size: CGSize, availableWidth: CGFloat, item: VoiceChatTileItem, transition: ContainedViewLayoutTransition) {
|
func update(size: CGSize, availableWidth: CGFloat, item: VoiceChatTileItem, transition: ContainedViewLayoutTransition) {
|
||||||
guard self.validLayout?.0 != size || self.validLayout?.1 != availableWidth || self.item != item else {
|
guard self.validLayout?.0 != size || self.validLayout?.1 != availableWidth || self.item != item else {
|
||||||
return
|
return
|
||||||
@ -244,6 +273,28 @@ final class VoiceChatTileItemNode: ASDisplayNode {
|
|||||||
let previousItem = self.item
|
let previousItem = self.item
|
||||||
self.item = item
|
self.item = item
|
||||||
|
|
||||||
|
if !item.videoReady {
|
||||||
|
let shimmerNode: VoiceChatTileShimmeringNode
|
||||||
|
if let current = self.shimmerNode {
|
||||||
|
shimmerNode = current
|
||||||
|
} else {
|
||||||
|
shimmerNode = VoiceChatTileShimmeringNode(account: item.account, peer: item.peer)
|
||||||
|
self.contentNode.insertSubnode(shimmerNode, aboveSubnode: self.fadeNode)
|
||||||
|
self.shimmerNode = shimmerNode
|
||||||
|
|
||||||
|
if let (rect, containerSize) = self.absoluteLocation {
|
||||||
|
shimmerNode.updateAbsoluteRect(rect, within: containerSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
transition.updateFrame(node: shimmerNode, frame: CGRect(origin: CGPoint(), size: size))
|
||||||
|
shimmerNode.update(shimmeringColor: UIColor.white, size: size, transition: transition)
|
||||||
|
} else if let shimmerNode = self.shimmerNode {
|
||||||
|
self.shimmerNode = nil
|
||||||
|
shimmerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak shimmerNode] _ in
|
||||||
|
shimmerNode?.removeFromSupernode()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if let getAudioLevel = item.getAudioLevel {
|
if let getAudioLevel = item.getAudioLevel {
|
||||||
self.audioLevelDisposable.set((getAudioLevel()
|
self.audioLevelDisposable.set((getAudioLevel()
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] value in
|
|> deliverOnMainQueue).start(next: { [weak self] value in
|
||||||
@ -255,11 +306,7 @@ final class VoiceChatTileItemNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut)
|
let transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut)
|
||||||
if item.speaking {
|
transition.updateAlpha(node: self.highlightNode, alpha: item.speaking ? 1.0 : 0.0)
|
||||||
transition.updateAlpha(node: self.highlightNode, alpha: 1.0)
|
|
||||||
} else {
|
|
||||||
transition.updateAlpha(node: self.highlightNode, alpha: 0.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
if previousItem?.videoEndpointId != item.videoEndpointId || self.videoNode == nil {
|
if previousItem?.videoEndpointId != item.videoEndpointId || self.videoNode == nil {
|
||||||
if let current = self.videoNode {
|
if let current = self.videoNode {
|
||||||
@ -275,6 +322,7 @@ final class VoiceChatTileItemNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let titleFont = Font.semibold(13.0)
|
let titleFont = Font.semibold(13.0)
|
||||||
|
let subtitleFont = Font.regular(13.0)
|
||||||
let titleColor = UIColor.white
|
let titleColor = UIColor.white
|
||||||
var titleAttributedString: NSAttributedString?
|
var titleAttributedString: NSAttributedString?
|
||||||
if let user = item.peer as? TelegramUser {
|
if let user = item.peer as? TelegramUser {
|
||||||
@ -303,6 +351,13 @@ final class VoiceChatTileItemNode: ASDisplayNode {
|
|||||||
} else if let channel = item.peer as? TelegramChannel {
|
} else if let channel = item.peer as? TelegramChannel {
|
||||||
titleAttributedString = NSAttributedString(string: channel.title, font: titleFont, textColor: titleColor)
|
titleAttributedString = NSAttributedString(string: channel.title, font: titleFont, textColor: titleColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var microphoneColor = UIColor.white
|
||||||
|
if let additionalText = item.additionalText, case let .text(text, _, color) = additionalText {
|
||||||
|
if case .destructive = color {
|
||||||
|
microphoneColor = destructiveColor
|
||||||
|
}
|
||||||
|
}
|
||||||
self.titleNode.attributedText = titleAttributedString
|
self.titleNode.attributedText = titleAttributedString
|
||||||
|
|
||||||
if case let .microphone(muted) = item.icon {
|
if case let .microphone(muted) = item.icon {
|
||||||
@ -315,7 +370,7 @@ final class VoiceChatTileItemNode: ASDisplayNode {
|
|||||||
self.infoNode.addSubnode(animationNode)
|
self.infoNode.addSubnode(animationNode)
|
||||||
}
|
}
|
||||||
animationNode.alpha = 1.0
|
animationNode.alpha = 1.0
|
||||||
animationNode.update(state: VoiceChatMicrophoneNode.State(muted: muted, filled: true, color: UIColor.white), animated: true)
|
animationNode.update(state: VoiceChatMicrophoneNode.State(muted: muted, filled: true, color: microphoneColor), animated: true)
|
||||||
} else if let animationNode = self.animationNode {
|
} else if let animationNode = self.animationNode {
|
||||||
self.animationNode = nil
|
self.animationNode = nil
|
||||||
animationNode.removeFromSupernode()
|
animationNode.removeFromSupernode()
|
||||||
@ -342,7 +397,7 @@ final class VoiceChatTileItemNode: ASDisplayNode {
|
|||||||
|
|
||||||
if self.videoContainerNode.supernode === self.contentNode {
|
if self.videoContainerNode.supernode === self.contentNode {
|
||||||
if let videoNode = self.videoNode {
|
if let videoNode = self.videoNode {
|
||||||
transition.updateFrame(node: videoNode, frame: bounds)
|
itemTransition.updateFrame(node: videoNode, frame: bounds)
|
||||||
videoNode.updateLayout(size: size, layoutMode: .fillOrFitToSquare, transition: itemTransition)
|
videoNode.updateLayout(size: size, layoutMode: .fillOrFitToSquare, transition: itemTransition)
|
||||||
}
|
}
|
||||||
transition.updateFrame(node: self.videoContainerNode, frame: bounds)
|
transition.updateFrame(node: self.videoContainerNode, frame: bounds)
|
||||||
@ -584,3 +639,208 @@ class VoiceChatTileHighlightNode: ASDisplayNode {
|
|||||||
self.updateAnimations()
|
self.updateAnimations()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final class ShimmerEffectForegroundNode: ASDisplayNode {
|
||||||
|
private var currentForegroundColor: UIColor?
|
||||||
|
private let imageNodeContainer: ASDisplayNode
|
||||||
|
private let imageNode: ASImageNode
|
||||||
|
|
||||||
|
private var absoluteLocation: (CGRect, CGSize)?
|
||||||
|
private var isCurrentlyInHierarchy = false
|
||||||
|
private var shouldBeAnimating = false
|
||||||
|
|
||||||
|
private let size: CGFloat
|
||||||
|
|
||||||
|
init(size: CGFloat) {
|
||||||
|
self.size = size
|
||||||
|
|
||||||
|
self.imageNodeContainer = ASDisplayNode()
|
||||||
|
self.imageNodeContainer.isLayerBacked = true
|
||||||
|
|
||||||
|
self.imageNode = ASImageNode()
|
||||||
|
self.imageNode.isLayerBacked = true
|
||||||
|
self.imageNode.displaysAsynchronously = false
|
||||||
|
self.imageNode.displayWithoutProcessing = true
|
||||||
|
self.imageNode.contentMode = .scaleToFill
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
self.isLayerBacked = true
|
||||||
|
self.clipsToBounds = true
|
||||||
|
|
||||||
|
self.imageNodeContainer.addSubnode(self.imageNode)
|
||||||
|
self.addSubnode(self.imageNodeContainer)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func didEnterHierarchy() {
|
||||||
|
super.didEnterHierarchy()
|
||||||
|
|
||||||
|
self.isCurrentlyInHierarchy = true
|
||||||
|
self.updateAnimation()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func didExitHierarchy() {
|
||||||
|
super.didExitHierarchy()
|
||||||
|
|
||||||
|
self.isCurrentlyInHierarchy = false
|
||||||
|
self.updateAnimation()
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(foregroundColor: UIColor) {
|
||||||
|
if let currentForegroundColor = self.currentForegroundColor, currentForegroundColor.isEqual(foregroundColor) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.currentForegroundColor = foregroundColor
|
||||||
|
|
||||||
|
let image = generateImage(CGSize(width: self.size, height: 16.0), opaque: false, scale: 1.0, rotatedContext: { size, context in
|
||||||
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
|
|
||||||
|
context.clip(to: CGRect(origin: CGPoint(), size: size))
|
||||||
|
|
||||||
|
let transparentColor = foregroundColor.withAlphaComponent(0.0).cgColor
|
||||||
|
let peakColor = foregroundColor.cgColor
|
||||||
|
|
||||||
|
var locations: [CGFloat] = [0.0, 0.5, 1.0]
|
||||||
|
let colors: [CGColor] = [transparentColor, peakColor, transparentColor]
|
||||||
|
|
||||||
|
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||||
|
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
||||||
|
|
||||||
|
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: size.width, y: 0.0), options: CGGradientDrawingOptions())
|
||||||
|
})
|
||||||
|
self.imageNode.image = image
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
|
||||||
|
if let absoluteLocation = self.absoluteLocation, absoluteLocation.0 == rect && absoluteLocation.1 == containerSize {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let sizeUpdated = self.absoluteLocation?.1 != containerSize
|
||||||
|
let frameUpdated = self.absoluteLocation?.0 != rect
|
||||||
|
self.absoluteLocation = (rect, containerSize)
|
||||||
|
|
||||||
|
if sizeUpdated {
|
||||||
|
if self.shouldBeAnimating {
|
||||||
|
self.imageNode.layer.removeAnimation(forKey: "shimmer")
|
||||||
|
self.addImageAnimation()
|
||||||
|
} else {
|
||||||
|
self.updateAnimation()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if frameUpdated {
|
||||||
|
self.imageNodeContainer.frame = CGRect(origin: CGPoint(x: -rect.minX, y: -rect.minY), size: containerSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateAnimation() {
|
||||||
|
let shouldBeAnimating = self.isCurrentlyInHierarchy && self.absoluteLocation != nil
|
||||||
|
if shouldBeAnimating != self.shouldBeAnimating {
|
||||||
|
self.shouldBeAnimating = shouldBeAnimating
|
||||||
|
if shouldBeAnimating {
|
||||||
|
self.addImageAnimation()
|
||||||
|
} else {
|
||||||
|
self.imageNode.layer.removeAnimation(forKey: "shimmer")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func addImageAnimation() {
|
||||||
|
guard let containerSize = self.absoluteLocation?.1 else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let gradientHeight: CGFloat = self.size
|
||||||
|
self.imageNode.frame = CGRect(origin: CGPoint(x: -gradientHeight, y: 0.0), size: CGSize(width: gradientHeight, height: containerSize.height))
|
||||||
|
let animation = self.imageNode.layer.makeAnimation(from: 0.0 as NSNumber, to: (containerSize.width + gradientHeight) as NSNumber, keyPath: "position.x", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 1.3 * 1.0, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: true)
|
||||||
|
animation.repeatCount = Float.infinity
|
||||||
|
animation.beginTime = 1.0
|
||||||
|
self.imageNode.layer.add(animation, forKey: "shimmer")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class VoiceChatTileShimmeringNode: ASDisplayNode {
|
||||||
|
private let backgroundNode: ImageNode
|
||||||
|
private let effectNode: ShimmerEffectForegroundNode
|
||||||
|
|
||||||
|
private let borderNode: ASDisplayNode
|
||||||
|
private var borderMaskView: UIView?
|
||||||
|
private let borderEffectNode: ShimmerEffectForegroundNode
|
||||||
|
|
||||||
|
private var currentShimmeringColor: UIColor?
|
||||||
|
private var currentSize: CGSize?
|
||||||
|
|
||||||
|
public init(account: Account, peer: Peer) {
|
||||||
|
self.backgroundNode = ImageNode(enableHasImage: false, enableEmpty: false, enableAnimatedTransition: true)
|
||||||
|
self.backgroundNode.displaysAsynchronously = false
|
||||||
|
self.backgroundNode.contentMode = .scaleAspectFill
|
||||||
|
|
||||||
|
self.effectNode = ShimmerEffectForegroundNode(size: 220.0)
|
||||||
|
|
||||||
|
self.borderNode = ASDisplayNode()
|
||||||
|
self.borderEffectNode = ShimmerEffectForegroundNode(size: 320.0)
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
self.clipsToBounds = true
|
||||||
|
self.cornerRadius = backgroundCornerRadius
|
||||||
|
|
||||||
|
self.addSubnode(self.backgroundNode)
|
||||||
|
self.addSubnode(self.effectNode)
|
||||||
|
self.addSubnode(self.borderNode)
|
||||||
|
self.borderNode.addSubnode(self.borderEffectNode)
|
||||||
|
|
||||||
|
self.backgroundNode.setSignal(peerAvatarCompleteImage(account: account, peer: peer, size: CGSize(width: 180.0, height: 180.0), round: false, font: Font.regular(16.0), drawLetters: false, fullSize: false, blurred: true))
|
||||||
|
}
|
||||||
|
|
||||||
|
public override func didLoad() {
|
||||||
|
super.didLoad()
|
||||||
|
|
||||||
|
self.effectNode.layer.compositingFilter = "screenBlendMode"
|
||||||
|
self.borderEffectNode.layer.compositingFilter = "screenBlendMode"
|
||||||
|
|
||||||
|
let borderMaskView = UIView()
|
||||||
|
borderMaskView.layer.borderWidth = 1.0
|
||||||
|
borderMaskView.layer.borderColor = UIColor.white.cgColor
|
||||||
|
borderMaskView.layer.cornerRadius = backgroundCornerRadius
|
||||||
|
self.borderMaskView = borderMaskView
|
||||||
|
|
||||||
|
if let size = self.currentSize {
|
||||||
|
borderMaskView.frame = CGRect(origin: CGPoint(), size: size)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.borderNode.view.mask = borderMaskView
|
||||||
|
|
||||||
|
if #available(iOS 13.0, *) {
|
||||||
|
self.layer.cornerCurve = .continuous
|
||||||
|
borderMaskView.layer.cornerCurve = .continuous
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
|
||||||
|
self.effectNode.updateAbsoluteRect(rect, within: containerSize)
|
||||||
|
self.borderEffectNode.updateAbsoluteRect(rect, within: containerSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func update(shimmeringColor: UIColor, size: CGSize, transition: ContainedViewLayoutTransition) {
|
||||||
|
if let currentShimmeringColor = self.currentShimmeringColor, currentShimmeringColor.isEqual(shimmeringColor) && self.currentSize == size {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.currentShimmeringColor = shimmeringColor
|
||||||
|
self.currentSize = size
|
||||||
|
|
||||||
|
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||||
|
|
||||||
|
self.effectNode.update(foregroundColor: shimmeringColor.withAlphaComponent(0.3))
|
||||||
|
self.effectNode.frame = bounds
|
||||||
|
|
||||||
|
self.borderEffectNode.update(foregroundColor: shimmeringColor.withAlphaComponent(0.5))
|
||||||
|
self.borderEffectNode.frame = bounds
|
||||||
|
|
||||||
|
transition.updateFrame(node: self.backgroundNode, frame: bounds)
|
||||||
|
transition.updateFrame(node: self.borderNode, frame: bounds)
|
||||||
|
if let borderMaskView = self.borderMaskView {
|
||||||
|
transition.updateFrame(view: borderMaskView, frame: bounds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user