Various fixes

This commit is contained in:
Ilya Laktyushin 2022-08-03 15:35:28 +03:00
parent 31ba2e963f
commit 3cc42cd034
2 changed files with 227 additions and 189 deletions

View File

@ -5,7 +5,7 @@ import AVFoundation
public extension UnicodeScalar {
var isEmoji: Bool {
switch self.value {
case 0x1F600...0x1F64F, 0x1F300...0x1F5FF, 0x1F680...0x1F6FF, 0x1F1E6...0x1F1FF, 0xE0020...0xE007F, 0xFE00...0xFE0F, 0x1F900...0x1F9FF, 0x1F018...0x1F0F5, 0x1F200...0x1F270, 65024...65039, 9100...9300, 8400...8447, 0x1F004, 0x1F18E, 0x1F191...0x1F19A, 0x1F5E8, 0x1FA70...0x1FA73, 0x1FA78...0x1FA7A, 0x1FA80...0x1FA82, 0x1FA90...0x1FA95, 0x1F382:
case 0x1F600...0x1F64F, 0x1F300...0x1F5FF, 0x1F680...0x1F6FF, 0x1F1E6...0x1F1FF, 0xE0020...0xE007F, 0xFE00...0xFE0F, 0x1F900...0x1F9FF, 0x1F018...0x1F0F5, 0x1F200...0x1F270, 65024...65039, 9100...9300, 8400...8447, 0x1F004, 0x1F18E, 0x1F191...0x1F19A, 0x1F5E8, 0x1FA70...0x1FA73, 0x1FA78...0x1FA7A, 0x1FA80...0x1FA82, 0x1FA90...0x1FA95, 0x1F382, 0x1FAF1, 0x1FAF2:
return true
case 0x2603, 0x265F, 0x267E, 0x2692, 0x26C4, 0x26C8, 0x26CE, 0x26CF, 0x26D1...0x26D3, 0x26E9, 0x26F0...0x26F9, 0x2705, 0x270A, 0x270B, 0x2728, 0x274E, 0x2753...0x2755, 0x274C, 0x2795...0x2797, 0x27B0, 0x27BF:
return true

View File

@ -543,12 +543,8 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
var emojiFile: TelegramMediaFile?
var emojiString: String?
if let entities = item.message.textEntitiesAttribute?.entities {
if entities.count == 1, case let .CustomEmoji(_, fileId) = entities[0].type {
if let file = item.message.associatedMedia[MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)] as? TelegramMediaFile {
emojiFile = file
}
} else if messageIsElligibleForLargeCustomEmoji(item.message) {
if let _ = item.message.textEntitiesAttribute?.entities {
if messageIsElligibleForLargeCustomEmoji(item.message) {
emojiString = item.message.text
}
}
@ -566,12 +562,18 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
} else if self.emojiFile?.id != emojiFile?.id {
self.emojiFile = emojiFile
if let emojiFile = emojiFile {
let dimensions = emojiFile.dimensions ?? PixelDimensions(width: 512, height: 512)
var dimensions = emojiFile.dimensions ?? PixelDimensions(width: 512, height: 512)
if emojiFile.isCustomEmoji {
dimensions = PixelDimensions(dimensions.cgSize.aspectFitted(CGSize(width: 512.0, height: 512.0)))
}
var fitzModifier: EmojiFitzModifier?
if let fitz = fitz {
fitzModifier = EmojiFitzModifier(emoji: fitz)
}
self.imageNode.setSignal(chatMessageAnimatedSticker(postbox: item.context.account.postbox, file: emojiFile, small: false, size: dimensions.cgSize.aspectFilled(CGSize(width: 384.0, height: 384.0)), fitzModifier: fitzModifier, thumbnail: false, synchronousLoad: synchronousLoad), attemptSynchronously: synchronousLoad)
let fillSize = emojiFile.isCustomEmoji ? CGSize(width: 512.0, height: 512.0) : CGSize(width: 384.0, height: 384.0)
self.imageNode.setSignal(chatMessageAnimatedSticker(postbox: item.context.account.postbox, file: emojiFile, small: false, size: dimensions.cgSize.aspectFilled(fillSize), fitzModifier: fitzModifier, thumbnail: false, synchronousLoad: synchronousLoad), attemptSynchronously: synchronousLoad)
self.disposable.set(freeMediaFileInteractiveFetched(account: item.context.account, fileReference: .standalone(media: emojiFile)).start())
}
@ -667,7 +669,10 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
self.didSetUpAnimationNode = true
if let file = file {
let dimensions = file.dimensions ?? PixelDimensions(width: 512, height: 512)
var dimensions = file.dimensions ?? PixelDimensions(width: 512, height: 512)
if file.isCustomEmoji {
dimensions = PixelDimensions(dimensions.cgSize.aspectFitted(CGSize(width: 512.0, height: 512.0)))
}
let fittedSize = isEmoji ? dimensions.cgSize.aspectFilled(CGSize(width: 384.0, height: 384.0)) : dimensions.cgSize.aspectFitted(CGSize(width: 384.0, height: 384.0))
let pathPrefix = item.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(file.resource.id)
@ -703,7 +708,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
if !alreadySeen {
item.controllerInteraction.seenOneTimeAnimatedMedia.insert(item.message.id)
if let file = file, file.isCustomEmoji {
if let emojiString = self.emojiString, emojiString.count == 1 {
self.playAdditionalEmojiAnimation(index: 1)
} else if let file = file, file.isPremiumSticker {
Queue.mainQueue().after(0.1) {
@ -940,7 +945,11 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
isEmoji = true
let displaySize = CGSize(width: floor(displaySize.width * item.presentationData.animatedEmojiScale), height: floor(displaySize.height * item.presentationData.animatedEmojiScale))
if let dimensions = emojiFile.dimensions {
if var dimensions = emojiFile.dimensions {
if emojiFile.isCustomEmoji {
dimensions = PixelDimensions(dimensions.cgSize.aspectFitted(CGSize(width: 512.0, height: 512.0)))
}
imageSize = CGSize(width: displaySize.width * CGFloat(dimensions.width) / 512.0, height: displaySize.height * CGFloat(dimensions.height) / 512.0)
} else if let thumbnailSize = emojiFile.previewRepresentations.first?.dimensions {
imageSize = thumbnailSize.cgSize.aspectFitted(displaySize)
@ -1744,7 +1753,21 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
}
let source = AnimatedStickerResourceSource(account: item.context.account, resource: resource, fitzModifier: nil)
guard let animationSize = self.animationSize, let animationNode = self.animationNode else {
let animationSize: CGSize?
let animationNodeFrame: CGRect?
if let size = self.animationSize, let node = self.animationNode {
animationSize = size
animationNodeFrame = node.frame
} else if let _ = self.emojiString {
animationSize = CGSize(width: 384.0, height: 384.0)
animationNodeFrame = self.textNode.textNode.frame
} else {
animationSize = nil
animationNodeFrame = nil
}
guard let animationSize = animationSize, let animationNodeFrame = animationNodeFrame else {
return
}
if self.additionalAnimationNodes.count >= 4 {
@ -1762,8 +1785,8 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
overlayMeshAnimationNode = current
} else {
if let animationView = MeshRenderer() {
let animationFrame = animationNode.frame.insetBy(dx: -animationNode.frame.width, dy: -animationNode.frame.height)
.offsetBy(dx: incomingMessage ? animationNode.frame.width - 10.0 : -animationNode.frame.width + 10.0, dy: 0.0)
let animationFrame = animationNodeFrame.insetBy(dx: -animationNodeFrame.width, dy: -animationNodeFrame.height)
.offsetBy(dx: incomingMessage ? animationNodeFrame.width - 10.0 : -animationNodeFrame.width + 10.0, dy: 0.0)
animationView.frame = animationFrame
animationView.allAnimationsCompleted = { [weak transitionNode, weak animationView, weak self] in
@ -1794,10 +1817,10 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
var animationFrame: CGRect
if isStickerEffect {
let scale: CGFloat = 0.245
animationFrame = animationNode.frame.offsetBy(dx: incomingMessage ? animationNode.frame.width * scale - 21.0 : -animationNode.frame.width * scale + 21.0, dy: -1.0).insetBy(dx: -animationNode.frame.width * scale, dy: -animationNode.frame.height * scale)
animationFrame = animationNodeFrame.offsetBy(dx: incomingMessage ? animationNodeFrame.width * scale - 21.0 : -animationNodeFrame.width * scale + 21.0, dy: -1.0).insetBy(dx: -animationNodeFrame.width * scale, dy: -animationNodeFrame.height * scale)
} else {
animationFrame = animationNode.frame.insetBy(dx: -animationNode.frame.width, dy: -animationNode.frame.height)
.offsetBy(dx: incomingMessage ? animationNode.frame.width - 10.0 : -animationNode.frame.width + 10.0, dy: 0.0)
animationFrame = animationNodeFrame.insetBy(dx: -animationNodeFrame.width, dy: -animationNodeFrame.height)
.offsetBy(dx: incomingMessage ? animationNodeFrame.width - 10.0 : -animationNodeFrame.width + 10.0, dy: 0.0)
animationFrame = animationFrame.offsetBy(dx: CGFloat.random(in: -30.0 ... 30.0), dy: CGFloat.random(in: -30.0 ... 30.0))
}
@ -1881,8 +1904,189 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
}
}
}
if let item = self.item, self.imageNode.frame.contains(location) {
let emojiTapAction: (Bool) -> InternalBubbleTapAction? = { shouldPlay in
let beatingHearts: [UInt32] = [0x2764, 0x1F90E, 0x1F9E1, 0x1F499, 0x1F49A, 0x1F49C, 0x1F49B, 0x1F5A4, 0x1F90D]
let heart = 0x2764
let peach = 0x1F351
let coffin = 0x26B0
let appConfiguration = item.context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration])
|> take(1)
|> map { view in
return view.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) ?? .defaultValue
}
let text = item.message.text
if var firstScalar = text.unicodeScalars.first {
var textEmoji = text.strippedEmoji
var additionalTextEmoji = textEmoji
if beatingHearts.contains(firstScalar.value) {
textEmoji = "❤️"
firstScalar = UnicodeScalar(heart)!
}
let (basicEmoji, fitz) = text.basicEmoji
if ["💛", "💙", "💚", "💜", "🧡", "🖤", "🤎", "🤍", "❤️"].contains(textEmoji) {
additionalTextEmoji = "❤️".strippedEmoji
} else if fitz != nil {
additionalTextEmoji = basicEmoji
}
let syncAnimations = item.message.id.peerId.namespace == Namespaces.Peer.CloudUser
return .optionalAction({
var haptic: EmojiHaptic?
if let current = self.haptic {
haptic = current
} else {
if firstScalar.value == heart {
haptic = HeartbeatHaptic()
} else if firstScalar.value == coffin {
haptic = CoffinHaptic()
} else if firstScalar.value == peach {
haptic = PeachHaptic()
}
haptic?.enabled = true
self.haptic = haptic
}
if syncAnimations, let animationItems = item.associatedData.additionalAnimatedEmojiStickers[additionalTextEmoji] {
let playHaptic = haptic == nil
var hapticFeedback: HapticFeedback
if let current = self.hapticFeedback {
hapticFeedback = current
} else {
hapticFeedback = HapticFeedback()
self.hapticFeedback = hapticFeedback
}
if syncAnimations {
self.startAdditionalAnimationsCommitTimer()
}
let timestamp = CACurrentMediaTime()
let previousAnimation = self.enqueuedAdditionalAnimations.last
var availableAnimations = animationItems
var delay: Double = 0.0
if availableAnimations.count > 1, let (previousIndex, _) = previousAnimation {
availableAnimations.removeValue(forKey: previousIndex)
}
if let (_, previousTimestamp) = previousAnimation {
delay = min(0.15, max(0.0, previousTimestamp + 0.15 - timestamp))
}
if let index = availableAnimations.randomElement()?.0 {
if delay > 0.0 {
Queue.mainQueue().after(delay) {
if playHaptic {
if previousAnimation == nil {
hapticFeedback.impact(.light)
} else {
let style: ImpactHapticFeedbackStyle
if self.enqueuedAdditionalAnimations.count == 1 {
style = .medium
} else {
style = [.light, .medium].randomElement() ?? .medium
}
hapticFeedback.impact(style)
}
}
if syncAnimations {
self.enqueuedAdditionalAnimations.append((index, timestamp + delay))
}
self.playAdditionalEmojiAnimation(index: index)
if syncAnimations, self.additionalAnimationsCommitTimer == nil {
self.startAdditionalAnimationsCommitTimer()
}
}
} else {
if playHaptic {
if previousAnimation == nil {
hapticFeedback.impact(.light)
} else {
let style: ImpactHapticFeedbackStyle
if self.enqueuedAdditionalAnimations.count == 1 {
style = .medium
} else {
style = [.light, .medium].randomElement() ?? .medium
}
hapticFeedback.impact(style)
}
}
if syncAnimations {
self.enqueuedAdditionalAnimations.append((index, timestamp))
}
self.playAdditionalEmojiAnimation(index: index)
}
}
} else if let emojiString = self.emojiString, emojiString.count == 1 {
let _ = item.controllerInteraction.openMessage(item.message, .default)
}
if shouldPlay {
let _ = (appConfiguration
|> deliverOnMainQueue).start(next: { [weak self] appConfiguration in
guard let strongSelf = self else {
return
}
let emojiSounds = AnimatedEmojiSoundsConfiguration.with(appConfiguration: appConfiguration, account: item.context.account)
var hasSound = false
for (emoji, file) in emojiSounds.sounds {
if emoji.strippedEmoji == textEmoji.strippedEmoji {
hasSound = true
let mediaManager = item.context.sharedContext.mediaManager
let mediaPlayer = MediaPlayer(audioSessionManager: mediaManager.audioSession, postbox: item.context.account.postbox, resourceReference: .standalone(resource: file.resource), streamable: .none, video: false, preferSoftwareDecoding: false, enableSound: true, fetchAutomatically: true, ambient: true)
mediaPlayer.togglePlayPause()
mediaPlayer.actionAtEnd = .action({ [weak self] in
self?.mediaPlayer = nil
})
strongSelf.mediaPlayer = mediaPlayer
strongSelf.mediaStatusDisposable.set((mediaPlayer.status
|> deliverOnMainQueue).start(next: { [weak self] status in
if let strongSelf = self {
if let haptic = haptic, !haptic.active {
haptic.start(time: 0.0)
}
switch status.status {
case .playing:
if let animationNode = strongSelf.animationNode as? AnimatedStickerNode {
animationNode.play(firstFrame: false, fromIndex: nil)
}
strongSelf.mediaStatusDisposable.set(nil)
default:
break
}
}
}))
return
}
}
if !hasSound {
if let haptic = haptic, !haptic.active {
haptic.start(time: 0.0)
}
if let animationNode = strongSelf.animationNode as? AnimatedStickerNode {
animationNode.play(firstFrame: false, fromIndex: nil)
}
}
})
}
})
}
return nil
}
if let emojiString = self.emojiString, emojiString.count == 1 {
return emojiTapAction(false)
}
if let file = self.telegramFile {
let noPremium = item.message.attributes.contains(where: { attribute in
if attribute is NonPremiumMessageAttribute {
@ -1916,176 +2120,8 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
if !animationNode.isPlaying && !emojiFile.isCustomEmoji {
shouldPlay = true
}
let beatingHearts: [UInt32] = [0x2764, 0x1F90E, 0x1F9E1, 0x1F499, 0x1F49A, 0x1F49C, 0x1F49B, 0x1F5A4, 0x1F90D]
let heart = 0x2764
let peach = 0x1F351
let coffin = 0x26B0
let appConfiguration = item.context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration])
|> take(1)
|> map { view in
return view.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) ?? .defaultValue
}
let text = item.message.text
if var firstScalar = text.unicodeScalars.first {
var textEmoji = text.strippedEmoji
var additionalTextEmoji = textEmoji
if beatingHearts.contains(firstScalar.value) {
textEmoji = "❤️"
firstScalar = UnicodeScalar(heart)!
}
let (basicEmoji, fitz) = text.basicEmoji
if ["💛", "💙", "💚", "💜", "🧡", "🖤", "🤎", "🤍", "❤️"].contains(textEmoji) {
additionalTextEmoji = "❤️".strippedEmoji
} else if fitz != nil {
additionalTextEmoji = basicEmoji
}
let syncAnimations = item.message.id.peerId.namespace == Namespaces.Peer.CloudUser
return .optionalAction({
var haptic: EmojiHaptic?
if let current = self.haptic {
haptic = current
} else {
if firstScalar.value == heart {
haptic = HeartbeatHaptic()
} else if firstScalar.value == coffin {
haptic = CoffinHaptic()
} else if firstScalar.value == peach {
haptic = PeachHaptic()
}
haptic?.enabled = true
self.haptic = haptic
}
if syncAnimations, let animationItems = item.associatedData.additionalAnimatedEmojiStickers[additionalTextEmoji] {
let playHaptic = haptic == nil
var hapticFeedback: HapticFeedback
if let current = self.hapticFeedback {
hapticFeedback = current
} else {
hapticFeedback = HapticFeedback()
self.hapticFeedback = hapticFeedback
}
if syncAnimations {
self.startAdditionalAnimationsCommitTimer()
}
let timestamp = CACurrentMediaTime()
let previousAnimation = self.enqueuedAdditionalAnimations.last
var availableAnimations = animationItems
var delay: Double = 0.0
if availableAnimations.count > 1, let (previousIndex, _) = previousAnimation {
availableAnimations.removeValue(forKey: previousIndex)
}
if let (_, previousTimestamp) = previousAnimation {
delay = min(0.15, max(0.0, previousTimestamp + 0.15 - timestamp))
}
if let index = availableAnimations.randomElement()?.0 {
if delay > 0.0 {
Queue.mainQueue().after(delay) {
if playHaptic {
if previousAnimation == nil {
hapticFeedback.impact(.light)
} else {
let style: ImpactHapticFeedbackStyle
if self.enqueuedAdditionalAnimations.count == 1 {
style = .medium
} else {
style = [.light, .medium].randomElement() ?? .medium
}
hapticFeedback.impact(style)
}
}
if syncAnimations {
self.enqueuedAdditionalAnimations.append((index, timestamp + delay))
}
self.playAdditionalEmojiAnimation(index: index)
if syncAnimations, self.additionalAnimationsCommitTimer == nil {
self.startAdditionalAnimationsCommitTimer()
}
}
} else {
if playHaptic {
if previousAnimation == nil {
hapticFeedback.impact(.light)
} else {
let style: ImpactHapticFeedbackStyle
if self.enqueuedAdditionalAnimations.count == 1 {
style = .medium
} else {
style = [.light, .medium].randomElement() ?? .medium
}
hapticFeedback.impact(style)
}
}
if syncAnimations {
self.enqueuedAdditionalAnimations.append((index, timestamp))
}
self.playAdditionalEmojiAnimation(index: index)
}
}
} else if emojiFile.isCustomEmoji {
let _ = item.controllerInteraction.openMessage(item.message, .default)
}
if shouldPlay {
let _ = (appConfiguration
|> deliverOnMainQueue).start(next: { [weak self, weak animationNode] appConfiguration in
guard let strongSelf = self else {
return
}
let emojiSounds = AnimatedEmojiSoundsConfiguration.with(appConfiguration: appConfiguration, account: item.context.account)
var hasSound = false
for (emoji, file) in emojiSounds.sounds {
if emoji.strippedEmoji == textEmoji.strippedEmoji {
hasSound = true
let mediaManager = item.context.sharedContext.mediaManager
let mediaPlayer = MediaPlayer(audioSessionManager: mediaManager.audioSession, postbox: item.context.account.postbox, resourceReference: .standalone(resource: file.resource), streamable: .none, video: false, preferSoftwareDecoding: false, enableSound: true, fetchAutomatically: true, ambient: true)
mediaPlayer.togglePlayPause()
mediaPlayer.actionAtEnd = .action({ [weak self] in
self?.mediaPlayer = nil
})
strongSelf.mediaPlayer = mediaPlayer
strongSelf.mediaStatusDisposable.set((mediaPlayer.status
|> deliverOnMainQueue).start(next: { [weak self, weak animationNode] status in
if let strongSelf = self {
if let haptic = haptic, !haptic.active {
haptic.start(time: 0.0)
}
switch status.status {
case .playing:
animationNode?.play(firstFrame: false, fromIndex: nil)
strongSelf.mediaStatusDisposable.set(nil)
default:
break
}
}
}))
return
}
}
if !hasSound {
if let haptic = haptic, !haptic.active {
haptic.start(time: 0.0)
}
animationNode?.play(firstFrame: false, fromIndex: nil)
}
})
}
})
if let result = emojiTapAction(shouldPlay) {
return result
}
}
}
@ -2602,6 +2638,8 @@ private func fontSizeForEmojiString(_ string: String) -> CGFloat {
let basicSize: CGFloat = 94.0
let multiplier: CGFloat
switch length {
case 1:
multiplier = 1.0
case 2:
multiplier = 0.7
case 3: