diff --git a/.bazelrc b/.bazelrc index b6ad92dac18..533ecde1bd5 100644 --- a/.bazelrc +++ b/.bazelrc @@ -31,3 +31,6 @@ build --spawn_strategy=standalone build --strategy=SwiftCompile=standalone build --define RULES_SWIFT_BUILD_DUMMY_WORKER=1 + +#build --linkopt=-fuse-ld=/Users/ali/build/zld/build/Build/Products/Release/zld +#build --linkopt=-Wl,-zld_original_ld_path,__BAZEL_XCODE_DEVELOPER_DIR__/Toolchains/XcodeDefault.xctoolchain/usr/bin/ld diff --git a/submodules/MusicAlbumArtResources/Sources/ExternalMusicAlbumArtResources.swift b/submodules/MusicAlbumArtResources/Sources/ExternalMusicAlbumArtResources.swift index e991414ae59..c7906f79d10 100644 --- a/submodules/MusicAlbumArtResources/Sources/ExternalMusicAlbumArtResources.swift +++ b/submodules/MusicAlbumArtResources/Sources/ExternalMusicAlbumArtResources.swift @@ -59,7 +59,7 @@ public class ExternalMusicAlbumArtResource: Equatable { } public func fetchExternalMusicAlbumArtResource(engine: TelegramEngine, file: FileMediaReference?, resource: ExternalMusicAlbumArtResource) -> Signal { - return engine.resources.fetchAlbumCover(file: file, title: resource.title, performer: resource.performer) + return engine.resources.fetchAlbumCover(file: file, title: resource.title, performer: resource.performer, isThumbnail: resource.isThumbnail) /*return Signal { subscriber in if resource.performer.isEmpty || resource.performer.lowercased().trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) == "unknown artist" || resource.title.isEmpty { diff --git a/submodules/SSignalKit/SwiftSignalKit/Source/Atomic.swift b/submodules/SSignalKit/SwiftSignalKit/Source/Atomic.swift index e6afddf50b5..aae69420530 100644 --- a/submodules/SSignalKit/SwiftSignalKit/Source/Atomic.swift +++ b/submodules/SSignalKit/SwiftSignalKit/Source/Atomic.swift @@ -1,5 +1,9 @@ import Foundation +public enum AtomicLockError: Error { + case isLocked +} + public final class Atomic { private var lock: pthread_mutex_t private var value: T @@ -23,6 +27,16 @@ public final class Atomic { return result } + public func tryWith(_ f: (T) -> R) throws -> R { + if pthread_mutex_trylock(&self.lock) == 0 { + let result = f(self.value) + pthread_mutex_unlock(&self.lock) + return result + } else { + throw AtomicLockError.isLocked + } + } + public func modify(_ f: (T) -> T) -> T { pthread_mutex_lock(&self.lock) let result = f(self.value) diff --git a/submodules/StickerPackPreviewUI/Sources/StickerPackEmojisItem.swift b/submodules/StickerPackPreviewUI/Sources/StickerPackEmojisItem.swift index 5fc72bdd616..c26069a9224 100644 --- a/submodules/StickerPackPreviewUI/Sources/StickerPackEmojisItem.swift +++ b/submodules/StickerPackPreviewUI/Sources/StickerPackEmojisItem.swift @@ -283,7 +283,7 @@ final class StickerPackEmojisItemNode: GridItemNode { context: context, dimensions: item.file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0), immediateThumbnailData: item.file.immediateThumbnailData, - shimmerView: strongSelf.shimmerHostView, + shimmerView: nil,//strongSelf.shimmerHostView, color: theme.chat.inputPanel.primaryTextColor.withMultipliedAlpha(0.08), size: itemNativeFitSize ) diff --git a/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewGridItem.swift b/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewGridItem.swift index e9805d04c43..888afaad031 100644 --- a/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewGridItem.swift +++ b/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewGridItem.swift @@ -50,7 +50,7 @@ final class StickerPackPreviewGridItem: GridItem { func node(layout: GridNodeLayout, synchronousLoad: Bool) -> GridItemNode { let node = StickerPackPreviewGridItemNode() - node.setup(account: self.account, stickerItem: self.stickerItem, interaction: self.interaction, theme: self.theme, isLocked: self.isLocked, isEmpty: self.isEmpty) + node.setup(account: self.account, stickerItem: self.stickerItem, interaction: self.interaction, theme: self.theme, isLocked: self.isLocked, isPremium: self.isPremium, isEmpty: self.isEmpty) return node } @@ -59,7 +59,7 @@ final class StickerPackPreviewGridItem: GridItem { assertionFailure() return } - node.setup(account: self.account, stickerItem: self.stickerItem, interaction: self.interaction, theme: self.theme, isLocked: self.isLocked, isEmpty: self.isEmpty) + node.setup(account: self.account, stickerItem: self.stickerItem, interaction: self.interaction, theme: self.theme, isLocked: self.isLocked, isPremium: self.isPremium, isEmpty: self.isEmpty) } } @@ -68,6 +68,7 @@ private let textFont = Font.regular(20.0) final class StickerPackPreviewGridItemNode: GridItemNode { private var currentState: (Account, StickerPackItem?)? private var isLocked: Bool? + private var isPremium: Bool? private var isEmpty: Bool? private let imageNode: TransformImageNode private var animationNode: AnimatedStickerNode? @@ -167,11 +168,11 @@ final class StickerPackPreviewGridItemNode: GridItemNode { } private var setupTimestamp: Double? - func setup(account: Account, stickerItem: StickerPackItem?, interaction: StickerPackPreviewInteraction, theme: PresentationTheme, isLocked: Bool, isEmpty: Bool) { + func setup(account: Account, stickerItem: StickerPackItem?, interaction: StickerPackPreviewInteraction, theme: PresentationTheme, isLocked: Bool, isPremium: Bool, isEmpty: Bool) { self.interaction = interaction self.theme = theme - if self.currentState == nil || self.currentState!.0 !== account || self.currentState!.1 != stickerItem || self.isLocked != isLocked || self.isEmpty != isEmpty { + if self.currentState == nil || self.currentState!.0 !== account || self.currentState!.1 != stickerItem || self.isLocked != isLocked || self.isPremium != isPremium || self.isEmpty != isEmpty { self.isLocked = isLocked if isLocked { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CloudFileMediaResource.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CloudFileMediaResource.swift index 0e64807653d..2c837442000 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CloudFileMediaResource.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CloudFileMediaResource.swift @@ -739,12 +739,14 @@ final class AlbumCoverResource: TelegramMediaResource, MediaResourceWithWebFileR let file: FileMediaReference? let title: String let performer: String + let isThumbnail: Bool - init(datacenterId: Int, file: FileMediaReference?, title: String, performer: String) { + init(datacenterId: Int, file: FileMediaReference?, title: String, performer: String, isThumbnail: Bool) { self.datacenterId = datacenterId self.file = file self.title = title self.performer = performer + self.isThumbnail = isThumbnail } init(decoder: PostboxDecoder) { @@ -761,17 +763,21 @@ final class AlbumCoverResource: TelegramMediaResource, MediaResourceWithWebFileR document = .inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference ?? Data())) flags |= 1 << 0 } - if !self.title.isEmpty { + var requestTitle: String? + var requestPerformer: String? + if !self.title.isEmpty || !self.performer.isEmpty { + requestTitle = self.title + requestPerformer = self.performer flags |= 1 << 1 } - if !self.performer.isEmpty { - flags |= 1 << 1 + if self.isThumbnail { + flags |= 1 << 2 } return .inputWebFileAudioAlbumThumbLocation( flags: flags, document: document, - title: self.title.isEmpty ? nil : self.title, - performer: self.performer.isEmpty ? nil : self.performer + title: requestTitle, + performer: requestPerformer ) } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Resources/TelegramEngineResources.swift b/submodules/TelegramCore/Sources/TelegramEngine/Resources/TelegramEngineResources.swift index 67138b5e640..82e7fd11e16 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Resources/TelegramEngineResources.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Resources/TelegramEngineResources.swift @@ -279,12 +279,12 @@ public extension TelegramEngine { } } - public func fetchAlbumCover(file: FileMediaReference?, title: String, performer: String) -> Signal { + public func fetchAlbumCover(file: FileMediaReference?, title: String, performer: String, isThumbnail: Bool) -> Signal { let signal = currentWebDocumentsHostDatacenterId(postbox: self.account.postbox, isTestingEnvironment: self.account.testingEnvironment) |> castError(EngineMediaResource.Fetch.Error.self) |> take(1) |> mapToSignal { datacenterId -> Signal in - let resource = AlbumCoverResource(datacenterId: Int(datacenterId), file: file, title: title, performer: performer) + let resource = AlbumCoverResource(datacenterId: Int(datacenterId), file: file, title: title, performer: performer, isThumbnail: isThumbnail) return multipartFetch(postbox: self.account.postbox, network: self.account.network, mediaReferenceRevalidationContext: self.account.mediaReferenceRevalidationContext, resource: resource, datacenterId: Int(datacenterId), size: nil, intervals: .single([(0 ..< Int64.max, .default)]), parameters: MediaResourceFetchParameters( tag: nil, diff --git a/submodules/TelegramUI/Components/EmojiTextAttachmentView/Sources/EmojiTextAttachmentView.swift b/submodules/TelegramUI/Components/EmojiTextAttachmentView/Sources/EmojiTextAttachmentView.swift index ed731a79395..ad64f102479 100644 --- a/submodules/TelegramUI/Components/EmojiTextAttachmentView/Sources/EmojiTextAttachmentView.swift +++ b/submodules/TelegramUI/Components/EmojiTextAttachmentView/Sources/EmojiTextAttachmentView.swift @@ -184,13 +184,14 @@ public final class InlineStickerItemLayer: MultiAnimationRenderTarget { } else { let pointSize = self.pointSize let placeholderColor = self.placeholderColor + let isThumbnailCancelled = Atomic(value: false) self.loadDisposable = self.renderer.loadFirstFrame(target: self, cache: self.cache, itemId: file.resource.id.stringRepresentation, size: self.pixelSize, fetch: animationCacheFetchFile(context: self.context, resource: .media(media: .standalone(media: file), resource: file.resource), type: AnimationCacheAnimationType(file: file), keyframeOnly: true), completion: { [weak self] result, isFinal in if !result { MultiAnimationRendererImpl.firstFrameQueue.async { let image = generateStickerPlaceholderImage(data: file.immediateThumbnailData, size: pointSize, scale: min(2.0, UIScreenScale), imageSize: file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0), backgroundColor: nil, foregroundColor: placeholderColor) DispatchQueue.main.async { - guard let strongSelf = self else { + guard let strongSelf = self, !isThumbnailCancelled.with({ $0 }) else { return } if let image = image { @@ -207,6 +208,7 @@ public final class InlineStickerItemLayer: MultiAnimationRenderTarget { guard let strongSelf = self else { return } + let _ = isThumbnailCancelled.swap(true) strongSelf.loadAnimation() } }) diff --git a/submodules/TelegramUI/Components/MultiAnimationRenderer/Sources/MultiAnimationRenderer.swift b/submodules/TelegramUI/Components/MultiAnimationRenderer/Sources/MultiAnimationRenderer.swift index 2e7db61c397..9e81281dac6 100644 --- a/submodules/TelegramUI/Components/MultiAnimationRenderer/Sources/MultiAnimationRenderer.swift +++ b/submodules/TelegramUI/Components/MultiAnimationRenderer/Sources/MultiAnimationRenderer.swift @@ -78,9 +78,11 @@ open class MultiAnimationRenderTarget: SimpleLayer { private final class LoadFrameGroupTask { let task: () -> () -> Void + let queueAffinity: Int - init(task: @escaping () -> () -> Void) { + init(task: @escaping () -> () -> Void, queueAffinity: Int) { self.task = task + self.queueAffinity = queueAffinity } } @@ -222,11 +224,12 @@ private final class ItemAnimationContext { static let queue1 = Queue(name: "ItemAnimationContext-1", qos: .default) private let cache: AnimationCache + let queueAffinity: Int private let stateUpdated: () -> Void private var disposable: Disposable? private var displayLink: ConstantDisplayLinkAnimator? - private var item: AnimationCacheItem? + private var item: Atomic? private var currentFrame: Frame? private var isLoadingFrame: Bool = false @@ -241,8 +244,9 @@ private final class ItemAnimationContext { let targets = Bag>() - init(cache: AnimationCache, itemId: String, size: CGSize, fetch: @escaping (AnimationCacheFetchOptions) -> Disposable, stateUpdated: @escaping () -> Void) { + init(cache: AnimationCache, queueAffinity: Int, itemId: String, size: CGSize, fetch: @escaping (AnimationCacheFetchOptions) -> Disposable, stateUpdated: @escaping () -> Void) { self.cache = cache + self.queueAffinity = queueAffinity self.stateUpdated = stateUpdated self.disposable = cache.get(sourceId: itemId, size: size, fetch: fetch).start(next: { [weak self] result in @@ -250,7 +254,9 @@ private final class ItemAnimationContext { guard let strongSelf = self else { return } - strongSelf.item = result.item + if let item = result.item { + strongSelf.item = Atomic(value: item) + } strongSelf.updateIsPlaying() } }) @@ -330,9 +336,14 @@ private final class ItemAnimationContext { return LoadFrameGroupTask(task: { [weak self] in let currentFrame: Frame? - if let frame = item.advance(advance: frameAdvance, requestedFormat: .rgba) { - currentFrame = Frame(frame: frame) - } else { + do { + if let frame = try item.tryWith({ $0.advance(advance: frameAdvance, requestedFormat: .rgba) }) { + currentFrame = Frame(frame: frame) + } else { + currentFrame = nil + } + } catch { + assertionFailure() currentFrame = nil } @@ -356,7 +367,7 @@ private final class ItemAnimationContext { } } } - }) + }, queueAffinity: self.queueAffinity) } if let _ = self.currentFrame { @@ -383,6 +394,7 @@ public final class MultiAnimationRendererImpl: MultiAnimationRenderer { } private var itemContexts: [ItemKey: ItemAnimationContext] = [:] + private var nextQueueAffinity: Int = 0 private(set) var isPlaying: Bool = false { didSet { @@ -403,7 +415,9 @@ public final class MultiAnimationRendererImpl: MultiAnimationRenderer { if let current = self.itemContexts[itemKey] { itemContext = current } else { - itemContext = ItemAnimationContext(cache: cache, itemId: itemId, size: size, fetch: fetch, stateUpdated: { [weak self] in + let queueAffinity = self.nextQueueAffinity + self.nextQueueAffinity += 1 + itemContext = ItemAnimationContext(cache: cache, queueAffinity: queueAffinity, itemId: itemId, size: size, fetch: fetch, stateUpdated: { [weak self] in guard let strongSelf = self else { return } @@ -668,59 +682,40 @@ public final class MultiAnimationRendererImpl: MultiAnimationRenderer { } if !tasks.isEmpty { - if tasks.count > 2 { - let tasks0 = Array(tasks.prefix(tasks.count / 2)) - let tasks1 = Array(tasks.suffix(tasks.count - tasks0.count)) - - var tasks0Completions: [() -> Void]? - var tasks1Completions: [() -> Void]? - - let complete: (Int, [() -> Void]) -> Void = { index, completions in - Queue.mainQueue().async { - if index == 0 { - tasks0Completions = completions - } else if index == 1 { - tasks1Completions = completions - } - if let tasks0Completions = tasks0Completions, let tasks1Completions = tasks1Completions { - for completion in tasks0Completions { - completion() - } - for completion in tasks1Completions { - completion() - } - } - } - } - - ItemAnimationContext.queue0.async { - var completions: [() -> Void] = [] - for task in tasks0 { - let complete = task.task() - completions.append(complete) - } - complete(0, completions) - } - ItemAnimationContext.queue1.async { + let tasks0 = tasks.filter { $0.queueAffinity % 2 == 0 } + let tasks1 = tasks.filter { $0.queueAffinity % 2 == 1 } + let allTasks = [tasks0, tasks1] + + let taskCompletions = Atomic<[Int: [() -> Void]]>(value: [:]) + let queues: [Queue] = [ItemAnimationContext.queue0, ItemAnimationContext.queue1] + + for i in 0 ..< 2 { + let partTasks = allTasks[i] + let id = i + queues[i].async { var completions: [() -> Void] = [] - for task in tasks1 { + for task in partTasks { let complete = task.task() completions.append(complete) } - complete(1, completions) - } - } else { - ItemAnimationContext.queue0.async { - var completions: [() -> Void] = [] - for task in tasks { - let complete = task.task() - completions.append(complete) + + var complete = false + let _ = taskCompletions.modify { current in + var current = current + current[id] = completions + if current.count == 2 { + complete = true + } + return current } - if !completions.isEmpty { + if complete { Queue.mainQueue().async { - for completion in completions { - completion() + let allCompletions = taskCompletions.with { $0 } + for (_, fs) in allCompletions { + for f in fs { + f() + } } } } diff --git a/submodules/TelegramUI/Sources/ChatEntityKeyboardInputNode.swift b/submodules/TelegramUI/Sources/ChatEntityKeyboardInputNode.swift index c2c12fb9a48..7f9d585bfbb 100644 --- a/submodules/TelegramUI/Sources/ChatEntityKeyboardInputNode.swift +++ b/submodules/TelegramUI/Sources/ChatEntityKeyboardInputNode.swift @@ -283,7 +283,9 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { itemGroupIndexById[groupId] = itemGroups.count var headerItem: EntityKeyboardAnimationData? - if let thumbnail = featuredEmojiPack.info.thumbnail { + if let thumbnailFileId = featuredEmojiPack.info.thumbnailFileId, let file = featuredEmojiPack.topItems.first(where: { $0.file.fileId.id == thumbnailFileId }) { + headerItem = EntityKeyboardAnimationData(file: file.file) + } else if let thumbnail = featuredEmojiPack.info.thumbnail { let info = featuredEmojiPack.info let type: EntityKeyboardAnimationData.ItemType if item.file.isAnimatedSticker { @@ -323,6 +325,23 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { hasClear = true } + var headerItem = group.headerItem + + if let groupId = group.id.base as? ItemCollectionId { + outer: for (id, info, _) in view.collectionInfos { + if id == groupId, let info = info as? StickerPackCollectionInfo { + if let thumbnailFileId = info.thumbnailFileId { + for item in group.items { + if let itemFile = item.itemFile, itemFile.fileId.id == thumbnailFileId { + headerItem = EntityKeyboardAnimationData(file: itemFile) + break outer + } + } + } + } + } + } + return EmojiPagerContentComponent.ItemGroup( supergroupId: group.supergroupId, groupId: group.id, @@ -335,7 +354,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { hasClear: hasClear, isExpandable: group.isExpandable, displayPremiumBadges: false, - headerItem: group.headerItem, + headerItem: headerItem, items: group.items ) }, @@ -697,7 +716,9 @@ final class ChatEntityKeyboardInputNode: ChatInputNode { let subtitle: String = strings.StickerPack_StickerCount(Int32(featuredStickerPack.info.count)) var headerItem: EntityKeyboardAnimationData? - if let thumbnail = featuredStickerPack.info.thumbnail { + if let thumbnailFileId = featuredStickerPack.info.thumbnailFileId, let file = featuredStickerPack.topItems.first(where: { $0.file.fileId.id == thumbnailFileId }) { + headerItem = EntityKeyboardAnimationData(file: file.file) + } else if let thumbnail = featuredStickerPack.info.thumbnail { let info = featuredStickerPack.info let type: EntityKeyboardAnimationData.ItemType if item.file.isAnimatedSticker { diff --git a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift index f0b47b1c456..6f09bf8141d 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift @@ -2610,7 +2610,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { if replaceRange.location < 0 { break } - if inputText.attributedSubstring(from: replaceRange).string != previousText.string { + let adjacentString = inputText.attributedSubstring(from: replaceRange) + if adjacentString.string != previousText.string || adjacentString.attribute(ChatTextInputAttributes.customEmoji, at: 0, effectiveRange: nil) != nil { break } inputText.replaceCharacters(in: replaceRange, with: NSAttributedString(string: text, attributes: [ChatTextInputAttributes.customEmoji: ChatTextInputTextCustomEmojiAttribute(stickerPack: emojiAttribute.stickerPack, fileId: emojiAttribute.fileId, file: emojiAttribute.file)])) diff --git a/submodules/TelegramUI/Sources/HorizontalStickerGridItem.swift b/submodules/TelegramUI/Sources/HorizontalStickerGridItem.swift index 5385b989981..a19783d16de 100755 --- a/submodules/TelegramUI/Sources/HorizontalStickerGridItem.swift +++ b/submodules/TelegramUI/Sources/HorizontalStickerGridItem.swift @@ -51,6 +51,9 @@ final class HorizontalStickerGridItemNode: GridItemNode { let imageNode: TransformImageNode private(set) var animationNode: AnimatedStickerNode? private(set) var placeholderNode: StickerShimmerEffectNode? + private var lockBackground: UIVisualEffectView? + private var lockTintView: UIView? + private var lockIconNode: ASImageNode? private let stickerFetchedDisposable = MetaDisposable() @@ -193,6 +196,47 @@ final class HorizontalStickerGridItemNode: GridItemNode { self.stickerFetchedDisposable.set(freeMediaFileResourceInteractiveFetched(account: account, fileReference: stickerPackFileReference(item.file), resource: chatMessageStickerResource(file: item.file, small: true)).start()) } + if item.file.isPremiumSticker { + let lockBackground: UIVisualEffectView + let lockIconNode: ASImageNode + if let currentBackground = self.lockBackground, let currentIcon = self.lockIconNode { + lockBackground = currentBackground + lockIconNode = currentIcon + } else { + let effect: UIBlurEffect + if #available(iOS 10.0, *) { + effect = UIBlurEffect(style: .regular) + } else { + effect = UIBlurEffect(style: .light) + } + lockBackground = UIVisualEffectView(effect: effect) + lockBackground.clipsToBounds = true + lockBackground.isUserInteractionEnabled = false + lockIconNode = ASImageNode() + lockIconNode.displaysAsynchronously = false + lockIconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat List/PeerPremiumIcon"), color: .white) + lockIconNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0) + + let lockTintView = UIView() + lockTintView.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.15) + lockBackground.contentView.addSubview(lockTintView) + + self.lockBackground = lockBackground + self.lockTintView = lockTintView + self.lockIconNode = lockIconNode + + self.view.addSubview(lockBackground) + self.addSubnode(lockIconNode) + } + } else if let lockBackground = self.lockBackground, let lockTintView = self.lockTintView, let lockIconNode = self.lockIconNode { + self.lockBackground = nil + self.lockTintView = nil + self.lockIconNode = nil + lockBackground.removeFromSuperview() + lockTintView.removeFromSuperview() + lockIconNode.removeFromSupernode() + } + self.currentState = (account, item, dimensions.cgSize) self.setNeedsLayout() } @@ -228,6 +272,21 @@ final class HorizontalStickerGridItemNode: GridItemNode { animationNode.updateLayout(size: self.imageNode.bounds.size) } } + + if let lockBackground = self.lockBackground, let lockTintView = self.lockTintView, let lockIconNode = self.lockIconNode { + let lockSize = CGSize(width: 16.0, height: 16.0) + let lockBackgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: bounds.height - lockSize.height), size: lockSize) + lockBackground.frame = lockBackgroundFrame + lockBackground.layer.cornerRadius = lockSize.width / 2.0 + if #available(iOS 13.0, *) { + lockBackground.layer.cornerCurve = .circular + } + lockTintView.frame = CGRect(origin: CGPoint(), size: lockBackgroundFrame.size) + if let icon = lockIconNode.image { + let iconSize = CGSize(width: icon.size.width - 4.0, height: icon.size.height - 4.0) + lockIconNode.frame = CGRect(origin: CGPoint(x: lockBackgroundFrame.minX + floorToScreenPixels((lockBackgroundFrame.width - iconSize.width) / 2.0), y: lockBackgroundFrame.minY + floorToScreenPixels((lockBackgroundFrame.height - iconSize.height) / 2.0)), size: iconSize) + } + } } override func updateAbsoluteRect(_ absoluteRect: CGRect, within containerSize: CGSize) {