Video Chat Improvements

This commit is contained in:
Ilya Laktyushin 2021-05-24 15:31:19 +04:00
parent aa66b9e4df
commit fe31bcdf26
6 changed files with 314 additions and 20 deletions

View File

@ -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>
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) {
iconSignal = combineLatest(.single(nil) |> then(signal), .single(nil) |> then(fullSizeSignal))
|> 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
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)
if blurred {
context.setFillColor(UIColor(rgb: 0x000000, alpha: 0.45).cgColor)
context.fill(CGRect(origin: CGPoint(), size: size))
}
})?.withRenderingMode(.alwaysOriginal)
subscriber.putNext(image)
@ -130,7 +134,7 @@ public func peerAvatarCompleteImage(account: Account, peer: Peer, size: CGSize,
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) {
return imageData
|> mapToSignal { data -> Signal<(UIImage, UIImage)?, NoError> in
@ -149,11 +153,22 @@ public func peerAvatarImage(account: Account, peerReference: PeerReference?, aut
context.clip()
}
var shouldBlur = false
if case .blurred = dataType {
shouldBlur = true
} else if blurred {
shouldBlur = true
}
if shouldBlur {
let imageContextSize = CGSize(width: 64.0, height: 64.0)
let imageContext = DrawingContext(size: imageContextSize, scale: 1.0, premultiplied: true, clear: true)
imageContext.withFlippedContext { c in
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)
@ -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))
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 displayDimensions.width == 60.0 {
context.setBlendMode(.destinationOut)

View File

@ -1400,7 +1400,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
contentHeight = layout.size.height
} else {
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
}
} else if keepInPlace {

View File

@ -126,7 +126,7 @@ public class ImageNode: ASDisplayNode {
private let hasImage: ValuePromise<Bool>?
private var first = true
private let enableEmpty: Bool
private let enableAnimatedTransition: Bool
public var enableAnimatedTransition: Bool
private let _contentReady = Promise<Bool>()
private var didSetReady: Bool = false

View File

@ -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 {
return nil
}
@ -433,7 +433,7 @@ public final class VoiceChatController: ViewController {
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, action: {
interaction.switchToPeer(peer.id, videoEndpointId, true)
}, contextAction: { node, gesture in
interaction.peerContextAction(peerEntry, node, gesture)
@ -4278,11 +4278,11 @@ public final class VoiceChatController: ViewController {
var isTile = false
if let interaction = self.itemInteraction {
if let videoEndpointId = member.presentationEndpointId, self.readyVideoNodes.contains(videoEndpointId) {
if let videoEndpointId = member.presentationEndpointId {
if !self.videoOrder.contains(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
tileByVideoEndpoint[videoEndpointId] = tileItem
}
@ -4290,11 +4290,11 @@ public final class VoiceChatController: ViewController {
latestWideVideo = videoEndpointId
}
}
if let videoEndpointId = member.videoEndpointId, self.readyVideoNodes.contains(videoEndpointId) {
if let videoEndpointId = member.videoEndpointId {
if !self.videoOrder.contains(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
tileByVideoEndpoint[videoEndpointId] = tileItem
}

View File

@ -15,6 +15,8 @@ final class VoiceChatTileGridNode: ASDisplayNode {
fileprivate var itemNodes: [String: VoiceChatTileItemNode] = [:]
private var isFirstTime = true
private var absoluteLocation: (CGRect, CGSize)?
init(context: AccountContext) {
self.context = context
@ -23,6 +25,16 @@ final class VoiceChatTileGridNode: ASDisplayNode {
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 {
self.items = items
@ -72,6 +84,13 @@ final class VoiceChatTileGridNode: ASDisplayNode {
} else {
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 cornersNode: ASImageNode
private var absoluteLocation: (CGRect, CGSize)?
var tileNodes: [VoiceChatTileItemNode] {
if let values = self.tileGridNode?.itemNodes.values {
return Array(values)
@ -208,10 +229,15 @@ final class VoiceChatTilesGridItemNode: ListViewItemNode {
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 tileGridSize = tileGridNode.update(size: CGSize(width: params.width - params.leftInset - params.rightInset, height: CGFloat.greatestFiniteMagnitude), items: item.tiles, transition: transition)
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.cornersNode.frame = CGRect(x: params.leftInset, y: layout.size.height, width: tileGridSize.width, height: 50.0)
} 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)
}
}

View File

@ -9,6 +9,7 @@ import TelegramCore
import AccountContext
import TelegramUIPreferences
import TelegramPresentationData
import AvatarNode
private let backgroundCornerRadius: CGFloat = 11.0
private let borderLineWidth: CGFloat = 2.0
@ -20,8 +21,10 @@ final class VoiceChatTileItem: Equatable {
case presentation
}
let account: Account
let peer: Peer
let videoEndpointId: String
let videoReady: Bool
let strings: PresentationStrings
let nameDisplayOrder: PresentationPersonNameOrder
let icon: Icon
@ -36,9 +39,11 @@ final class VoiceChatTileItem: Equatable {
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, action: @escaping () -> Void, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?, getVideo: @escaping () -> GroupVideoNode?, getAudioLevel: (() -> Signal<Float, NoError>)?) {
self.account = account
self.peer = peer
self.videoEndpointId = videoEndpointId
self.videoReady = videoReady
self.strings = strings
self.nameDisplayOrder = nameDisplayOrder
self.icon = icon
@ -57,6 +62,9 @@ final class VoiceChatTileItem: Equatable {
if lhs.videoEndpointId != rhs.videoEndpointId {
return false
}
if lhs.videoReady != rhs.videoReady {
return false
}
if lhs.speaking != rhs.speaking {
return false
}
@ -93,6 +101,7 @@ final class VoiceChatTileItemNode: ASDisplayNode {
var videoNode: GroupVideoNode?
let infoNode: ASDisplayNode
let fadeNode: ASDisplayNode
private var shimmerNode: VoiceChatTileShimmeringNode?
private let titleNode: ImmediateTextNode
private let iconNode: ASImageNode
private var animationNode: VoiceChatMicrophoneNode?
@ -197,7 +206,9 @@ final class VoiceChatTileItemNode: ASDisplayNode {
}
@objc private func tap() {
self.item?.action()
if let item = self.item, item.videoReady {
item.action()
}
}
private func updateIsExtracted(_ isExtracted: Bool, transition: ContainedViewLayoutTransition) {
@ -232,6 +243,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) {
guard self.validLayout?.0 != size || self.validLayout?.1 != availableWidth || self.item != item else {
return
@ -244,6 +263,28 @@ final class VoiceChatTileItemNode: ASDisplayNode {
let previousItem = self.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 {
self.audioLevelDisposable.set((getAudioLevel()
|> deliverOnMainQueue).start(next: { [weak self] value in
@ -255,11 +296,7 @@ final class VoiceChatTileItemNode: ASDisplayNode {
}
let transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut)
if item.speaking {
transition.updateAlpha(node: self.highlightNode, alpha: 1.0)
} else {
transition.updateAlpha(node: self.highlightNode, alpha: 0.0)
}
transition.updateAlpha(node: self.highlightNode, alpha: item.speaking ? 1.0 : 0.0)
if previousItem?.videoEndpointId != item.videoEndpointId || self.videoNode == nil {
if let current = self.videoNode {
@ -342,7 +379,7 @@ final class VoiceChatTileItemNode: ASDisplayNode {
if self.videoContainerNode.supernode === self.contentNode {
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)
}
transition.updateFrame(node: self.videoContainerNode, frame: bounds)
@ -584,3 +621,208 @@ class VoiceChatTileHighlightNode: ASDisplayNode {
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)
}
}
}