mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge commit 'acac317502496d98930a6e2ca375f343182b35e5'
This commit is contained in:
commit
b7bc1854e6
@ -5,7 +5,7 @@ import AVFoundation
|
|||||||
public extension UnicodeScalar {
|
public extension UnicodeScalar {
|
||||||
var isEmoji: Bool {
|
var isEmoji: Bool {
|
||||||
switch self.value {
|
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
|
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:
|
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
|
return true
|
||||||
|
@ -492,7 +492,10 @@ class PremiumStarComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let from = node.presentation.eulerAngles
|
var from = node.presentation.eulerAngles
|
||||||
|
if abs(from.y - .pi * 2.0) < 0.001 {
|
||||||
|
from.y = 0.0
|
||||||
|
}
|
||||||
node.removeAnimation(forKey: "tapRotate")
|
node.removeAnimation(forKey: "tapRotate")
|
||||||
|
|
||||||
var toValue: Float = smallAngle ? 0.0 : .pi * 2.0
|
var toValue: Float = smallAngle ? 0.0 : .pi * 2.0
|
||||||
@ -505,9 +508,9 @@ class PremiumStarComponent: Component {
|
|||||||
let to = SCNVector3(x: 0.0, y: toValue, z: 0.0)
|
let to = SCNVector3(x: 0.0, y: toValue, z: 0.0)
|
||||||
let distance = rad2deg(to.y - from.y)
|
let distance = rad2deg(to.y - from.y)
|
||||||
|
|
||||||
guard !distance.isZero else {
|
// guard !distance.isZero else {
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
let springAnimation = CASpringAnimation(keyPath: "eulerAngles")
|
let springAnimation = CASpringAnimation(keyPath: "eulerAngles")
|
||||||
springAnimation.fromValue = NSValue(scnVector3: from)
|
springAnimation.fromValue = NSValue(scnVector3: from)
|
||||||
@ -517,7 +520,11 @@ class PremiumStarComponent: Component {
|
|||||||
springAnimation.damping = 5.8
|
springAnimation.damping = 5.8
|
||||||
springAnimation.duration = springAnimation.settlingDuration * 0.75
|
springAnimation.duration = springAnimation.settlingDuration * 0.75
|
||||||
springAnimation.initialVelocity = velocity.flatMap { abs($0 / CGFloat(distance)) } ?? 1.7
|
springAnimation.initialVelocity = velocity.flatMap { abs($0 / CGFloat(distance)) } ?? 1.7
|
||||||
|
springAnimation.completion = { [weak node] finished in
|
||||||
|
if finished {
|
||||||
|
node?.eulerAngles = SCNVector3(x: 0.0, y: 0.0, z: 0.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
node.addAnimation(springAnimation, forKey: "rotate")
|
node.addAnimation(springAnimation, forKey: "rotate")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -411,7 +411,7 @@ private func stringForSelectiveSettings(strings: PresentationStrings, settings:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func privacyAndSecurityControllerEntries(presentationData: PresentationData, state: PrivacyAndSecurityControllerState, privacySettings: AccountPrivacySettings?, accessChallengeData: PostboxAccessChallengeData, blockedPeerCount: Int?, activeWebsitesCount: Int, hasTwoStepAuth: Bool?, twoStepAuthData: TwoStepVerificationAccessConfiguration?, canAutoarchive: Bool, isPremium: Bool) -> [PrivacyAndSecurityEntry] {
|
private func privacyAndSecurityControllerEntries(presentationData: PresentationData, state: PrivacyAndSecurityControllerState, privacySettings: AccountPrivacySettings?, accessChallengeData: PostboxAccessChallengeData, blockedPeerCount: Int?, activeWebsitesCount: Int, hasTwoStepAuth: Bool?, twoStepAuthData: TwoStepVerificationAccessConfiguration?, canAutoarchive: Bool, isPremiumDisabled: Bool, isPremium: Bool) -> [PrivacyAndSecurityEntry] {
|
||||||
var entries: [PrivacyAndSecurityEntry] = []
|
var entries: [PrivacyAndSecurityEntry] = []
|
||||||
|
|
||||||
entries.append(.blockedPeers(presentationData.theme, presentationData.strings.Settings_BlockedUsers, blockedPeerCount == nil ? "" : (blockedPeerCount == 0 ? presentationData.strings.PrivacySettings_BlockedPeersEmpty : "\(blockedPeerCount!)")))
|
entries.append(.blockedPeers(presentationData.theme, presentationData.strings.Settings_BlockedUsers, blockedPeerCount == nil ? "" : (blockedPeerCount == 0 ? presentationData.strings.PrivacySettings_BlockedPeersEmpty : "\(blockedPeerCount!)")))
|
||||||
@ -449,7 +449,9 @@ private func privacyAndSecurityControllerEntries(presentationData: PresentationD
|
|||||||
entries.append(.lastSeenPrivacy(presentationData.theme, presentationData.strings.PrivacySettings_LastSeen, stringForSelectiveSettings(strings: presentationData.strings, settings: privacySettings.presence)))
|
entries.append(.lastSeenPrivacy(presentationData.theme, presentationData.strings.PrivacySettings_LastSeen, stringForSelectiveSettings(strings: presentationData.strings, settings: privacySettings.presence)))
|
||||||
entries.append(.profilePhotoPrivacy(presentationData.theme, presentationData.strings.Privacy_ProfilePhoto, stringForSelectiveSettings(strings: presentationData.strings, settings: privacySettings.profilePhoto)))
|
entries.append(.profilePhotoPrivacy(presentationData.theme, presentationData.strings.Privacy_ProfilePhoto, stringForSelectiveSettings(strings: presentationData.strings, settings: privacySettings.profilePhoto)))
|
||||||
entries.append(.voiceCallPrivacy(presentationData.theme, presentationData.strings.Privacy_Calls, stringForSelectiveSettings(strings: presentationData.strings, settings: privacySettings.voiceCalls)))
|
entries.append(.voiceCallPrivacy(presentationData.theme, presentationData.strings.Privacy_Calls, stringForSelectiveSettings(strings: presentationData.strings, settings: privacySettings.voiceCalls)))
|
||||||
entries.append(.voiceMessagePrivacy(presentationData.theme, presentationData.strings.Privacy_VoiceMessages, stringForSelectiveSettings(strings: presentationData.strings, settings: privacySettings.voiceMessages), !isPremium))
|
if !isPremiumDisabled {
|
||||||
|
entries.append(.voiceMessagePrivacy(presentationData.theme, presentationData.strings.Privacy_VoiceMessages, stringForSelectiveSettings(strings: presentationData.strings, settings: privacySettings.voiceMessages), !isPremium))
|
||||||
|
}
|
||||||
entries.append(.forwardPrivacy(presentationData.theme, presentationData.strings.Privacy_Forwards, stringForSelectiveSettings(strings: presentationData.strings, settings: privacySettings.forwards)))
|
entries.append(.forwardPrivacy(presentationData.theme, presentationData.strings.Privacy_Forwards, stringForSelectiveSettings(strings: presentationData.strings, settings: privacySettings.forwards)))
|
||||||
entries.append(.groupPrivacy(presentationData.theme, presentationData.strings.Privacy_GroupsAndChannels, stringForSelectiveSettings(strings: presentationData.strings, settings: privacySettings.groupInvitations)))
|
entries.append(.groupPrivacy(presentationData.theme, presentationData.strings.Privacy_GroupsAndChannels, stringForSelectiveSettings(strings: presentationData.strings, settings: privacySettings.groupInvitations)))
|
||||||
|
|
||||||
@ -458,7 +460,9 @@ private func privacyAndSecurityControllerEntries(presentationData: PresentationD
|
|||||||
entries.append(.lastSeenPrivacy(presentationData.theme, presentationData.strings.PrivacySettings_LastSeen, presentationData.strings.Channel_NotificationLoading))
|
entries.append(.lastSeenPrivacy(presentationData.theme, presentationData.strings.PrivacySettings_LastSeen, presentationData.strings.Channel_NotificationLoading))
|
||||||
entries.append(.profilePhotoPrivacy(presentationData.theme, presentationData.strings.Privacy_ProfilePhoto, presentationData.strings.Channel_NotificationLoading))
|
entries.append(.profilePhotoPrivacy(presentationData.theme, presentationData.strings.Privacy_ProfilePhoto, presentationData.strings.Channel_NotificationLoading))
|
||||||
entries.append(.voiceCallPrivacy(presentationData.theme, presentationData.strings.Privacy_Calls, presentationData.strings.Channel_NotificationLoading))
|
entries.append(.voiceCallPrivacy(presentationData.theme, presentationData.strings.Privacy_Calls, presentationData.strings.Channel_NotificationLoading))
|
||||||
entries.append(.voiceMessagePrivacy(presentationData.theme, presentationData.strings.Privacy_VoiceMessages, presentationData.strings.Channel_NotificationLoading, !isPremium))
|
if !isPremiumDisabled {
|
||||||
|
entries.append(.voiceMessagePrivacy(presentationData.theme, presentationData.strings.Privacy_VoiceMessages, presentationData.strings.Channel_NotificationLoading, !isPremium))
|
||||||
|
}
|
||||||
entries.append(.forwardPrivacy(presentationData.theme, presentationData.strings.Privacy_Forwards, presentationData.strings.Channel_NotificationLoading))
|
entries.append(.forwardPrivacy(presentationData.theme, presentationData.strings.Privacy_Forwards, presentationData.strings.Channel_NotificationLoading))
|
||||||
entries.append(.groupPrivacy(presentationData.theme, presentationData.strings.Privacy_GroupsAndChannels, presentationData.strings.Channel_NotificationLoading))
|
entries.append(.groupPrivacy(presentationData.theme, presentationData.strings.Privacy_GroupsAndChannels, presentationData.strings.Channel_NotificationLoading))
|
||||||
entries.append(.selectivePrivacyInfo(presentationData.theme, presentationData.strings.PrivacyLastSeenSettings_GroupsAndChannelsHelp))
|
entries.append(.selectivePrivacyInfo(presentationData.theme, presentationData.strings.PrivacyLastSeenSettings_GroupsAndChannelsHelp))
|
||||||
@ -772,10 +776,14 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
|
|||||||
let hapticFeedback = HapticFeedback()
|
let hapticFeedback = HapticFeedback()
|
||||||
hapticFeedback.impact()
|
hapticFeedback.impact()
|
||||||
|
|
||||||
|
var alreadyPresented = false
|
||||||
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: presentationData.strings.Privacy_VoiceMessages_Tooltip), elevatedLayout: false, animateInAsReplacement: false, action: { action in
|
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: presentationData.strings.Privacy_VoiceMessages_Tooltip), elevatedLayout: false, animateInAsReplacement: false, action: { action in
|
||||||
if action == .info {
|
if action == .info {
|
||||||
let controller = PremiumIntroScreen(context: context, source: .settings)
|
if !alreadyPresented {
|
||||||
pushControllerImpl?(controller, true)
|
let controller = PremiumIntroScreen(context: context, source: .settings)
|
||||||
|
pushControllerImpl?(controller, true)
|
||||||
|
alreadyPresented = true
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@ -961,8 +969,9 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
|
|||||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.PrivacySettings_Title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
|
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.PrivacySettings_Title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
|
||||||
|
|
||||||
let isPremium = accountPeer?.isPremium ?? false
|
let isPremium = accountPeer?.isPremium ?? false
|
||||||
|
let isPremiumDisabled = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }).isPremiumDisabled
|
||||||
|
|
||||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: privacyAndSecurityControllerEntries(presentationData: presentationData, state: state, privacySettings: privacySettings, accessChallengeData: accessChallengeData.data, blockedPeerCount: blockedPeersState.totalCount, activeWebsitesCount: activeWebsitesState.sessions.count, hasTwoStepAuth: twoStepAuth.0, twoStepAuthData: twoStepAuth.1, canAutoarchive: canAutoarchive, isPremium: isPremium), style: .blocks, ensureVisibleItemTag: focusOnItemTag, animateChanges: false)
|
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: privacyAndSecurityControllerEntries(presentationData: presentationData, state: state, privacySettings: privacySettings, accessChallengeData: accessChallengeData.data, blockedPeerCount: blockedPeersState.totalCount, activeWebsitesCount: activeWebsitesState.sessions.count, hasTwoStepAuth: twoStepAuth.0, twoStepAuthData: twoStepAuth.1, canAutoarchive: canAutoarchive, isPremiumDisabled: isPremiumDisabled, isPremium: isPremium), style: .blocks, ensureVisibleItemTag: focusOnItemTag, animateChanges: false)
|
||||||
|
|
||||||
return (controllerState, (listState, arguments))
|
return (controllerState, (listState, arguments))
|
||||||
}
|
}
|
||||||
|
@ -139,8 +139,8 @@ func chatHistoryEntriesForView(
|
|||||||
contentTypeHint = .animatedEmoji
|
contentTypeHint = .animatedEmoji
|
||||||
} else if stickersEnabled && message.text.count == 1, let _ = associatedData.animatedEmojiStickers[message.text.basicEmoji.0], (message.textEntitiesAttribute?.entities.isEmpty ?? true) {
|
} else if stickersEnabled && message.text.count == 1, let _ = associatedData.animatedEmojiStickers[message.text.basicEmoji.0], (message.textEntitiesAttribute?.entities.isEmpty ?? true) {
|
||||||
contentTypeHint = .animatedEmoji
|
contentTypeHint = .animatedEmoji
|
||||||
} else if message.text.count < 10 && messageIsElligibleForLargeEmoji(message) {
|
} else if messageIsElligibleForLargeEmoji(message) {
|
||||||
contentTypeHint = .largeEmoji
|
contentTypeHint = .animatedEmoji
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -218,10 +218,12 @@ func chatHistoryEntriesForView(
|
|||||||
|
|
||||||
var contentTypeHint: ChatMessageEntryContentType = .generic
|
var contentTypeHint: ChatMessageEntryContentType = .generic
|
||||||
if presentationData.largeEmoji, topMessage.media.isEmpty {
|
if presentationData.largeEmoji, topMessage.media.isEmpty {
|
||||||
if stickersEnabled && topMessage.text.count == 1, let _ = associatedData.animatedEmojiStickers[topMessage.text.basicEmoji.0] {
|
if stickersEnabled && messageIsElligibleForLargeCustomEmoji(topMessage) {
|
||||||
|
contentTypeHint = .animatedEmoji
|
||||||
|
} else if stickersEnabled && topMessage.text.count == 1, let _ = associatedData.animatedEmojiStickers[topMessage.text.basicEmoji.0] {
|
||||||
|
contentTypeHint = .animatedEmoji
|
||||||
|
} else if messageIsElligibleForLargeEmoji(topMessage) {
|
||||||
contentTypeHint = .animatedEmoji
|
contentTypeHint = .animatedEmoji
|
||||||
} else if topMessage.text.count < 10 && messageIsElligibleForLargeEmoji(topMessage) {
|
|
||||||
contentTypeHint = .largeEmoji
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -543,17 +543,10 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
|
|
||||||
var emojiFile: TelegramMediaFile?
|
var emojiFile: TelegramMediaFile?
|
||||||
var emojiString: String?
|
var emojiString: String?
|
||||||
if let entities = item.message.textEntitiesAttribute?.entities {
|
if messageIsElligibleForLargeCustomEmoji(item.message) || (item.message.text.count > 1 && messageIsElligibleForLargeEmoji(item.message)) {
|
||||||
if entities.count == 1, case let .CustomEmoji(_, fileId) = entities[0].type {
|
emojiString = item.message.text
|
||||||
if let file = item.message.associatedMedia[MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)] as? TelegramMediaFile {
|
|
||||||
emojiFile = file
|
|
||||||
}
|
|
||||||
} else if messageIsElligibleForLargeCustomEmoji(item.message) {
|
|
||||||
emojiString = item.message.text
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if emojiFile == nil && emojiString == nil {
|
if emojiFile == nil && emojiString == nil {
|
||||||
emojiFile = item.associatedData.animatedEmojiStickers[emoji]?.first?.file
|
emojiFile = item.associatedData.animatedEmojiStickers[emoji]?.first?.file
|
||||||
}
|
}
|
||||||
@ -566,12 +559,18 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
} else if self.emojiFile?.id != emojiFile?.id {
|
} else if self.emojiFile?.id != emojiFile?.id {
|
||||||
self.emojiFile = emojiFile
|
self.emojiFile = emojiFile
|
||||||
if let 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?
|
var fitzModifier: EmojiFitzModifier?
|
||||||
if let fitz = fitz {
|
if let fitz = fitz {
|
||||||
fitzModifier = EmojiFitzModifier(emoji: 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())
|
self.disposable.set(freeMediaFileInteractiveFetched(account: item.context.account, fileReference: .standalone(media: emojiFile)).start())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -667,7 +666,10 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
self.didSetUpAnimationNode = true
|
self.didSetUpAnimationNode = true
|
||||||
|
|
||||||
if let file = file {
|
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 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)
|
let pathPrefix = item.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(file.resource.id)
|
||||||
@ -703,7 +705,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
|
|
||||||
if !alreadySeen {
|
if !alreadySeen {
|
||||||
item.controllerInteraction.seenOneTimeAnimatedMedia.insert(item.message.id)
|
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)
|
self.playAdditionalEmojiAnimation(index: 1)
|
||||||
} else if let file = file, file.isPremiumSticker {
|
} else if let file = file, file.isPremiumSticker {
|
||||||
Queue.mainQueue().after(0.1) {
|
Queue.mainQueue().after(0.1) {
|
||||||
@ -940,7 +942,11 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
isEmoji = true
|
isEmoji = true
|
||||||
|
|
||||||
let displaySize = CGSize(width: floor(displaySize.width * item.presentationData.animatedEmojiScale), height: floor(displaySize.height * item.presentationData.animatedEmojiScale))
|
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)
|
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 {
|
} else if let thumbnailSize = emojiFile.previewRepresentations.first?.dimensions {
|
||||||
imageSize = thumbnailSize.cgSize.aspectFitted(displaySize)
|
imageSize = thumbnailSize.cgSize.aspectFitted(displaySize)
|
||||||
@ -961,9 +967,13 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
deliveryFailedInset += 24.0
|
deliveryFailedInset += 24.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if item.message.forwardInfo != nil || item.message.attributes.first(where: { $0 is ReplyMessageAttribute }) != nil {
|
||||||
|
tmpWidth -= 60.0
|
||||||
|
}
|
||||||
|
|
||||||
tmpWidth -= deliveryFailedInset
|
tmpWidth -= deliveryFailedInset
|
||||||
|
|
||||||
let maximumContentWidth = floor(tmpWidth - layoutConstants.bubble.edgeInset - layoutConstants.bubble.edgeInset - layoutConstants.bubble.contentInsets.left - layoutConstants.bubble.contentInsets.right - avatarInset)
|
let maximumContentWidth = floor(tmpWidth - layoutConstants.bubble.edgeInset - layoutConstants.bubble.edgeInset - layoutConstants.bubble.contentInsets.left - layoutConstants.bubble.contentInsets.right - avatarInset) - 70.0
|
||||||
|
|
||||||
let font = Font.regular(fontSizeForEmojiString(item.message.text))
|
let font = Font.regular(fontSizeForEmojiString(item.message.text))
|
||||||
let attributedText = stringWithAppliedEntities(item.message.text, entities: item.message.textEntitiesAttribute?.entities ?? [], baseColor: .black, linkColor: .black, baseFont: font, linkFont: font, boldFont: font, italicFont: font, boldItalicFont: font, fixedFont: font, blockQuoteFont: font, message: item.message)
|
let attributedText = stringWithAppliedEntities(item.message.text, entities: item.message.textEntitiesAttribute?.entities ?? [], baseColor: .black, linkColor: .black, baseFont: font, linkFont: font, boldFont: font, italicFont: font, boldItalicFont: font, fixedFont: font, blockQuoteFont: font, message: item.message)
|
||||||
@ -1060,7 +1070,8 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
var needsReplyBackground = false
|
var needsReplyBackground = false
|
||||||
var replyMarkup: ReplyMarkupMessageAttribute?
|
var replyMarkup: ReplyMarkupMessageAttribute?
|
||||||
|
|
||||||
let availableContentWidth = max(60.0, params.width - params.leftInset - params.rightInset - max(imageSize.width, 160.0) - 20.0 - layoutConstants.bubble.edgeInset * 2.0 - avatarInset - layoutConstants.bubble.contentInsets.left)
|
|
||||||
|
let availableContentWidth = min(120.0, max(60.0, params.width - params.leftInset - params.rightInset - max(imageSize.width, 160.0) - 20.0 - layoutConstants.bubble.edgeInset * 2.0 - avatarInset - layoutConstants.bubble.contentInsets.left))
|
||||||
|
|
||||||
var ignoreForward = false
|
var ignoreForward = false
|
||||||
if let forwardInfo = item.message.forwardInfo {
|
if let forwardInfo = item.message.forwardInfo {
|
||||||
@ -1632,7 +1643,16 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func commitEnqueuedAnimations() {
|
private func commitEnqueuedAnimations() {
|
||||||
guard let item = self.item, let file = self.emojiFile, !self.enqueuedAdditionalAnimations.isEmpty else {
|
guard let item = self.item, !self.enqueuedAdditionalAnimations.isEmpty else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var emojiFile = self.emojiFile
|
||||||
|
if emojiFile == nil {
|
||||||
|
emojiFile = item.message.associatedMedia.first?.value as? TelegramMediaFile
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let file = emojiFile else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1744,7 +1764,21 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let source = AnimatedStickerResourceSource(account: item.context.account, resource: resource, fitzModifier: nil)
|
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
|
return
|
||||||
}
|
}
|
||||||
if self.additionalAnimationNodes.count >= 4 {
|
if self.additionalAnimationNodes.count >= 4 {
|
||||||
@ -1762,8 +1796,8 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
overlayMeshAnimationNode = current
|
overlayMeshAnimationNode = current
|
||||||
} else {
|
} else {
|
||||||
if let animationView = MeshRenderer() {
|
if let animationView = MeshRenderer() {
|
||||||
let animationFrame = animationNode.frame.insetBy(dx: -animationNode.frame.width, dy: -animationNode.frame.height)
|
let animationFrame = animationNodeFrame.insetBy(dx: -animationNodeFrame.width, dy: -animationNodeFrame.height)
|
||||||
.offsetBy(dx: incomingMessage ? animationNode.frame.width - 10.0 : -animationNode.frame.width + 10.0, dy: 0.0)
|
.offsetBy(dx: incomingMessage ? animationNodeFrame.width - 10.0 : -animationNodeFrame.width + 10.0, dy: 0.0)
|
||||||
animationView.frame = animationFrame
|
animationView.frame = animationFrame
|
||||||
|
|
||||||
animationView.allAnimationsCompleted = { [weak transitionNode, weak animationView, weak self] in
|
animationView.allAnimationsCompleted = { [weak transitionNode, weak animationView, weak self] in
|
||||||
@ -1794,10 +1828,10 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
var animationFrame: CGRect
|
var animationFrame: CGRect
|
||||||
if isStickerEffect {
|
if isStickerEffect {
|
||||||
let scale: CGFloat = 0.245
|
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 {
|
} else {
|
||||||
animationFrame = animationNode.frame.insetBy(dx: -animationNode.frame.width, dy: -animationNode.frame.height)
|
animationFrame = animationNodeFrame.insetBy(dx: -animationNodeFrame.width, dy: -animationNodeFrame.height)
|
||||||
.offsetBy(dx: incomingMessage ? animationNode.frame.width - 10.0 : -animationNode.frame.width + 10.0, dy: 0.0)
|
.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))
|
animationFrame = animationFrame.offsetBy(dx: CGFloat.random(in: -30.0 ... 30.0), dy: CGFloat.random(in: -30.0 ... 30.0))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1881,8 +1915,189 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let item = self.item, self.imageNode.frame.contains(location) {
|
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 {
|
if let file = self.telegramFile {
|
||||||
let noPremium = item.message.attributes.contains(where: { attribute in
|
let noPremium = item.message.attributes.contains(where: { attribute in
|
||||||
if attribute is NonPremiumMessageAttribute {
|
if attribute is NonPremiumMessageAttribute {
|
||||||
@ -1916,176 +2131,8 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
if !animationNode.isPlaying && !emojiFile.isCustomEmoji {
|
if !animationNode.isPlaying && !emojiFile.isCustomEmoji {
|
||||||
shouldPlay = true
|
shouldPlay = true
|
||||||
}
|
}
|
||||||
|
if let result = emojiTapAction(shouldPlay) {
|
||||||
let beatingHearts: [UInt32] = [0x2764, 0x1F90E, 0x1F9E1, 0x1F499, 0x1F49A, 0x1F49C, 0x1F49B, 0x1F5A4, 0x1F90D]
|
return result
|
||||||
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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2592,7 +2639,7 @@ private func fontSizeForEmojiString(_ string: String) -> CGFloat {
|
|||||||
|
|
||||||
var maxLineLength = 0
|
var maxLineLength = 0
|
||||||
for line in lines {
|
for line in lines {
|
||||||
maxLineLength = max(maxLineLength, line.count)
|
maxLineLength = max(maxLineLength, line.replacingOccurrences(of: " ", with: "").count)
|
||||||
}
|
}
|
||||||
|
|
||||||
let linesCount = lines.count
|
let linesCount = lines.count
|
||||||
@ -2602,6 +2649,8 @@ private func fontSizeForEmojiString(_ string: String) -> CGFloat {
|
|||||||
let basicSize: CGFloat = 94.0
|
let basicSize: CGFloat = 94.0
|
||||||
let multiplier: CGFloat
|
let multiplier: CGFloat
|
||||||
switch length {
|
switch length {
|
||||||
|
case 1:
|
||||||
|
multiplier = 1.0
|
||||||
case 2:
|
case 2:
|
||||||
multiplier = 0.7
|
multiplier = 0.7
|
||||||
case 3:
|
case 3:
|
||||||
|
@ -192,7 +192,7 @@ private func matchingEmojiEntry(_ emoji: String) -> (UInt8, UInt8, UInt8)? {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func messageIsElligibleForLargeEmoji(_ message: Message) -> Bool {
|
func messageIsElligibleForLargeEmoji(_ message: Message) -> Bool {
|
||||||
if !message.text.isEmpty && message.text.containsOnlyEmoji && message.text.emojis.count < 6 {
|
if !message.text.isEmpty && message.text.containsOnlyEmoji {
|
||||||
if !(message.textEntitiesAttribute?.entities.isEmpty ?? true) {
|
if !(message.textEntitiesAttribute?.entities.isEmpty ?? true) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user