mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
348acd4ac2
3
.bazelrc
3
.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
|
||||
|
@ -59,7 +59,7 @@ public class ExternalMusicAlbumArtResource: Equatable {
|
||||
}
|
||||
|
||||
public func fetchExternalMusicAlbumArtResource(engine: TelegramEngine, file: FileMediaReference?, resource: ExternalMusicAlbumArtResource) -> Signal<EngineMediaResource.Fetch.Result, EngineMediaResource.Fetch.Error> {
|
||||
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 {
|
||||
|
@ -1,5 +1,9 @@
|
||||
import Foundation
|
||||
|
||||
public enum AtomicLockError: Error {
|
||||
case isLocked
|
||||
}
|
||||
|
||||
public final class Atomic<T> {
|
||||
private var lock: pthread_mutex_t
|
||||
private var value: T
|
||||
@ -23,6 +27,16 @@ public final class Atomic<T> {
|
||||
return result
|
||||
}
|
||||
|
||||
public func tryWith<R>(_ 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)
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -279,12 +279,12 @@ public extension TelegramEngine {
|
||||
}
|
||||
}
|
||||
|
||||
public func fetchAlbumCover(file: FileMediaReference?, title: String, performer: String) -> Signal<EngineMediaResource.Fetch.Result, EngineMediaResource.Fetch.Error> {
|
||||
public func fetchAlbumCover(file: FileMediaReference?, title: String, performer: String, isThumbnail: Bool) -> Signal<EngineMediaResource.Fetch.Result, EngineMediaResource.Fetch.Error> {
|
||||
let signal = currentWebDocumentsHostDatacenterId(postbox: self.account.postbox, isTestingEnvironment: self.account.testingEnvironment)
|
||||
|> castError(EngineMediaResource.Fetch.Error.self)
|
||||
|> take(1)
|
||||
|> mapToSignal { datacenterId -> Signal<EngineMediaResource.Fetch.Result, EngineMediaResource.Fetch.Error> 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,
|
||||
|
@ -184,13 +184,14 @@ public final class InlineStickerItemLayer: MultiAnimationRenderTarget {
|
||||
} else {
|
||||
let pointSize = self.pointSize
|
||||
let placeholderColor = self.placeholderColor
|
||||
let isThumbnailCancelled = Atomic<Bool>(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()
|
||||
}
|
||||
})
|
||||
|
@ -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<AnimationCacheItem>?
|
||||
|
||||
private var currentFrame: Frame?
|
||||
private var isLoadingFrame: Bool = false
|
||||
@ -241,8 +244,9 @@ private final class ItemAnimationContext {
|
||||
|
||||
let targets = Bag<Weak<MultiAnimationRenderTarget>>()
|
||||
|
||||
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,11 +336,16 @@ private final class ItemAnimationContext {
|
||||
|
||||
return LoadFrameGroupTask(task: { [weak self] in
|
||||
let currentFrame: Frame?
|
||||
if let frame = item.advance(advance: frameAdvance, requestedFormat: .rgba) {
|
||||
do {
|
||||
if let frame = try item.tryWith({ $0.advance(advance: frameAdvance, requestedFormat: .rgba) }) {
|
||||
currentFrame = Frame(frame: frame)
|
||||
} else {
|
||||
currentFrame = nil
|
||||
}
|
||||
} catch {
|
||||
assertionFailure()
|
||||
currentFrame = nil
|
||||
}
|
||||
|
||||
return {
|
||||
guard let strongSelf = self else {
|
||||
@ -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))
|
||||
let tasks0 = tasks.filter { $0.queueAffinity % 2 == 0 }
|
||||
let tasks1 = tasks.filter { $0.queueAffinity % 2 == 1 }
|
||||
let allTasks = [tasks0, tasks1]
|
||||
|
||||
var tasks0Completions: [() -> Void]?
|
||||
var tasks1Completions: [() -> Void]?
|
||||
let taskCompletions = Atomic<[Int: [() -> Void]]>(value: [:])
|
||||
let queues: [Queue] = [ItemAnimationContext.queue0, ItemAnimationContext.queue1]
|
||||
|
||||
let complete: (Int, [() -> Void]) -> Void = { index, completions in
|
||||
for i in 0 ..< 2 {
|
||||
let partTasks = allTasks[i]
|
||||
let id = i
|
||||
queues[i].async {
|
||||
var completions: [() -> Void] = []
|
||||
for task in partTasks {
|
||||
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 complete {
|
||||
Queue.mainQueue().async {
|
||||
if index == 0 {
|
||||
tasks0Completions = completions
|
||||
} else if index == 1 {
|
||||
tasks1Completions = completions
|
||||
let allCompletions = taskCompletions.with { $0 }
|
||||
for (_, fs) in allCompletions {
|
||||
for f in fs {
|
||||
f()
|
||||
}
|
||||
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 {
|
||||
var completions: [() -> Void] = []
|
||||
for task in tasks1 {
|
||||
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)
|
||||
}
|
||||
|
||||
if !completions.isEmpty {
|
||||
Queue.mainQueue().async {
|
||||
for completion in completions {
|
||||
completion()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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)]))
|
||||
|
@ -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) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user