Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Ilya Laktyushin 2022-08-03 22:44:04 +03:00
commit 348acd4ac2
12 changed files with 177 additions and 75 deletions

View File

@ -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

View File

@ -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 {

View File

@ -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)

View File

@ -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
)

View File

@ -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 {

View File

@ -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
)
}
}

View File

@ -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,

View File

@ -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()
}
})

View File

@ -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,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))
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
Queue.mainQueue().async {
if index == 0 {
tasks0Completions = completions
} else if index == 1 {
tasks1Completions = completions
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
}
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)
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()
}
}
}
}

View File

@ -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 {

View File

@ -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)]))

View 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) {