mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-15 21:45:19 +00:00
Emoji status and reaction improvements
This commit is contained in:
parent
e26c4a6bf0
commit
c535d51621
@ -7221,6 +7221,7 @@ Sorry for the inconvenience.";
|
||||
|
||||
"Channel.AdminLog.AllowedReactionsUpdated" = "%1$@ updated the list of allowed reactions to: %2$@";
|
||||
"Channel.AdminLog.ReactionsDisabled" = "%1$@ disabled reactions";
|
||||
"Channel.AdminLog.ReactionsEnabled" = "%1$@ enabled all reactions";
|
||||
|
||||
"Contacts.ScanQrCode" = "Scan QR Code";
|
||||
"Contacts.QrCode.MyCode" = "My QR Code";
|
||||
|
@ -23,6 +23,8 @@ swift_library(
|
||||
"//submodules/MeshAnimationCache:MeshAnimationCache",
|
||||
"//submodules/Utils/RangeSet:RangeSet",
|
||||
"//submodules/InAppPurchaseManager:InAppPurchaseManager",
|
||||
"//submodules/TelegramUI/Components/AnimationCache:AnimationCache",
|
||||
"//submodules/TelegramUI/Components/MultiAnimationRenderer:MultiAnimationRenderer",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -12,6 +12,8 @@ import DeviceLocationManager
|
||||
import TemporaryCachedPeerDataManager
|
||||
import MeshAnimationCache
|
||||
import InAppPurchaseManager
|
||||
import AnimationCache
|
||||
import MultiAnimationRenderer
|
||||
|
||||
public final class TelegramApplicationOpenUrlCompletion {
|
||||
public let completion: (Bool) -> Void
|
||||
@ -885,6 +887,9 @@ public protocol AccountContext: AnyObject {
|
||||
var cachedGroupCallContexts: AccountGroupCallContextCache { get }
|
||||
var meshAnimationCache: MeshAnimationCache { get }
|
||||
|
||||
var animationCache: AnimationCache { get }
|
||||
var animationRenderer: MultiAnimationRenderer { get }
|
||||
|
||||
var animatedEmojiStickers: [String: [StickerPackItem]] { get }
|
||||
|
||||
var userLimits: EngineConfiguration.UserLimits { get }
|
||||
|
@ -36,6 +36,7 @@ swift_library(
|
||||
"//submodules/TelegramCore:TelegramCore",
|
||||
"//submodules/AnimationCompression:AnimationCompression",
|
||||
"//submodules/Components/MetalImageView:MetalImageView",
|
||||
"//submodules/WebPBinding:WebPBinding",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -7,6 +7,7 @@ import CoreMedia
|
||||
import ManagedFile
|
||||
import Accelerate
|
||||
import TelegramCore
|
||||
import WebPBinding
|
||||
|
||||
private let sharedStoreQueue = Queue.concurrentDefaultQueue()
|
||||
|
||||
@ -282,6 +283,7 @@ final class VideoStickerDirectFrameSource: AnimatedStickerFrameSource {
|
||||
private let width: Int
|
||||
private let height: Int
|
||||
private let cache: VideoStickerFrameSourceCache?
|
||||
private let image: UIImage?
|
||||
private let bytesPerRow: Int
|
||||
var frameCount: Int
|
||||
let frameRate: Int
|
||||
@ -290,7 +292,11 @@ final class VideoStickerDirectFrameSource: AnimatedStickerFrameSource {
|
||||
private let source: SoftwareVideoSource?
|
||||
|
||||
var frameIndex: Int {
|
||||
return self.currentFrame % self.frameCount
|
||||
if self.frameCount == 0 {
|
||||
return 0
|
||||
} else {
|
||||
return self.currentFrame % self.frameCount
|
||||
}
|
||||
}
|
||||
|
||||
init?(queue: Queue, path: String, width: Int, height: Int, cachePathPrefix: String?, unpremultiplyAlpha: Bool = true) {
|
||||
@ -307,11 +313,18 @@ final class VideoStickerDirectFrameSource: AnimatedStickerFrameSource {
|
||||
|
||||
if useCache, let cache = self.cache, cache.frameCount > 0 {
|
||||
self.source = nil
|
||||
self.image = nil
|
||||
self.frameRate = Int(cache.frameRate)
|
||||
self.frameCount = Int(cache.frameCount)
|
||||
} else if let data = try? Data(contentsOf: URL(fileURLWithPath: path)), let image = WebP.convert(fromWebP: data) {
|
||||
self.source = nil
|
||||
self.image = image
|
||||
self.frameRate = 1
|
||||
self.frameCount = 1
|
||||
} else {
|
||||
let source = SoftwareVideoSource(path: path, hintVP9: true, unpremultiplyAlpha: unpremultiplyAlpha)
|
||||
self.source = source
|
||||
self.image = nil
|
||||
self.frameRate = min(30, source.getFramerate())
|
||||
self.frameCount = 0
|
||||
}
|
||||
@ -331,7 +344,15 @@ final class VideoStickerDirectFrameSource: AnimatedStickerFrameSource {
|
||||
|
||||
self.currentFrame += 1
|
||||
if draw {
|
||||
if useCache, let cache = self.cache, let yuvData = cache.readUncompressedYuvaFrame(index: frameIndex) {
|
||||
if let image = self.image {
|
||||
let context = DrawingContext(size: CGSize(width: self.width, height: self.height), scale: 1.0, opaque: false, clear: true, bytesPerRow: self.bytesPerRow)
|
||||
context.withFlippedContext { c in
|
||||
c.draw(image.cgImage!, in: CGRect(origin: CGPoint(), size: context.size))
|
||||
}
|
||||
let frameData = Data(bytes: context.bytes, count: self.bytesPerRow * self.height)
|
||||
|
||||
return AnimatedStickerFrame(data: frameData, type: .argb, width: self.width, height: self.height, bytesPerRow: self.bytesPerRow, index: frameIndex, isLastFrame: frameIndex == self.frameCount - 1, totalFrames: self.frameCount, multiplyAlpha: true)
|
||||
} else if useCache, let cache = self.cache, let yuvData = cache.readUncompressedYuvaFrame(index: frameIndex) {
|
||||
return AnimatedStickerFrame(data: yuvData, type: .yuva, width: self.width, height: self.height, bytesPerRow: self.width * 2, index: frameIndex, isLastFrame: frameIndex == self.frameCount - 1, totalFrames: self.frameCount)
|
||||
} else if let source = self.source {
|
||||
let frameAndLoop = source.readFrame(maxPts: nil)
|
||||
|
@ -336,10 +336,8 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
|
||||
self.presentController = presentController
|
||||
self.makeEntityInputView = makeEntityInputView
|
||||
|
||||
self.animationCache = AnimationCacheImpl(basePath: context.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: {
|
||||
return TempBox.shared.tempFile(fileName: "file").path
|
||||
})
|
||||
self.animationRenderer = MultiAnimationRendererImpl()
|
||||
self.animationCache = context.animationCache
|
||||
self.animationRenderer = context.animationRenderer
|
||||
|
||||
var hasSpoilers = true
|
||||
if presentationInterfaceState.chatLocation.peerId?.namespace == Namespaces.Peer.SecretChat {
|
||||
|
@ -190,10 +190,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
self.presentationData = (context.sharedContext.currentPresentationData.with { $0 })
|
||||
self.presentationDataValue.set(.single(self.presentationData))
|
||||
|
||||
self.animationCache = AnimationCacheImpl(basePath: context.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: {
|
||||
return TempBox.shared.tempFile(fileName: "file").path
|
||||
})
|
||||
self.animationRenderer = MultiAnimationRendererImpl()
|
||||
self.animationCache = context.animationCache
|
||||
self.animationRenderer = context.animationRenderer
|
||||
|
||||
self.titleView = ChatListTitleView(
|
||||
context: context,
|
||||
@ -327,20 +325,25 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
return (data.isLockable, false)
|
||||
}
|
||||
|
||||
let peerStatus: Signal<NetworkStatusTitle.Status?, NoError> = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|
||||
|> map { peer -> NetworkStatusTitle.Status? in
|
||||
guard case let .user(user) = peer else {
|
||||
return nil
|
||||
}
|
||||
if let emojiStatus = user.emojiStatus {
|
||||
return .emoji(emojiStatus)
|
||||
} else if user.isPremium {
|
||||
return .premium
|
||||
} else {
|
||||
return nil
|
||||
let peerStatus: Signal<NetworkStatusTitle.Status?, NoError>
|
||||
if filter != nil || groupId != .root {
|
||||
peerStatus = .single(nil)
|
||||
} else {
|
||||
peerStatus = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|
||||
|> map { peer -> NetworkStatusTitle.Status? in
|
||||
guard case let .user(user) = peer else {
|
||||
return nil
|
||||
}
|
||||
if let emojiStatus = user.emojiStatus {
|
||||
return .emoji(emojiStatus)
|
||||
} else if user.isPremium {
|
||||
return .premium
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
|
||||
let previousEditingAndNetworkStateValue = Atomic<(Bool, AccountNetworkState)?>(value: nil)
|
||||
if !self.hideNetworkActivityStatus {
|
||||
@ -858,7 +861,10 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
areUnicodeEmojiEnabled: false,
|
||||
areCustomEmojiEnabled: true,
|
||||
chatPeerId: self.context.account.peerId
|
||||
)
|
||||
),
|
||||
destinationItemView: { [weak sourceView] in
|
||||
return sourceView
|
||||
}
|
||||
), in: .window(.root))
|
||||
}
|
||||
|
||||
|
@ -384,6 +384,7 @@ final class ChatListTitleView: UIView, NavigationBarTitleView, NavigationBarTitl
|
||||
containerSize: CGSize(width: 22.0, height: 22.0)
|
||||
)
|
||||
titleCredibilityIconTransition.setFrame(view: titleCredibilityIconView, frame: CGRect(origin: CGPoint(x: titleFrame.maxX + 2.0, y: floorToScreenPixels(titleFrame.midY - titleIconSize.height / 2.0)), size: titleIconSize))
|
||||
titleCredibilityIconView.isHidden = self.title.activity
|
||||
} else {
|
||||
if let titleCredibilityIconView = self.titleCredibilityIconView {
|
||||
self.titleCredibilityIconView = nil
|
||||
|
@ -597,7 +597,7 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
|
||||
self.readStats = readStats
|
||||
|
||||
var mergedItems: [EngineMessageReactionListContext.Item] = listState.items
|
||||
if !listState.canLoadMore, let readStats = readStats {
|
||||
if !listState.canLoadMore, let readStats = readStats {
|
||||
var existingPeers = Set(mergedItems.map(\.peer.id))
|
||||
for peer in readStats.peers {
|
||||
if !existingPeers.contains(peer.id) {
|
||||
@ -607,10 +607,6 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
|
||||
}
|
||||
}
|
||||
|
||||
/*for _ in 0 ..< 5 {
|
||||
mergedItems.append(contentsOf: mergedItems)
|
||||
}*/
|
||||
|
||||
self.mergedItems = mergedItems
|
||||
}
|
||||
|
||||
|
@ -974,7 +974,10 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
strongSelf.statusNode.frame = statusFrame
|
||||
transition.animatePositionAdditive(node: strongSelf.statusNode, offset: CGPoint(x: previousStatusFrame.minX - statusFrame.minX, y: 0))
|
||||
|
||||
if let credibilityIcon = credibilityIcon, let animationCache = item.animationCache, let animationRenderer = item.animationRenderer {
|
||||
if let credibilityIcon = credibilityIcon {
|
||||
let animationCache = item.context.animationCache
|
||||
let animationRenderer = item.context.animationRenderer
|
||||
|
||||
let credibilityIconView: ComponentHostView<Empty>
|
||||
if let current = strongSelf.credibilityIconView {
|
||||
credibilityIconView = current
|
||||
|
@ -576,12 +576,15 @@ final class InnerTextSelectionTipContainerNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
self.highlightBackgroundNode.backgroundColor = presentationData.theme.contextMenu.itemHighlightedBackgroundColor
|
||||
self.highlightBackgroundNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||
self.buttonNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||
|
||||
return size
|
||||
}
|
||||
|
||||
func setActualSize(size: CGSize, transition: ContainedViewLayoutTransition) {
|
||||
self.highlightBackgroundNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||
self.buttonNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||
}
|
||||
|
||||
func updateTheme(presentationData: PresentationData) {
|
||||
self.backgroundColor = presentationData.theme.contextMenu.backgroundColor
|
||||
}
|
||||
@ -802,6 +805,7 @@ final class ContextActionsContainerNode: ASDisplayNode {
|
||||
contentSize.height += 8.0
|
||||
let textSelectionTipSize = textSelectionTipNode.updateLayout(widthClass: widthClass, width: actionsSize.width, transition: transition)
|
||||
transition.updateFrame(node: textSelectionTipNode, frame: CGRect(origin: CGPoint(x: 0.0, y: contentSize.height), size: textSelectionTipSize))
|
||||
textSelectionTipNode.setActualSize(size: textSelectionTipSize, transition: transition)
|
||||
contentSize.height += textSelectionTipSize.height
|
||||
}
|
||||
|
||||
|
@ -963,7 +963,7 @@ final class ContextControllerActionsStackNode: ASDisplayNode {
|
||||
return (size, apparentHeight)
|
||||
}
|
||||
|
||||
func updateTip(presentationData: PresentationData, width: CGFloat, transition: ContainedViewLayoutTransition) -> (node: ASDisplayNode, height: CGFloat)? {
|
||||
func updateTip(presentationData: PresentationData, width: CGFloat, transition: ContainedViewLayoutTransition) -> (node: InnerTextSelectionTipContainerNode, height: CGFloat)? {
|
||||
if let tip = self.tip {
|
||||
var updatedTransition = transition
|
||||
if let tipNode = self.tipNode, tipNode.tip == tip {
|
||||
@ -1172,7 +1172,7 @@ final class ContextControllerActionsStackNode: ASDisplayNode {
|
||||
let animateAppearingContainers = transition.isAnimated && !self.dismissingItemContainers.isEmpty
|
||||
|
||||
struct TipLayout {
|
||||
var tipNode: ASDisplayNode
|
||||
var tipNode: InnerTextSelectionTipContainerNode
|
||||
var tipHeight: CGFloat
|
||||
}
|
||||
|
||||
@ -1320,11 +1320,13 @@ final class ContextControllerActionsStackNode: ASDisplayNode {
|
||||
self.addSubnode(tip.tipNode)
|
||||
animateTipIn = transition.isAnimated
|
||||
tip.tipNode.frame = CGRect(origin: CGPoint(x: previousNavigationContainerFrame.minX, y: previousNavigationContainerFrame.maxY + tipSpacing), size: CGSize(width: itemLayouts[i].size.width, height: tip.tipHeight))
|
||||
tip.tipNode.setActualSize(size: tip.tipNode.bounds.size, transition: .immediate)
|
||||
}
|
||||
|
||||
let tipAlpha: CGFloat = itemLayouts[i].alphaTransitionFraction
|
||||
|
||||
tipTransition.updateFrame(node: tip.tipNode, frame: CGRect(origin: CGPoint(x: navigationContainerFrame.minX, y: navigationContainerFrame.maxY + tipSpacing), size: CGSize(width: itemLayouts[i].size.width, height: tip.tipHeight)), beginWithCurrentState: true)
|
||||
tip.tipNode.setActualSize(size: tip.tipNode.bounds.size, transition: tipTransition)
|
||||
|
||||
if animateTipIn {
|
||||
tip.tipNode.alpha = tipAlpha
|
||||
|
@ -671,7 +671,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
reactionContextNodeTransition = .immediate
|
||||
}
|
||||
reactionContextNodeTransition.updateFrame(node: reactionContextNode, frame: CGRect(origin: CGPoint(), size: layout.size), beginWithCurrentState: true)
|
||||
reactionContextNode.updateLayout(size: layout.size, insets: UIEdgeInsets(top: topInset, left: 0.0, bottom: 0.0, right: 0.0), anchorRect: contentRect.offsetBy(dx: contentParentGlobalFrame.minX, dy: 0.0), isAnimatingOut: isAnimatingOut, transition: reactionContextNodeTransition)
|
||||
reactionContextNode.updateLayout(size: layout.size, insets: UIEdgeInsets(top: topInset, left: layout.safeInsets.left, bottom: 0.0, right: layout.safeInsets.right), anchorRect: contentRect.offsetBy(dx: contentParentGlobalFrame.minX, dy: 0.0), isAnimatingOut: isAnimatingOut, transition: reactionContextNodeTransition)
|
||||
|
||||
self.proposedReactionsPositionLock = contentRect.minY - 18.0 - reactionContextNode.contentHeight - 46.0
|
||||
} else {
|
||||
|
@ -356,10 +356,8 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
self.statusNode = RadialStatusNode(backgroundNodeColor: .clear)
|
||||
self.statusNode.isUserInteractionEnabled = false
|
||||
|
||||
self.animationCache = AnimationCacheImpl(basePath: context.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: {
|
||||
return TempBox.shared.tempFile(fileName: "file").path
|
||||
})
|
||||
self.animationRenderer = MultiAnimationRendererImpl()
|
||||
self.animationCache = context.animationCache
|
||||
self.animationRenderer = context.animationRenderer
|
||||
|
||||
super.init()
|
||||
|
||||
|
@ -36,10 +36,8 @@ public final class HashtagSearchController: TelegramBaseController {
|
||||
self.peer = peer
|
||||
self.query = query
|
||||
|
||||
self.animationCache = AnimationCacheImpl(basePath: context.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: {
|
||||
return TempBox.shared.tempFile(fileName: "file").path
|
||||
})
|
||||
self.animationRenderer = MultiAnimationRendererImpl()
|
||||
self.animationCache = context.animationCache
|
||||
self.animationRenderer = context.animationRenderer
|
||||
|
||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
|
@ -23,6 +23,8 @@ swift_library(
|
||||
"//submodules/ContextUI:ContextUI",
|
||||
"//submodules/PresentationDataUtils:PresentationDataUtils",
|
||||
"//submodules/AccountContext:AccountContext",
|
||||
"//submodules/ComponentFlow:ComponentFlow",
|
||||
"//submodules/TelegramUI/Components/EmojiStatusComponent:EmojiStatusComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -13,6 +13,8 @@ import TelegramStringFormatting
|
||||
import PeerPresenceStatusManager
|
||||
import ContextUI
|
||||
import AccountContext
|
||||
import ComponentFlow
|
||||
import EmojiStatusComponent
|
||||
|
||||
private final class ShimmerEffectNode: ASDisplayNode {
|
||||
private var currentBackgroundColor: UIColor?
|
||||
@ -460,7 +462,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
|
||||
private let labelBadgeNode: ASImageNode
|
||||
private var labelArrowNode: ASImageNode?
|
||||
private let statusNode: TextNode
|
||||
private var credibilityIconNode: ASImageNode?
|
||||
private var credibilityIconView: ComponentHostView<Empty>?
|
||||
private var switchNode: SwitchNode?
|
||||
private var checkNode: ASImageNode?
|
||||
|
||||
@ -601,27 +603,34 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
|
||||
let labelDisclosureFont = Font.regular(item.presentationData.fontSize.itemListBaseFontSize)
|
||||
|
||||
var updatedLabelBadgeImage: UIImage?
|
||||
var currentCredibilityIconImage: UIImage?
|
||||
var credibilityIcon: EmojiStatusComponent.Content?
|
||||
|
||||
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: item.context.currentAppConfiguration.with { $0 })
|
||||
|
||||
if case .threatSelfAsSaved = item.aliasHandling, item.peer.id == item.context.account.peerId {
|
||||
|
||||
} else {
|
||||
if item.peer.isScam {
|
||||
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
|
||||
credibilityIcon = .scam(color: item.presentationData.theme.chat.message.incoming.scamColor)
|
||||
} else if item.peer.isFake {
|
||||
currentCredibilityIconImage = PresentationResourcesChatList.fakeIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
|
||||
credibilityIcon = .fake(color: item.presentationData.theme.chat.message.incoming.scamColor)
|
||||
} else if case let .user(user) = item.peer, let emojiStatus = user.emojiStatus {
|
||||
credibilityIcon = .emojiStatus(status: emojiStatus, size: CGSize(width: 20.0, height: 20.0), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor)
|
||||
} else if item.peer.isVerified {
|
||||
currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme)
|
||||
credibilityIcon = .verified(fillColor: item.presentationData.theme.list.itemCheckColors.fillColor, foregroundColor: item.presentationData.theme.list.itemCheckColors.foregroundColor)
|
||||
} else if item.peer.isPremium && !premiumConfiguration.isPremiumDisabled {
|
||||
currentCredibilityIconImage = PresentationResourcesChatList.premiumIcon(item.presentationData.theme)
|
||||
credibilityIcon = .premium(color: item.presentationData.theme.list.itemAccentColor)
|
||||
}
|
||||
}
|
||||
|
||||
var titleIconsWidth: CGFloat = 0.0
|
||||
if let currentCredibilityIconImage = currentCredibilityIconImage {
|
||||
titleIconsWidth += 4.0 + currentCredibilityIconImage.size.width
|
||||
if let credibilityIcon = credibilityIcon {
|
||||
titleIconsWidth += 4.0
|
||||
switch credibilityIcon {
|
||||
case .scam, .fake:
|
||||
titleIconsWidth += 30.0
|
||||
default:
|
||||
titleIconsWidth += 16.0
|
||||
}
|
||||
}
|
||||
|
||||
var badgeColor: UIColor?
|
||||
@ -1063,23 +1072,38 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
|
||||
transition.updateFrame(node: strongSelf.titleNode, frame: titleFrame)
|
||||
transition.updateFrame(node: strongSelf.statusNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: strongSelf.titleNode.frame.maxY + titleSpacing), size: statusLayout.size))
|
||||
|
||||
if let currentCredibilityIconImage = currentCredibilityIconImage {
|
||||
let iconNode: ASImageNode
|
||||
if let current = strongSelf.credibilityIconNode {
|
||||
iconNode = current
|
||||
if let credibilityIcon = credibilityIcon {
|
||||
let animationCache = item.context.animationCache
|
||||
let animationRenderer = item.context.animationRenderer
|
||||
|
||||
let credibilityIconView: ComponentHostView<Empty>
|
||||
if let current = strongSelf.credibilityIconView {
|
||||
credibilityIconView = current
|
||||
} else {
|
||||
iconNode = ASImageNode()
|
||||
iconNode.isLayerBacked = true
|
||||
iconNode.displaysAsynchronously = false
|
||||
iconNode.displayWithoutProcessing = true
|
||||
strongSelf.containerNode.addSubnode(iconNode)
|
||||
strongSelf.credibilityIconNode = iconNode
|
||||
credibilityIconView = ComponentHostView<Empty>()
|
||||
strongSelf.containerNode.view.addSubview(credibilityIconView)
|
||||
strongSelf.credibilityIconView = credibilityIconView
|
||||
}
|
||||
iconNode.image = currentCredibilityIconImage
|
||||
transition.updateFrame(node: iconNode, frame: CGRect(origin: CGPoint(x: titleFrame.maxX + 4.0, y: floorToScreenPixels(titleFrame.midY - currentCredibilityIconImage.size.height / 2.0) - UIScreenPixel), size: currentCredibilityIconImage.size))
|
||||
} else if let credibilityIconNode = strongSelf.credibilityIconNode {
|
||||
strongSelf.credibilityIconNode = nil
|
||||
credibilityIconNode.removeFromSupernode()
|
||||
|
||||
let iconSize = credibilityIconView.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(EmojiStatusComponent(
|
||||
context: item.context,
|
||||
animationCache: animationCache,
|
||||
animationRenderer: animationRenderer,
|
||||
content: credibilityIcon,
|
||||
action: nil,
|
||||
longTapAction: nil,
|
||||
emojiFileUpdated: nil
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 20.0, height: 20.0)
|
||||
)
|
||||
|
||||
transition.updateFrame(view: credibilityIconView, frame: CGRect(origin: CGPoint(x: titleFrame.maxX + 4.0, y: floorToScreenPixels(titleFrame.midY - iconSize.height / 2.0)), size: iconSize))
|
||||
} else if let credibilityIconView = strongSelf.credibilityIconView {
|
||||
strongSelf.credibilityIconView = nil
|
||||
credibilityIconView.removeFromSuperview()
|
||||
}
|
||||
|
||||
if let currentSwitchNode = currentSwitchNode {
|
||||
@ -1321,8 +1345,8 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
|
||||
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: self.titleNode.frame.minY), size: self.titleNode.bounds.size))
|
||||
transition.updateFrame(node: self.statusNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: self.statusNode.frame.minY), size: self.statusNode.bounds.size))
|
||||
|
||||
if let credibilityIconNode = self.credibilityIconNode {
|
||||
transition.updateFrame(node: credibilityIconNode, frame: CGRect(origin: CGPoint(x: self.titleNode.frame.maxX + 4.0, y: credibilityIconNode.frame.minY), size: credibilityIconNode.bounds.size))
|
||||
if let credibilityIconView = self.credibilityIconView {
|
||||
transition.updateFrame(view: credibilityIconView, frame: CGRect(origin: CGPoint(x: self.titleNode.frame.maxX + 4.0, y: credibilityIconView.frame.minY), size: credibilityIconView.bounds.size))
|
||||
}
|
||||
|
||||
var rightLabelInset: CGFloat = 15.0 + params.rightInset
|
||||
|
@ -1667,10 +1667,8 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
||||
self.present = present
|
||||
self.completion = completion
|
||||
|
||||
self.animationCache = AnimationCacheImpl(basePath: context.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: {
|
||||
return TempBox.shared.tempFile(fileName: "file").path
|
||||
})
|
||||
self.animationRenderer = MultiAnimationRendererImpl()
|
||||
self.animationCache = context.animationCache
|
||||
self.animationRenderer = context.animationRenderer
|
||||
|
||||
super.init()
|
||||
|
||||
|
@ -127,10 +127,8 @@ private class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
|
||||
self.animationCache = AnimationCacheImpl(basePath: self.context.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: {
|
||||
return TempBox.shared.tempFile(fileName: "file").path
|
||||
})
|
||||
self.animationRenderer = MultiAnimationRendererImpl()
|
||||
self.animationCache = context.animationCache
|
||||
self.animationRenderer = context.animationRenderer
|
||||
|
||||
var reactionMap: [MessageReaction.Reaction: AvailableReactions.Reaction] = [:]
|
||||
for reaction in reactions {
|
||||
@ -362,7 +360,7 @@ private class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
applicationAnimation: aroundAnimation,
|
||||
largeApplicationAnimation: reaction.effectAnimation,
|
||||
isCustom: false
|
||||
), animationCache: self.animationCache, animationRenderer: self.animationRenderer, hasAppearAnimation: false, useDirectRendering: false)
|
||||
), animationCache: self.animationCache, animationRenderer: self.animationRenderer, loopIdle: false, hasAppearAnimation: false, useDirectRendering: false)
|
||||
containerNode.isUserInteractionEnabled = false
|
||||
containerNode.addSubnode(itemNode)
|
||||
self.addSubnode(containerNode)
|
||||
|
@ -193,6 +193,9 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
|
||||
private var animateInInfo: (centerX: CGFloat, width: CGFloat)?
|
||||
|
||||
private var availableReactions: AvailableReactions?
|
||||
private var availableReactionsDisposable: Disposable?
|
||||
|
||||
public init(context: AccountContext, animationCache: AnimationCache, presentationData: PresentationData, items: [ReactionContextItem], getEmojiContent: ((AnimationCache, MultiAnimationRenderer) -> Signal<EmojiPagerContentComponent, NoError>)?, isExpandedUpdated: @escaping (ContainedViewLayoutTransition) -> Void, requestLayout: @escaping (ContainedViewLayoutTransition) -> Void) {
|
||||
self.context = context
|
||||
self.presentationData = presentationData
|
||||
@ -294,10 +297,20 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
self.view.addGestureRecognizer(horizontalExpandRecognizer)
|
||||
self.horizontalExpandRecognizer = horizontalExpandRecognizer
|
||||
}
|
||||
|
||||
self.availableReactionsDisposable = (context.engine.stickers.availableReactions()
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] availableReactions in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.availableReactions = availableReactions
|
||||
})
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.emojiContentDisposable?.dispose()
|
||||
self.availableReactionsDisposable?.dispose()
|
||||
}
|
||||
|
||||
override public func didLoad() {
|
||||
@ -371,7 +384,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
contentSize.width = max(46.0, contentSize.width)
|
||||
contentSize.height = self.currentContentHeight
|
||||
|
||||
let sideInset: CGFloat = 11.0
|
||||
let sideInset: CGFloat = 11.0 + insets.left
|
||||
let backgroundOffset: CGPoint = CGPoint(x: 22.0, y: -7.0)
|
||||
|
||||
var rect: CGRect
|
||||
@ -459,14 +472,23 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
|
||||
var topVisibleItems: Int
|
||||
if self.getEmojiContent != nil {
|
||||
topVisibleItems = min(self.items.count, visibleItemCount - 1)
|
||||
if compressionFactor >= 0.6 {
|
||||
topVisibleItems = min(self.items.count, visibleItemCount)
|
||||
}
|
||||
topVisibleItems = min(self.items.count, visibleItemCount)
|
||||
} else {
|
||||
topVisibleItems = self.items.count
|
||||
}
|
||||
|
||||
var loopIdle = false
|
||||
for i in 0 ..< min(self.items.count, visibleItemCount) {
|
||||
if let reaction = self.items[i].reaction {
|
||||
switch reaction.rawValue {
|
||||
case .builtin:
|
||||
break
|
||||
case .custom:
|
||||
loopIdle = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var validIndices = Set<Int>()
|
||||
var nextX: CGFloat = sideInset
|
||||
for i in 0 ..< self.items.count {
|
||||
@ -519,7 +541,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
if appearBounds.intersects(baseItemFrame) || (self.visibleItemNodes[i] != nil && visibleBounds.intersects(baseItemFrame)) {
|
||||
validIndices.insert(i)
|
||||
|
||||
let itemFrame = baseItemFrame
|
||||
var itemFrame = baseItemFrame
|
||||
|
||||
var isPreviewing = false
|
||||
if let highlightedReaction = self.highlightedReaction, highlightedReaction == self.items[i].reaction {
|
||||
@ -540,7 +562,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
itemTransition = .immediate
|
||||
|
||||
if case let .reaction(item) = self.items[i] {
|
||||
itemNode = ReactionNode(context: self.context, theme: self.presentationData.theme, item: item, animationCache: self.animationCache, animationRenderer: self.animationRenderer)
|
||||
itemNode = ReactionNode(context: self.context, theme: self.presentationData.theme, item: item, animationCache: self.animationCache, animationRenderer: self.animationRenderer, loopIdle: loopIdle)
|
||||
maskNode = nil
|
||||
} else {
|
||||
itemNode = PremiumReactionsNode(theme: self.presentationData.theme)
|
||||
@ -570,6 +592,13 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
if self.getEmojiContent != nil && i == visibleItemCount - 1 {
|
||||
itemFrame.origin.x -= (1.0 - compressionFactor) * itemFrame.width * 0.5
|
||||
itemNode.isUserInteractionEnabled = false
|
||||
} else {
|
||||
itemNode.isUserInteractionEnabled = true
|
||||
}
|
||||
|
||||
itemTransition.updateFrame(node: itemNode, frame: itemFrame, beginWithCurrentState: true, completion: { [weak self, weak itemNode] completed in
|
||||
guard let strongSelf = self, let itemNode = itemNode else {
|
||||
return
|
||||
@ -588,6 +617,10 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
if animateIn {
|
||||
itemNode.appear(animated: !self.context.sharedContext.currentPresentationData.with({ $0 }).reduceMotion)
|
||||
}
|
||||
|
||||
if self.getEmojiContent != nil && i == visibleItemCount - 1 {
|
||||
transition.updateSublayerTransformScale(node: itemNode, scale: 0.001 * (1.0 - compressionFactor) + 1.0 * compressionFactor)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -907,25 +940,44 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
|
||||
emojiContent.inputInteractionHolder.inputInteraction = EmojiPagerContentComponent.InputInteraction(
|
||||
performItemAction: { [weak self] groupId, item, sourceView, sourceRect, sourceLayer, isLongPress in
|
||||
guard let strongSelf = self, let itemFile = item.itemFile else {
|
||||
guard let strongSelf = self, let availableReactions = strongSelf.availableReactions, let itemFile = item.itemFile else {
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.didTriggerExpandedReaction = isLongPress
|
||||
|
||||
var found = false
|
||||
if let groupId = groupId.base as? String, groupId == "recent" {
|
||||
for reactionItem in strongSelf.items {
|
||||
if case let .reaction(reactionItem) = reactionItem {
|
||||
if reactionItem.stillAnimation.fileId == itemFile.fileId {
|
||||
found = true
|
||||
|
||||
strongSelf.customReactionSource = (sourceView, sourceRect, sourceLayer, reactionItem)
|
||||
strongSelf.reactionSelected?(reactionItem.updateMessageReaction, isLongPress)
|
||||
|
||||
break
|
||||
}
|
||||
for reaction in availableReactions.reactions {
|
||||
guard let centerAnimation = reaction.centerAnimation, let aroundAnimation = reaction.aroundAnimation else {
|
||||
continue
|
||||
}
|
||||
|
||||
if reaction.selectAnimation.fileId == itemFile.fileId {
|
||||
found = true
|
||||
|
||||
let updateReaction: UpdateMessageReaction
|
||||
switch reaction.value {
|
||||
case let .builtin(value):
|
||||
updateReaction = .builtin(value)
|
||||
case let .custom(fileId):
|
||||
updateReaction = .custom(fileId: fileId, file: nil)
|
||||
}
|
||||
|
||||
let reactionItem = ReactionItem(
|
||||
reaction: ReactionItem.Reaction(rawValue: reaction.value),
|
||||
appearAnimation: reaction.appearAnimation,
|
||||
stillAnimation: reaction.selectAnimation,
|
||||
listAnimation: centerAnimation,
|
||||
largeListAnimation: reaction.activateAnimation,
|
||||
applicationAnimation: aroundAnimation,
|
||||
largeApplicationAnimation: reaction.effectAnimation,
|
||||
isCustom: false
|
||||
)
|
||||
|
||||
strongSelf.customReactionSource = (sourceView, sourceRect, sourceLayer, reactionItem)
|
||||
strongSelf.reactionSelected?(updateReaction, isLongPress)
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
@ -978,7 +1030,26 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
})
|
||||
},
|
||||
clearGroup: { _ in
|
||||
clearGroup: { [weak self] groupId in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if groupId == AnyHashable("popular") {
|
||||
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
||||
let actionSheet = ActionSheetController(theme: ActionSheetControllerTheme(presentationTheme: presentationData.theme, fontSize: presentationData.listsFontSize))
|
||||
var items: [ActionSheetItem] = []
|
||||
let context = strongSelf.context
|
||||
items.append(ActionSheetButtonItem(title: presentationData.strings.Emoji_ClearRecent, color: .destructive, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
let _ = context.engine.stickers.clearRecentlyUsedReactions().start()
|
||||
}))
|
||||
actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
|
||||
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
})
|
||||
])])
|
||||
context.sharedContext.mainWindow?.presentInGlobalOverlay(actionSheet)
|
||||
}
|
||||
},
|
||||
pushController: { _ in
|
||||
},
|
||||
@ -1159,7 +1230,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
|
||||
if let customReactionSource = self.customReactionSource {
|
||||
let itemNode = ReactionNode(context: self.context, theme: self.presentationData.theme, item: customReactionSource.item, animationCache: self.animationCache, animationRenderer: self.animationRenderer, useDirectRendering: false)
|
||||
let itemNode = ReactionNode(context: self.context, theme: self.presentationData.theme, item: customReactionSource.item, animationCache: self.animationCache, animationRenderer: self.animationRenderer, loopIdle: false, useDirectRendering: false)
|
||||
if let contents = customReactionSource.layer.contents {
|
||||
itemNode.setCustomContents(contents: contents)
|
||||
}
|
||||
@ -1176,7 +1247,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
|
||||
let switchToInlineImmediately: Bool
|
||||
if itemNode.item.listAnimation.isVideoEmoji || itemNode.item.listAnimation.isVideoSticker || itemNode.item.listAnimation.isAnimatedSticker {
|
||||
if itemNode.item.listAnimation.isVideoEmoji || itemNode.item.listAnimation.isVideoSticker || itemNode.item.listAnimation.isAnimatedSticker || itemNode.item.listAnimation.isStaticEmoji {
|
||||
switch itemNode.item.reaction.rawValue {
|
||||
case .builtin:
|
||||
switchToInlineImmediately = false
|
||||
@ -1196,11 +1267,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
targetView.isHidden = true
|
||||
} else {
|
||||
targetView.alpha = 0.0
|
||||
targetView.layer.animateAlpha(from: targetView.alpha, to: 0.0, duration: 0.2, completion: { [weak targetView] completed in
|
||||
if completed {
|
||||
targetView?.isHidden = true
|
||||
}
|
||||
})
|
||||
targetView.layer.animateAlpha(from: targetView.alpha, to: 0.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1216,7 +1283,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
|
||||
var expandedSize: CGSize = selfTargetRect.size
|
||||
if self.didTriggerExpandedReaction {
|
||||
if itemNode.item.listAnimation.isVideoEmoji || itemNode.item.listAnimation.isVideoSticker {
|
||||
if itemNode.item.listAnimation.isVideoEmoji || itemNode.item.listAnimation.isVideoSticker || itemNode.item.listAnimation.isStaticEmoji {
|
||||
expandedSize = CGSize(width: 80.0, height: 80.0)
|
||||
} else {
|
||||
expandedSize = CGSize(width: 120.0, height: 120.0)
|
||||
@ -1267,7 +1334,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
} else if itemNode.item.isCustom {
|
||||
additionalAnimationNode = nil
|
||||
|
||||
if let url = getAppBundle().url(forResource: "generic_reaction_effect", withExtension: "json"), let composition = Animation.filepath(url.path) {
|
||||
if let url = getAppBundle().url(forResource: self.didTriggerExpandedReaction ? "generic_reaction_effect" : "generic_reaction_small_effect", withExtension: "json"), let composition = Animation.filepath(url.path) {
|
||||
let view = AnimationView(animation: composition, configuration: LottieConfiguration(renderingEngine: .mainThread, decodingStrategy: .codable))
|
||||
view.animationSpeed = 1.0
|
||||
view.backgroundColor = nil
|
||||
@ -1279,36 +1346,40 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
|
||||
genericAnimationView = view
|
||||
|
||||
let animationCache = AnimationCacheImpl(basePath: itemNode.context.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: {
|
||||
return TempBox.shared.tempFile(fileName: "file").path
|
||||
})
|
||||
let animationRenderer = MultiAnimationRendererImpl()
|
||||
let animationCache = itemNode.context.animationCache
|
||||
let animationRenderer = itemNode.context.animationRenderer
|
||||
|
||||
let allLayers = view.allLayers(forKeypath: AnimationKeypath(keypath: "BODY 1 Precomp"))
|
||||
for animationLayer in allLayers {
|
||||
let baseItemLayer = InlineStickerItemLayer(
|
||||
context: itemNode.context,
|
||||
attemptSynchronousLoad: false,
|
||||
emoji: ChatTextInputTextCustomEmojiAttribute(stickerPack: nil, fileId: itemNode.item.listAnimation.fileId.id, file: itemNode.item.listAnimation),
|
||||
file: itemNode.item.listAnimation,
|
||||
cache: animationCache,
|
||||
renderer: animationRenderer,
|
||||
placeholderColor: UIColor(white: 0.0, alpha: 0.0),
|
||||
pointSize: CGSize(width: self.didTriggerExpandedReaction ? 64.0 : 32.0, height: self.didTriggerExpandedReaction ? 64.0 : 32.0)
|
||||
)
|
||||
|
||||
if let sublayers = animationLayer.sublayers {
|
||||
for sublayer in sublayers {
|
||||
sublayer.isHidden = true
|
||||
for i in 1 ... 32 {
|
||||
let allLayers = view.allLayers(forKeypath: AnimationKeypath(keypath: "placeholder_\(i)"))
|
||||
for animationLayer in allLayers {
|
||||
let baseItemLayer = InlineStickerItemLayer(
|
||||
context: itemNode.context,
|
||||
attemptSynchronousLoad: false,
|
||||
emoji: ChatTextInputTextCustomEmojiAttribute(stickerPack: nil, fileId: itemNode.item.listAnimation.fileId.id, file: itemNode.item.listAnimation),
|
||||
file: itemNode.item.listAnimation,
|
||||
cache: animationCache,
|
||||
renderer: animationRenderer,
|
||||
placeholderColor: UIColor(white: 0.0, alpha: 0.0),
|
||||
pointSize: CGSize(width: self.didTriggerExpandedReaction ? 64.0 : 32.0, height: self.didTriggerExpandedReaction ? 64.0 : 32.0)
|
||||
)
|
||||
|
||||
if let sublayers = animationLayer.sublayers {
|
||||
for sublayer in sublayers {
|
||||
sublayer.isHidden = true
|
||||
}
|
||||
}
|
||||
|
||||
baseItemLayer.isVisibleForAnimations = true
|
||||
baseItemLayer.frame = CGRect(origin: CGPoint(x: -0.0, y: -0.0), size: CGSize(width: 500.0, height: 500.0))
|
||||
animationLayer.addSublayer(baseItemLayer)
|
||||
}
|
||||
|
||||
baseItemLayer.isVisibleForAnimations = true
|
||||
baseItemLayer.frame = CGRect(origin: CGPoint(x: -0.0, y: -0.0), size: CGSize(width: 500.0, height: 500.0))
|
||||
animationLayer.addSublayer(baseItemLayer)
|
||||
}
|
||||
|
||||
view.frame = effectFrame.insetBy(dx: -10.0, dy: -10.0).offsetBy(dx: incomingMessage ? 22.0 : -22.0, dy: 0.0)
|
||||
if self.didTriggerExpandedReaction {
|
||||
view.frame = effectFrame.insetBy(dx: -10.0, dy: -10.0).offsetBy(dx: incomingMessage ? 22.0 : -22.0, dy: 0.0)
|
||||
} else {
|
||||
view.frame = effectFrame.insetBy(dx: -20.0, dy: -20.0)
|
||||
}
|
||||
self.view.addSubview(view)
|
||||
}
|
||||
} else {
|
||||
@ -1348,14 +1419,19 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
guard let itemNode = itemNode else {
|
||||
return
|
||||
}
|
||||
guard let targetView = targetView as? ReactionIconView else {
|
||||
return
|
||||
}
|
||||
if let animateTargetContainer = animateTargetContainer {
|
||||
animateTargetContainer.isHidden = false
|
||||
}
|
||||
targetView.isHidden = false
|
||||
targetView.alpha = 1.0
|
||||
|
||||
if let targetView = targetView {
|
||||
targetView.isHidden = false
|
||||
targetView.alpha = 1.0
|
||||
targetView.layer.removeAnimation(forKey: "opacity")
|
||||
}
|
||||
|
||||
guard let targetView = targetView as? ReactionIconView else {
|
||||
return
|
||||
}
|
||||
|
||||
if switchToInlineImmediately {
|
||||
targetView.updateIsAnimationHidden(isAnimationHidden: false, transition: .immediate)
|
||||
@ -1619,6 +1695,9 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
if itemNode.supernode === self.scrollNode && !self.scrollNode.bounds.intersects(itemNode.frame) {
|
||||
continue
|
||||
}
|
||||
if !itemNode.isUserInteractionEnabled {
|
||||
continue
|
||||
}
|
||||
let itemPoint = self.view.convert(point, to: itemNode.view)
|
||||
if itemNode.bounds.insetBy(dx: -touchInset, dy: -touchInset).contains(itemPoint) {
|
||||
return itemNode
|
||||
@ -1704,7 +1783,7 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
|
||||
itemNode = currentItemNode
|
||||
} else {
|
||||
let animationRenderer = MultiAnimationRendererImpl()
|
||||
itemNode = ReactionNode(context: context, theme: theme, item: reaction, animationCache: animationCache, animationRenderer: animationRenderer)
|
||||
itemNode = ReactionNode(context: context, theme: theme, item: reaction, animationCache: animationCache, animationRenderer: animationRenderer, loopIdle: false)
|
||||
}
|
||||
self.itemNode = itemNode
|
||||
|
||||
@ -1812,7 +1891,7 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
|
||||
} else if itemNode.item.isCustom {
|
||||
additionalAnimationNode = nil
|
||||
|
||||
if let url = getAppBundle().url(forResource: "generic_reaction_effect", withExtension: "json"), let composition = Animation.filepath(url.path) {
|
||||
if let url = getAppBundle().url(forResource: "generic_reaction_small_effect", withExtension: "json"), let composition = Animation.filepath(url.path) {
|
||||
let view = AnimationView(animation: composition, configuration: LottieConfiguration(renderingEngine: .mainThread, decodingStrategy: .codable))
|
||||
view.animationSpeed = 1.0
|
||||
view.backgroundColor = nil
|
||||
@ -1824,36 +1903,36 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
|
||||
|
||||
genericAnimationView = view
|
||||
|
||||
let animationCache = AnimationCacheImpl(basePath: itemNode.context.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: {
|
||||
return TempBox.shared.tempFile(fileName: "file").path
|
||||
})
|
||||
let animationRenderer = MultiAnimationRendererImpl()
|
||||
let animationCache = itemNode.context.animationCache
|
||||
let animationRenderer = itemNode.context.animationRenderer
|
||||
|
||||
let allLayers = view.allLayers(forKeypath: AnimationKeypath(keypath: "BODY 1 Precomp"))
|
||||
for animationLayer in allLayers {
|
||||
let baseItemLayer = InlineStickerItemLayer(
|
||||
context: itemNode.context,
|
||||
attemptSynchronousLoad: false,
|
||||
emoji: ChatTextInputTextCustomEmojiAttribute(stickerPack: nil, fileId: itemNode.item.listAnimation.fileId.id, file: itemNode.item.listAnimation),
|
||||
file: itemNode.item.listAnimation,
|
||||
cache: animationCache,
|
||||
renderer: animationRenderer,
|
||||
placeholderColor: UIColor(white: 0.0, alpha: 0.0),
|
||||
pointSize: CGSize(width: 32.0, height: 32.0)
|
||||
)
|
||||
for i in 1 ... 7 {
|
||||
let allLayers = view.allLayers(forKeypath: AnimationKeypath(keypath: "placeholder_\(i)"))
|
||||
for animationLayer in allLayers {
|
||||
let baseItemLayer = InlineStickerItemLayer(
|
||||
context: itemNode.context,
|
||||
attemptSynchronousLoad: false,
|
||||
emoji: ChatTextInputTextCustomEmojiAttribute(stickerPack: nil, fileId: itemNode.item.listAnimation.fileId.id, file: itemNode.item.listAnimation),
|
||||
file: itemNode.item.listAnimation,
|
||||
cache: animationCache,
|
||||
renderer: animationRenderer,
|
||||
placeholderColor: UIColor(white: 0.0, alpha: 0.0),
|
||||
pointSize: CGSize(width: 32.0, height: 32.0)
|
||||
)
|
||||
|
||||
if let sublayers = animationLayer.sublayers {
|
||||
for sublayer in sublayers {
|
||||
sublayer.isHidden = true
|
||||
if let sublayers = animationLayer.sublayers {
|
||||
for sublayer in sublayers {
|
||||
sublayer.isHidden = true
|
||||
}
|
||||
}
|
||||
|
||||
baseItemLayer.isVisibleForAnimations = true
|
||||
baseItemLayer.frame = CGRect(origin: CGPoint(x: -0.0, y: -0.0), size: CGSize(width: 500.0, height: 500.0))
|
||||
animationLayer.addSublayer(baseItemLayer)
|
||||
}
|
||||
|
||||
baseItemLayer.isVisibleForAnimations = true
|
||||
baseItemLayer.frame = CGRect(origin: CGPoint(x: -0.0, y: -0.0), size: CGSize(width: 500.0, height: 500.0))
|
||||
animationLayer.addSublayer(baseItemLayer)
|
||||
}
|
||||
|
||||
view.frame = effectFrame.insetBy(dx: -10.0, dy: -10.0).offsetBy(dx: incomingMessage ? 22.0 : -22.0, dy: 0.0)
|
||||
view.frame = effectFrame.insetBy(dx: -20.0, dy: -20.0)//.offsetBy(dx: incomingMessage ? 22.0 : -22.0, dy: 0.0)
|
||||
self.view.addSubview(view)
|
||||
}
|
||||
} else {
|
||||
|
@ -49,6 +49,7 @@ protocol ReactionItemNode: ASDisplayNode {
|
||||
public final class ReactionNode: ASDisplayNode, ReactionItemNode {
|
||||
let context: AccountContext
|
||||
let item: ReactionItem
|
||||
private let loopIdle: Bool
|
||||
private let hasAppearAnimation: Bool
|
||||
private let useDirectRendering: Bool
|
||||
|
||||
@ -79,9 +80,10 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode {
|
||||
return self.staticAnimationNode.currentFrameImage
|
||||
}
|
||||
|
||||
public init(context: AccountContext, theme: PresentationTheme, item: ReactionItem, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, hasAppearAnimation: Bool = true, useDirectRendering: Bool = false) {
|
||||
public init(context: AccountContext, theme: PresentationTheme, item: ReactionItem, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, loopIdle: Bool, hasAppearAnimation: Bool = true, useDirectRendering: Bool = false) {
|
||||
self.context = context
|
||||
self.item = item
|
||||
self.loopIdle = loopIdle
|
||||
self.hasAppearAnimation = hasAppearAnimation
|
||||
self.useDirectRendering = useDirectRendering
|
||||
|
||||
@ -105,7 +107,9 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode {
|
||||
}
|
||||
if strongSelf.animationNode == nil {
|
||||
strongSelf.staticAnimationNode.isHidden = false
|
||||
strongSelf.staticAnimationNode.playLoop()
|
||||
if strongSelf.loopIdle {
|
||||
strongSelf.staticAnimationNode.playLoop()
|
||||
}
|
||||
}
|
||||
|
||||
strongSelf.animateInAnimationNode?.removeFromSupernode()
|
||||
@ -133,6 +137,13 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode {
|
||||
if animated {
|
||||
if self.item.isCustom {
|
||||
self.layer.animateSpring(from: 0.01 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4)
|
||||
|
||||
if self.animationNode == nil {
|
||||
self.staticAnimationNode.isHidden = false
|
||||
if self.loopIdle {
|
||||
self.staticAnimationNode.playLoop()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.animateInAnimationNode?.visibility = true
|
||||
}
|
||||
@ -184,11 +195,11 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode {
|
||||
}
|
||||
|
||||
if largeExpanded {
|
||||
let source = AnimatedStickerResourceSource(account: self.context.account, resource: self.item.largeListAnimation.resource, isVideo: self.item.largeListAnimation.isVideoSticker || self.item.largeListAnimation.isVideoEmoji)
|
||||
let source = AnimatedStickerResourceSource(account: self.context.account, resource: self.item.largeListAnimation.resource, isVideo: self.item.largeListAnimation.isVideoSticker || self.item.largeListAnimation.isVideoEmoji || self.item.largeListAnimation.isStaticSticker || self.item.largeListAnimation.isStaticEmoji)
|
||||
|
||||
animationNode.setup(source: source, width: Int(expandedAnimationFrame.width * 2.0), height: Int(expandedAnimationFrame.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.item.largeListAnimation.resource.id)))
|
||||
} else {
|
||||
let source = AnimatedStickerResourceSource(account: self.context.account, resource: self.item.listAnimation.resource, isVideo: self.item.listAnimation.isVideoSticker || self.item.listAnimation.isVideoEmoji)
|
||||
let source = AnimatedStickerResourceSource(account: self.context.account, resource: self.item.listAnimation.resource, isVideo: self.item.listAnimation.isVideoSticker || self.item.listAnimation.isVideoEmoji || self.item.listAnimation.isVideoSticker || self.item.listAnimation.isStaticSticker || self.item.listAnimation.isStaticEmoji)
|
||||
animationNode.setup(source: source, width: Int(expandedAnimationFrame.width * 2.0), height: Int(expandedAnimationFrame.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.item.listAnimation.resource.id)))
|
||||
}
|
||||
animationNode.frame = expandedAnimationFrame
|
||||
@ -240,7 +251,7 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode {
|
||||
transition.animateTransformScale(node: customContentsNode, from: customContentsNode.bounds.width / animationFrame.width)
|
||||
transition.animatePositionAdditive(node: customContentsNode, offset: CGPoint(x: customContentsNode.frame.midX - animationFrame.midX, y: customContentsNode.frame.midY - animationFrame.midY))
|
||||
|
||||
if self.item.listAnimation.isVideoEmoji || self.item.listAnimation.isVideoSticker || self.item.listAnimation.isAnimatedSticker {
|
||||
if self.item.listAnimation.isVideoEmoji || self.item.listAnimation.isVideoSticker || self.item.listAnimation.isAnimatedSticker || self.item.listAnimation.isStaticSticker || self.item.listAnimation.isStaticEmoji {
|
||||
customContentsNode.alpha = 0.0
|
||||
customContentsNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2)
|
||||
}
|
||||
@ -273,7 +284,7 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode {
|
||||
self.stillAnimationNode = stillAnimationNode
|
||||
self.addSubnode(stillAnimationNode)
|
||||
|
||||
stillAnimationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.item.stillAnimation.resource, isVideo: self.item.stillAnimation.isVideoEmoji || self.item.stillAnimation.isVideoSticker), width: Int(animationDisplaySize.width * 2.0), height: Int(animationDisplaySize.height * 2.0), playbackMode: .loop, mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.item.stillAnimation.resource.id)))
|
||||
stillAnimationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.item.stillAnimation.resource, isVideo: self.item.stillAnimation.isVideoEmoji || self.item.stillAnimation.isVideoSticker || self.item.stillAnimation.isStaticSticker || self.item.stillAnimation.isStaticEmoji), width: Int(animationDisplaySize.width * 2.0), height: Int(animationDisplaySize.height * 2.0), playbackMode: self.loopIdle ? .loop : .still(.start), mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.item.stillAnimation.resource.id)))
|
||||
stillAnimationNode.position = animationFrame.center
|
||||
stillAnimationNode.bounds = CGRect(origin: CGPoint(), size: animationFrame.size)
|
||||
stillAnimationNode.updateLayout(size: animationFrame.size)
|
||||
@ -288,7 +299,9 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode {
|
||||
animateInAnimationNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1)
|
||||
|
||||
strongSelf.staticAnimationNode.isHidden = false
|
||||
strongSelf.staticAnimationNode.playLoop()
|
||||
if strongSelf.loopIdle {
|
||||
strongSelf.staticAnimationNode.playLoop()
|
||||
}
|
||||
}
|
||||
}
|
||||
stillAnimationNode.visibility = true
|
||||
@ -329,9 +342,9 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode {
|
||||
|
||||
self.staticAnimationNode.automaticallyLoadFirstFrame = true
|
||||
if !self.hasAppearAnimation {
|
||||
self.staticAnimationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.item.largeListAnimation.resource, isVideo: self.item.largeListAnimation.isVideoEmoji || self.item.largeListAnimation.isVideoSticker), width: Int(expandedAnimationFrame.width * 2.0), height: Int(expandedAnimationFrame.height * 2.0), playbackMode: .still(.start), mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.item.largeListAnimation.resource.id)))
|
||||
self.staticAnimationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.item.largeListAnimation.resource, isVideo: self.item.largeListAnimation.isVideoEmoji || self.item.largeListAnimation.isVideoSticker || self.item.largeListAnimation.isStaticSticker || self.item.largeListAnimation.isStaticEmoji), width: Int(expandedAnimationFrame.width * 2.0), height: Int(expandedAnimationFrame.height * 2.0), playbackMode: .still(.start), mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.item.largeListAnimation.resource.id)))
|
||||
} else {
|
||||
self.staticAnimationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.item.stillAnimation.resource, isVideo: self.item.stillAnimation.isVideoEmoji || self.item.stillAnimation.isVideoSticker), width: Int(animationDisplaySize.width * 2.0), height: Int(animationDisplaySize.height * 2.0), playbackMode: .still(.start), mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.item.stillAnimation.resource.id)))
|
||||
self.staticAnimationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.item.stillAnimation.resource, isVideo: self.item.stillAnimation.isVideoEmoji || self.item.stillAnimation.isVideoSticker || self.item.stillAnimation.isStaticSticker || self.item.stillAnimation.isStaticEmoji), width: Int(animationDisplaySize.width * 2.0), height: Int(animationDisplaySize.height * 2.0), playbackMode: .still(.start), mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.item.stillAnimation.resource.id)))
|
||||
}
|
||||
self.staticAnimationNode.position = animationFrame.center
|
||||
self.staticAnimationNode.bounds = CGRect(origin: CGPoint(), size: animationFrame.size)
|
||||
@ -339,7 +352,7 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode {
|
||||
self.staticAnimationNode.visibility = true
|
||||
|
||||
if let animateInAnimationNode = self.animateInAnimationNode {
|
||||
animateInAnimationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.item.appearAnimation.resource, isVideo: self.item.appearAnimation.isVideoEmoji || self.item.appearAnimation.isVideoSticker), width: Int(animationDisplaySize.width * 2.0), height: Int(animationDisplaySize.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.item.appearAnimation.resource.id)))
|
||||
animateInAnimationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.item.appearAnimation.resource, isVideo: self.item.appearAnimation.isVideoEmoji || self.item.appearAnimation.isVideoSticker || self.item.appearAnimation.isStaticSticker || self.item.appearAnimation.isStaticEmoji), width: Int(animationDisplaySize.width * 2.0), height: Int(animationDisplaySize.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.item.appearAnimation.resource.id)))
|
||||
animateInAnimationNode.position = animationFrame.center
|
||||
animateInAnimationNode.bounds = CGRect(origin: CGPoint(), size: animationFrame.size)
|
||||
animateInAnimationNode.updateLayout(size: animationFrame.size)
|
||||
|
@ -169,15 +169,7 @@ class ReactionChatPreviewItemNode: ListViewItemNode {
|
||||
let standaloneReactionAnimation = StandaloneReactionAnimation()
|
||||
self.standaloneReactionAnimation = standaloneReactionAnimation
|
||||
|
||||
let animationCache: AnimationCache
|
||||
if let current = self.animationCache {
|
||||
animationCache = current
|
||||
} else {
|
||||
animationCache = AnimationCacheImpl(basePath: item.context.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: {
|
||||
return TempBox.shared.tempFile(fileName: "file").path
|
||||
})
|
||||
self.animationCache = animationCache
|
||||
}
|
||||
let animationCache = item.context.animationCache
|
||||
|
||||
supernode.addSubnode(standaloneReactionAnimation)
|
||||
standaloneReactionAnimation.frame = supernode.bounds
|
||||
|
@ -63,10 +63,8 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView
|
||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
self.presentationThemeSettings = presentationThemeSettings
|
||||
|
||||
self.animationCache = AnimationCacheImpl(basePath: context.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: {
|
||||
return TempBox.shared.tempFile(fileName: "file").path
|
||||
})
|
||||
self.animationRenderer = MultiAnimationRendererImpl()
|
||||
self.animationCache = context.animationCache
|
||||
self.animationRenderer = context.animationRenderer
|
||||
|
||||
let calendar = Calendar(identifier: .gregorian)
|
||||
var components = calendar.dateComponents(Set([.era, .year, .month, .day, .hour, .minute, .second]), from: Date())
|
||||
|
@ -233,10 +233,8 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
|
||||
self.wallpaper = self.presentationData.chatWallpaper
|
||||
let bubbleCorners = self.presentationData.chatBubbleCorners
|
||||
|
||||
self.animationCache = AnimationCacheImpl(basePath: context.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: {
|
||||
return TempBox.shared.tempFile(fileName: "file").path
|
||||
})
|
||||
self.animationRenderer = MultiAnimationRendererImpl()
|
||||
self.animationCache = context.animationCache
|
||||
self.animationRenderer = context.animationRenderer
|
||||
|
||||
self.ready = ready
|
||||
|
||||
|
@ -88,10 +88,8 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
|
||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
self.animationCache = AnimationCacheImpl(basePath: context.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: {
|
||||
return TempBox.shared.tempFile(fileName: "file").path
|
||||
})
|
||||
self.animationRenderer = MultiAnimationRendererImpl()
|
||||
self.animationCache = context.animationCache
|
||||
self.animationRenderer = context.animationRenderer
|
||||
|
||||
let calendar = Calendar(identifier: .gregorian)
|
||||
var components = calendar.dateComponents(Set([.era, .year, .month, .day, .hour, .minute, .second]), from: Date())
|
||||
|
@ -1547,19 +1547,9 @@ public final class StickerPackScreenImpl: ViewController {
|
||||
self.parentNavigationController = parentNavigationController
|
||||
self.sendSticker = sendSticker
|
||||
self.actionPerformed = actionPerformed
|
||||
|
||||
self.animationCache = AnimationCacheImpl(basePath: context.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: {
|
||||
return TempBox.shared.tempFile(fileName: "file").path
|
||||
})
|
||||
|
||||
let animationRenderer: MultiAnimationRenderer
|
||||
/*if #available(iOS 13.0, *) {
|
||||
animationRenderer = MultiAnimationMetalRendererImpl()
|
||||
} else {*/
|
||||
animationRenderer = MultiAnimationRendererImpl()
|
||||
//}
|
||||
|
||||
self.animationRenderer = animationRenderer
|
||||
|
||||
self.animationCache = context.animationCache
|
||||
self.animationRenderer = context.animationRenderer
|
||||
|
||||
super.init(navigationBarPresentationData: nil)
|
||||
|
||||
|
@ -68,8 +68,11 @@ private extension PremiumPromoConfiguration {
|
||||
case let .premiumPromo(statusText, statusEntities, videoSections, videoFiles, periodOptions, _):
|
||||
self.status = statusText
|
||||
self.statusEntities = messageTextEntitiesFromApiEntities(statusEntities)
|
||||
self.currency = currency
|
||||
self.monthlyAmount = monthlyAmount
|
||||
let _ = periodOptions
|
||||
self.currency = "USD"
|
||||
self.monthlyAmount = 500
|
||||
//self.currency = currency
|
||||
//self.monthlyAmount = monthlyAmount
|
||||
var videos: [String: TelegramMediaFile] = [:]
|
||||
for (key, document) in zip(videoSections, videoFiles) {
|
||||
if let file = telegramMediaFileFromApiDocument(document) {
|
||||
|
@ -270,7 +270,7 @@ func managedRecentReactions(postbox: Postbox, network: Network) -> Signal<Void,
|
||||
return fileId.id
|
||||
}
|
||||
}, reverseHashOrder: false, forceFetch: false, fetch: { hash in
|
||||
return network.request(Api.functions.messages.getRecentReactions(limit: 24, hash: hash))
|
||||
return network.request(Api.functions.messages.getRecentReactions(limit: 100, hash: hash))
|
||||
|> retryRequest
|
||||
|> mapToSignal { result -> Signal<[OrderedItemListEntry]?, NoError> in
|
||||
switch result {
|
||||
@ -321,7 +321,7 @@ func managedTopReactions(postbox: Postbox, network: Network) -> Signal<Void, NoE
|
||||
return fileId.id
|
||||
}
|
||||
}, reverseHashOrder: false, forceFetch: false, fetch: { hash in
|
||||
return network.request(Api.functions.messages.getTopReactions(limit: 24, hash: hash))
|
||||
return network.request(Api.functions.messages.getTopReactions(limit: 32, hash: hash))
|
||||
|> retryRequest
|
||||
|> mapToSignal { result -> Signal<[OrderedItemListEntry]?, NoError> in
|
||||
switch result {
|
||||
|
@ -18,7 +18,7 @@ public enum UpdateMessageReaction {
|
||||
}
|
||||
}
|
||||
|
||||
public func updateMessageReactionsInteractively(account: Account, messageId: MessageId, reactions: [UpdateMessageReaction], isLarge: Bool) -> Signal<Never, NoError> {
|
||||
public func updateMessageReactionsInteractively(account: Account, messageId: MessageId, reactions: [UpdateMessageReaction], isLarge: Bool, storeAsRecentlyUsed: Bool) -> Signal<Never, NoError> {
|
||||
return account.postbox.transaction { transaction -> Void in
|
||||
let isPremium = (transaction.getPeer(account.peerId) as? TelegramUser)?.isPremium ?? false
|
||||
let appConfiguration = transaction.getPreferencesEntry(key: PreferencesKeys.appConfiguration)?.get(AppConfiguration.self) ?? .defaultValue
|
||||
@ -57,25 +57,27 @@ public func updateMessageReactionsInteractively(account: Account, messageId: Mes
|
||||
}
|
||||
}
|
||||
|
||||
for attribute in currentMessage.attributes {
|
||||
if let attribute = attribute as? ReactionsMessageAttribute {
|
||||
for updatedReaction in reactions {
|
||||
if !attribute.reactions.contains(where: { $0.value == updatedReaction.reaction }) {
|
||||
let recentReactionItem: RecentReactionItem
|
||||
switch updatedReaction {
|
||||
case let .builtin(value):
|
||||
recentReactionItem = RecentReactionItem(.builtin(value))
|
||||
case let .custom(fileId, file):
|
||||
if let file = file ?? (transaction.getMedia(MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)) as? TelegramMediaFile) {
|
||||
recentReactionItem = RecentReactionItem(.custom(file))
|
||||
} else {
|
||||
continue
|
||||
if storeAsRecentlyUsed {
|
||||
for attribute in currentMessage.attributes {
|
||||
if let attribute = attribute as? ReactionsMessageAttribute {
|
||||
for updatedReaction in reactions {
|
||||
if !attribute.reactions.contains(where: { $0.value == updatedReaction.reaction }) {
|
||||
let recentReactionItem: RecentReactionItem
|
||||
switch updatedReaction {
|
||||
case let .builtin(value):
|
||||
recentReactionItem = RecentReactionItem(.builtin(value))
|
||||
case let .custom(fileId, file):
|
||||
if let file = file ?? (transaction.getMedia(MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)) as? TelegramMediaFile) {
|
||||
recentReactionItem = RecentReactionItem(.custom(file))
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if let entry = CodableEntry(recentReactionItem) {
|
||||
let itemEntry = OrderedItemListEntry(id: recentReactionItem.id.rawValue, contents: entry)
|
||||
transaction.addOrMoveToFirstPositionOrderedItemListItem(collectionId: Namespaces.OrderedItemList.CloudRecentReactions, item: itemEntry, removeTailIfCountExceeds: 50)
|
||||
}
|
||||
}
|
||||
|
||||
if let entry = CodableEntry(recentReactionItem) {
|
||||
let itemEntry = OrderedItemListEntry(id: recentReactionItem.id.rawValue, contents: entry)
|
||||
transaction.addOrMoveToFirstPositionOrderedItemListItem(collectionId: Namespaces.OrderedItemList.CloudRecentReactions, item: itemEntry, removeTailIfCountExceeds: 50)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -84,7 +86,7 @@ public func updateMessageReactionsInteractively(account: Account, messageId: Mes
|
||||
|
||||
var mappedReactions = mappedReactions
|
||||
|
||||
let updatedReactions = mergedMessageReactions(attributes: attributes + [PendingReactionsMessageAttribute(accountPeerId: account.peerId, reactions: mappedReactions, isLarge: isLarge)])?.reactions ?? []
|
||||
let updatedReactions = mergedMessageReactions(attributes: attributes + [PendingReactionsMessageAttribute(accountPeerId: account.peerId, reactions: mappedReactions, isLarge: isLarge, storeAsRecentlyUsed: storeAsRecentlyUsed)])?.reactions ?? []
|
||||
let updatedOutgoingReactions = updatedReactions.filter(\.isSelected)
|
||||
if updatedOutgoingReactions.count > maxCount {
|
||||
let sortedOutgoingReactions = updatedOutgoingReactions.sorted(by: { $0.chosenOrder! < $1.chosenOrder! })
|
||||
@ -93,7 +95,7 @@ public func updateMessageReactionsInteractively(account: Account, messageId: Mes
|
||||
})
|
||||
}
|
||||
|
||||
attributes.append(PendingReactionsMessageAttribute(accountPeerId: account.peerId, reactions: mappedReactions, isLarge: isLarge))
|
||||
attributes.append(PendingReactionsMessageAttribute(accountPeerId: account.peerId, reactions: mappedReactions, isLarge: isLarge, storeAsRecentlyUsed: storeAsRecentlyUsed))
|
||||
|
||||
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
|
||||
})
|
||||
@ -106,7 +108,7 @@ private enum RequestUpdateMessageReactionError {
|
||||
}
|
||||
|
||||
private func requestUpdateMessageReaction(postbox: Postbox, network: Network, stateManager: AccountStateManager, messageId: MessageId) -> Signal<Never, RequestUpdateMessageReactionError> {
|
||||
return postbox.transaction { transaction -> (Peer, [MessageReaction.Reaction]?, Bool)? in
|
||||
return postbox.transaction { transaction -> (Peer, [MessageReaction.Reaction]?, Bool, Bool)? in
|
||||
guard let peer = transaction.getPeer(messageId.peerId) else {
|
||||
return nil
|
||||
}
|
||||
@ -115,20 +117,22 @@ private func requestUpdateMessageReaction(postbox: Postbox, network: Network, st
|
||||
}
|
||||
var reactions: [MessageReaction.Reaction]?
|
||||
var isLarge: Bool = false
|
||||
var storeAsRecentlyUsed: Bool = false
|
||||
for attribute in message.attributes {
|
||||
if let attribute = attribute as? PendingReactionsMessageAttribute {
|
||||
if !attribute.reactions.isEmpty {
|
||||
reactions = attribute.reactions.map(\.value)
|
||||
}
|
||||
isLarge = attribute.isLarge
|
||||
storeAsRecentlyUsed = attribute.storeAsRecentlyUsed
|
||||
break
|
||||
}
|
||||
}
|
||||
return (peer, reactions, isLarge)
|
||||
return (peer, reactions, isLarge, storeAsRecentlyUsed)
|
||||
}
|
||||
|> castError(RequestUpdateMessageReactionError.self)
|
||||
|> mapToSignal { peerAndValue in
|
||||
guard let (peer, reactions, isLarge) = peerAndValue else {
|
||||
guard let (peer, reactions, isLarge, storeAsRecentlyUsed) = peerAndValue else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
guard let inputPeer = apiInputPeer(peer) else {
|
||||
@ -144,6 +148,9 @@ private func requestUpdateMessageReaction(postbox: Postbox, network: Network, st
|
||||
if isLarge {
|
||||
flags |= 1 << 1
|
||||
}
|
||||
if storeAsRecentlyUsed {
|
||||
flags |= 1 << 2
|
||||
}
|
||||
}
|
||||
|
||||
let signal: Signal<Never, RequestUpdateMessageReactionError> = network.request(Api.functions.messages.sendReaction(flags: flags, peer: inputPeer, msgId: messageId.id, reaction: reactions?.map(\.apiReaction)))
|
||||
@ -518,16 +525,23 @@ public final class EngineMessageReactionListContext {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
var existingPeerIds = Set<EnginePeer.Id>()
|
||||
|
||||
struct ItemHash: Hashable {
|
||||
var peerId: EnginePeer.Id
|
||||
var value: MessageReaction.Reaction?
|
||||
}
|
||||
|
||||
var existingItems = Set<ItemHash>()
|
||||
for item in strongSelf.state.items {
|
||||
existingPeerIds.insert(item.peer.id)
|
||||
existingItems.insert(ItemHash(peerId: item.peer.id, value: item.reaction))
|
||||
}
|
||||
|
||||
for item in state.items {
|
||||
if existingPeerIds.contains(item.peer.id) {
|
||||
let itemHash = ItemHash(peerId: item.peer.id, value: item.reaction)
|
||||
if existingItems.contains(itemHash) {
|
||||
continue
|
||||
}
|
||||
existingPeerIds.insert(item.peer.id)
|
||||
existingItems.insert(itemHash)
|
||||
strongSelf.state.items.append(item)
|
||||
}
|
||||
if state.canLoadMore {
|
||||
|
@ -250,6 +250,7 @@ public final class PendingReactionsMessageAttribute: MessageAttribute {
|
||||
public let accountPeerId: PeerId?
|
||||
public let reactions: [PendingReaction]
|
||||
public let isLarge: Bool
|
||||
public let storeAsRecentlyUsed: Bool
|
||||
|
||||
public var associatedPeerIds: [PeerId] {
|
||||
if let accountPeerId = self.accountPeerId {
|
||||
@ -277,16 +278,18 @@ public final class PendingReactionsMessageAttribute: MessageAttribute {
|
||||
return result
|
||||
}
|
||||
|
||||
public init(accountPeerId: PeerId?, reactions: [PendingReaction], isLarge: Bool) {
|
||||
public init(accountPeerId: PeerId?, reactions: [PendingReaction], isLarge: Bool, storeAsRecentlyUsed: Bool) {
|
||||
self.accountPeerId = accountPeerId
|
||||
self.reactions = reactions
|
||||
self.isLarge = isLarge
|
||||
self.storeAsRecentlyUsed = storeAsRecentlyUsed
|
||||
}
|
||||
|
||||
required public init(decoder: PostboxDecoder) {
|
||||
self.accountPeerId = decoder.decodeOptionalInt64ForKey("ap").flatMap(PeerId.init)
|
||||
self.reactions = decoder.decodeObjectArrayWithDecoderForKey("reac")
|
||||
self.isLarge = decoder.decodeInt32ForKey("l", orElse: 0) != 0
|
||||
self.storeAsRecentlyUsed = decoder.decodeInt32ForKey("used", orElse: 0) != 0
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
@ -299,5 +302,6 @@ public final class PendingReactionsMessageAttribute: MessageAttribute {
|
||||
encoder.encodeObjectArray(self.reactions, forKey: "reac")
|
||||
|
||||
encoder.encodeInt32(self.isLarge ? 1 : 0, forKey: "l")
|
||||
encoder.encodeInt32(self.storeAsRecentlyUsed ? 1 : 0, forKey: "used")
|
||||
}
|
||||
}
|
||||
|
@ -522,6 +522,15 @@ public final class TelegramMediaFile: Media, Equatable, Codable {
|
||||
return false
|
||||
}
|
||||
|
||||
public var isStaticEmoji: Bool {
|
||||
for attribute in self.attributes {
|
||||
if case .CustomEmoji = attribute {
|
||||
return self.mimeType == "image/webp"
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
public var isVideo: Bool {
|
||||
for attribute in self.attributes {
|
||||
if case .Video = attribute {
|
||||
|
@ -140,6 +140,18 @@ public extension TelegramEngine {
|
||||
|> ignoreValues
|
||||
}
|
||||
|
||||
public func clearRecentlyUsedReactions() -> Signal<Never, NoError> {
|
||||
let _ = self.account.postbox.transaction({ transaction -> Void in
|
||||
transaction.replaceOrderedItemListItems(collectionId: Namespaces.OrderedItemList.CloudRecentReactions, items: [])
|
||||
}).start()
|
||||
|
||||
return self.account.network.request(Api.functions.messages.clearRecentReactions())
|
||||
|> `catch` { _ -> Signal<Api.Bool, NoError> in
|
||||
return .single(.boolFalse)
|
||||
}
|
||||
|> ignoreValues
|
||||
}
|
||||
|
||||
public func reorderStickerPacks(namespace: ItemCollectionId.Namespace, itemIds: [ItemCollectionId]) -> Signal<Never, NoError> {
|
||||
return self.account.postbox.transaction { transaction -> Void in
|
||||
let infos = transaction.getItemCollectionsInfos(namespace: namespace)
|
||||
|
@ -19,10 +19,14 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/AnimationCache:AnimationCache",
|
||||
"//submodules/TelegramUI/Components/MultiAnimationRenderer:MultiAnimationRenderer",
|
||||
"//submodules/TelegramUI/Components/EntityKeyboard:EntityKeyboard",
|
||||
"//submodules/TelegramUI/Components/EmojiTextAttachmentView:EmojiTextAttachmentView",
|
||||
"//submodules/Components/ComponentDisplayAdapters:ComponentDisplayAdapters",
|
||||
"//submodules/Components/PagerComponent:PagerComponent",
|
||||
"//submodules/TelegramPresentationData:TelegramPresentationData",
|
||||
"//submodules/AccountContext:AccountContext",
|
||||
"//submodules/lottie-ios:Lottie",
|
||||
"//submodules/TextFormat:TextFormat",
|
||||
"//submodules/AppBundle:AppBundle",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -13,6 +13,10 @@ import AccountContext
|
||||
import PagerComponent
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import Lottie
|
||||
import EmojiTextAttachmentView
|
||||
import TextFormat
|
||||
import AppBundle
|
||||
|
||||
public final class EmojiStatusSelectionComponent: Component {
|
||||
public typealias EnvironmentType = Empty
|
||||
@ -184,6 +188,11 @@ public final class EmojiStatusSelectionController: ViewController {
|
||||
private var emojiContent: EmojiPagerContentComponent?
|
||||
private var scheduledEmojiContentAnimationHint: EmojiPagerContentComponent.ContentAnimation?
|
||||
|
||||
private var availableReactions: AvailableReactions?
|
||||
private var availableReactionsDisposable: Disposable?
|
||||
|
||||
private var hapticFeedback: HapticFeedback?
|
||||
|
||||
private var isDismissed: Bool = false
|
||||
|
||||
init(controller: EmojiStatusSelectionController, context: AccountContext, sourceView: UIView?, emojiContent: Signal<EmojiPagerContentComponent, NoError>) {
|
||||
@ -235,11 +244,11 @@ public final class EmojiStatusSelectionController: ViewController {
|
||||
strongSelf.emojiContent = emojiContent
|
||||
|
||||
emojiContent.inputInteractionHolder.inputInteraction = EmojiPagerContentComponent.InputInteraction(
|
||||
performItemAction: { _, item, _, _, _, _ in
|
||||
performItemAction: { groupId, item, _, _, _, _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.applyItem(item: item)
|
||||
strongSelf.applyItem(groupId: groupId, item: item)
|
||||
},
|
||||
deleteBackwards: {
|
||||
},
|
||||
@ -291,10 +300,20 @@ public final class EmojiStatusSelectionController: ViewController {
|
||||
|
||||
strongSelf.refreshLayout(transition: .immediate)
|
||||
})
|
||||
|
||||
self.availableReactionsDisposable = (context.engine.stickers.availableReactions()
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] availableReactions in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.availableReactions = availableReactions
|
||||
})
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.emojiContentDisposable?.dispose()
|
||||
self.availableReactionsDisposable?.dispose()
|
||||
}
|
||||
|
||||
private func refreshLayout(transition: Transition) {
|
||||
@ -316,6 +335,134 @@ public final class EmojiStatusSelectionController: ViewController {
|
||||
self.cloudShadowLayer1.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false)
|
||||
}
|
||||
|
||||
func animateOutToStatus(groupId: AnyHashable, item: EmojiPagerContentComponent.Item, destinationView: UIView) {
|
||||
guard let emojiView = self.componentHost.findTaggedView(tag: EmojiPagerContentComponent.Tag(id: AnyHashable("emoji"))) as? EmojiPagerContentComponent.View, let sourceLayer = emojiView.layerForItem( groupId: groupId, item: item) else {
|
||||
self.controller?.dismiss()
|
||||
return
|
||||
}
|
||||
|
||||
self.isUserInteractionEnabled = false
|
||||
|
||||
let hapticFeedback: HapticFeedback
|
||||
if let current = self.hapticFeedback {
|
||||
hapticFeedback = current
|
||||
} else {
|
||||
hapticFeedback = HapticFeedback()
|
||||
self.hapticFeedback = hapticFeedback
|
||||
}
|
||||
|
||||
hapticFeedback.prepareTap()
|
||||
|
||||
var itemCompleted = false
|
||||
var contentCompleted = false
|
||||
var effectCompleted = false
|
||||
let completion: () -> Void = { [weak self] in
|
||||
guard let strongSelf = self, itemCompleted, contentCompleted, effectCompleted else {
|
||||
return
|
||||
}
|
||||
strongSelf.controller?.dismissNow()
|
||||
}
|
||||
|
||||
if let sourceCopyLayer = sourceLayer.snapshotContentTree() {
|
||||
self.layer.addSublayer(sourceCopyLayer)
|
||||
sourceCopyLayer.frame = sourceLayer.convert(sourceLayer.bounds, to: self.layer)
|
||||
sourceLayer.isHidden = true
|
||||
destinationView.isHidden = true
|
||||
|
||||
let previousSourceCopyFrame = sourceCopyLayer.frame
|
||||
|
||||
let localDestinationFrame = destinationView.convert(destinationView.bounds, to: self.view)
|
||||
let destinationSize = max(localDestinationFrame.width, localDestinationFrame.height)
|
||||
let effectFrame = localDestinationFrame.insetBy(dx: -destinationSize * 2.0, dy: -destinationSize * 2.0)
|
||||
let destinationNormalScale = localDestinationFrame.width / previousSourceCopyFrame.width
|
||||
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .linear)
|
||||
sourceCopyLayer.position = localDestinationFrame.center
|
||||
transition.animatePositionWithKeyframes(layer: sourceCopyLayer, keyframes: generateParabollicMotionKeyframes(from: previousSourceCopyFrame.center, to: localDestinationFrame.center, elevation: -(localDestinationFrame.center.y - previousSourceCopyFrame.center.y) + 30.0), completion: { [weak self, weak sourceCopyLayer, weak destinationView] _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
itemCompleted = true
|
||||
sourceCopyLayer?.isHidden = true
|
||||
if let destinationView = destinationView {
|
||||
destinationView.isHidden = false
|
||||
destinationView.layer.animateScale(from: 0.5, to: 1.0, duration: 0.1)
|
||||
}
|
||||
|
||||
hapticFeedback.tap()
|
||||
|
||||
if let itemFile = item.itemFile, let url = getAppBundle().url(forResource: "generic_reaction_small_effect", withExtension: "json"), let composition = Animation.filepath(url.path) {
|
||||
let view = AnimationView(animation: composition, configuration: LottieConfiguration(renderingEngine: .mainThread, decodingStrategy: .codable))
|
||||
view.animationSpeed = 1.0
|
||||
view.backgroundColor = nil
|
||||
view.isOpaque = false
|
||||
|
||||
let animationCache = strongSelf.context.animationCache
|
||||
let animationRenderer = strongSelf.context.animationRenderer
|
||||
|
||||
for i in 1 ... 7 {
|
||||
let allLayers = view.allLayers(forKeypath: AnimationKeypath(keypath: "placeholder_\(i)"))
|
||||
for animationLayer in allLayers {
|
||||
let baseItemLayer = InlineStickerItemLayer(
|
||||
context: strongSelf.context,
|
||||
attemptSynchronousLoad: false,
|
||||
emoji: ChatTextInputTextCustomEmojiAttribute(stickerPack: nil, fileId: itemFile.fileId.id, file: itemFile),
|
||||
file: item.itemFile,
|
||||
cache: animationCache,
|
||||
renderer: animationRenderer,
|
||||
placeholderColor: UIColor(white: 0.0, alpha: 0.0),
|
||||
pointSize: CGSize(width: 32.0, height: 32.0)
|
||||
)
|
||||
|
||||
if let sublayers = animationLayer.sublayers {
|
||||
for sublayer in sublayers {
|
||||
sublayer.isHidden = true
|
||||
}
|
||||
}
|
||||
|
||||
baseItemLayer.isVisibleForAnimations = true
|
||||
baseItemLayer.frame = CGRect(origin: CGPoint(x: -0.0, y: -0.0), size: CGSize(width: 500.0, height: 500.0))
|
||||
animationLayer.addSublayer(baseItemLayer)
|
||||
}
|
||||
}
|
||||
|
||||
view.frame = effectFrame
|
||||
strongSelf.view.addSubview(view)
|
||||
view.play(completion: { _ in
|
||||
effectCompleted = true
|
||||
completion()
|
||||
})
|
||||
} else {
|
||||
effectCompleted = true
|
||||
}
|
||||
|
||||
completion()
|
||||
})
|
||||
let scaleKeyframes: [CGFloat] = [
|
||||
1.0,
|
||||
1.4,
|
||||
1.0,
|
||||
destinationNormalScale * 0.5
|
||||
]
|
||||
sourceCopyLayer.transform = CATransform3DMakeScale(scaleKeyframes[scaleKeyframes.count - 1], scaleKeyframes[scaleKeyframes.count - 1], 1.0)
|
||||
sourceCopyLayer.animateKeyframes(values: scaleKeyframes.map({ $0 as NSNumber }), duration: 0.2, keyPath: "transform.scale", timingFunction: CAMediaTimingFunctionName.linear.rawValue)
|
||||
} else {
|
||||
itemCompleted = true
|
||||
}
|
||||
|
||||
/*if let availableReactions = self.availableReactions, let availableReaction = availableReactions.reactions.first(where: { $0.value == }) {
|
||||
|
||||
} else {
|
||||
effectCompleted = true
|
||||
}*/
|
||||
|
||||
self.animateOut(completion: {
|
||||
contentCompleted = true
|
||||
completion()
|
||||
})
|
||||
}
|
||||
|
||||
func containerLayoutUpdated(layout: ContainerViewLayout, transition: Transition) {
|
||||
self.validLayout = layout
|
||||
|
||||
@ -465,30 +612,42 @@ public final class EmojiStatusSelectionController: ViewController {
|
||||
return nil
|
||||
}
|
||||
|
||||
private func applyItem(item: EmojiPagerContentComponent.Item?) {
|
||||
self.controller?.dismiss()
|
||||
|
||||
private func applyItem(groupId: AnyHashable, item: EmojiPagerContentComponent.Item?) {
|
||||
guard let controller = self.controller else {
|
||||
return
|
||||
}
|
||||
|
||||
let _ = (self.context.engine.accountData.setEmojiStatus(file: item?.itemFile)
|
||||
|> deliverOnMainQueue).start()
|
||||
|
||||
if let item = item, let destinationView = controller.destinationItemView() {
|
||||
self.animateOutToStatus(groupId: groupId, item: item, destinationView: destinationView)
|
||||
} else {
|
||||
controller.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private let context: AccountContext
|
||||
private weak var sourceView: UIView?
|
||||
private let emojiContent: Signal<EmojiPagerContentComponent, NoError>
|
||||
private let destinationItemView: () -> UIView?
|
||||
|
||||
fileprivate let _ready = Promise<Bool>()
|
||||
override public var ready: Promise<Bool> {
|
||||
return self._ready
|
||||
}
|
||||
|
||||
public init(context: AccountContext, sourceView: UIView, emojiContent: Signal<EmojiPagerContentComponent, NoError>) {
|
||||
public init(context: AccountContext, sourceView: UIView, emojiContent: Signal<EmojiPagerContentComponent, NoError>, destinationItemView: @escaping () -> UIView?) {
|
||||
self.context = context
|
||||
self.sourceView = sourceView
|
||||
self.emojiContent = emojiContent
|
||||
self.destinationItemView = destinationItemView
|
||||
|
||||
super.init(navigationBarPresentationData: nil)
|
||||
|
||||
self.lockOrientation = true
|
||||
|
||||
self.statusBar.statusBarStyle = .Ignore
|
||||
}
|
||||
|
||||
@ -500,6 +659,10 @@ public final class EmojiStatusSelectionController: ViewController {
|
||||
super.viewDidAppear(animated)
|
||||
}
|
||||
|
||||
private func dismissNow() {
|
||||
self.presentingViewController?.dismiss(animated: false, completion: nil)
|
||||
}
|
||||
|
||||
override public func dismiss(completion: (() -> Void)? = nil) {
|
||||
(self.displayNode as! Node).animateOut(completion: { [weak self] in
|
||||
self?.presentingViewController?.dismiss(animated: false, completion: nil)
|
||||
@ -519,3 +682,37 @@ public final class EmojiStatusSelectionController: ViewController {
|
||||
(self.displayNode as! Node).containerLayoutUpdated(layout: layout, transition: Transition(transition))
|
||||
}
|
||||
}
|
||||
|
||||
private func generateParabollicMotionKeyframes(from sourcePoint: CGPoint, to targetPosition: CGPoint, elevation: CGFloat) -> [CGPoint] {
|
||||
let midPoint = CGPoint(x: (sourcePoint.x + targetPosition.x) / 2.0, y: sourcePoint.y - elevation)
|
||||
|
||||
let x1 = sourcePoint.x
|
||||
let y1 = sourcePoint.y
|
||||
let x2 = midPoint.x
|
||||
let y2 = midPoint.y
|
||||
let x3 = targetPosition.x
|
||||
let y3 = targetPosition.y
|
||||
|
||||
var keyframes: [CGPoint] = []
|
||||
if abs(y1 - y3) < 5.0 && abs(x1 - x3) < 5.0 {
|
||||
for i in 0 ..< 10 {
|
||||
let k = CGFloat(i) / CGFloat(10 - 1)
|
||||
let x = sourcePoint.x * (1.0 - k) + targetPosition.x * k
|
||||
let y = sourcePoint.y * (1.0 - k) + targetPosition.y * k
|
||||
keyframes.append(CGPoint(x: x, y: y))
|
||||
}
|
||||
} else {
|
||||
let a = (x3 * (y2 - y1) + x2 * (y1 - y3) + x1 * (y3 - y2)) / ((x1 - x2) * (x1 - x3) * (x2 - x3))
|
||||
let b = (x1 * x1 * (y2 - y3) + x3 * x3 * (y1 - y2) + x2 * x2 * (y3 - y1)) / ((x1 - x2) * (x1 - x3) * (x2 - x3))
|
||||
let c = (x2 * x2 * (x3 * y1 - x1 * y3) + x2 * (x1 * x1 * y3 - x3 * x3 * y1) + x1 * x3 * (x3 - x1) * y2) / ((x1 - x2) * (x1 - x3) * (x2 - x3))
|
||||
|
||||
for i in 0 ..< 10 {
|
||||
let k = CGFloat(i) / CGFloat(10 - 1)
|
||||
let x = sourcePoint.x * (1.0 - k) + targetPosition.x * k
|
||||
let y = a * x * x + b * x + c
|
||||
keyframes.append(CGPoint(x: x, y: y))
|
||||
}
|
||||
}
|
||||
|
||||
return keyframes
|
||||
}
|
||||
|
@ -2728,6 +2728,15 @@ public final class EmojiPagerContentComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
public func layerForItem(groupId: AnyHashable, item: EmojiPagerContentComponent.Item) -> CALayer? {
|
||||
let itemKey = EmojiPagerContentComponent.View.ItemLayer.Key(groupId: groupId, itemId: item.content.id)
|
||||
if let itemLayer = self.visibleItemLayers[itemKey] {
|
||||
return itemLayer
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public func scrollToItemGroup(id supergroupId: AnyHashable, subgroupId: Int32?) {
|
||||
guard let component = self.component, let pagerEnvironment = self.pagerEnvironment, let itemLayout = self.itemLayout else {
|
||||
return
|
||||
@ -4593,6 +4602,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
var isPremiumLocked: Bool
|
||||
var isFeatured: Bool
|
||||
var isExpandable: Bool
|
||||
var isClearable: Bool
|
||||
var headerItem: EntityKeyboardAnimationData?
|
||||
var items: [EmojiPagerContentComponent.Item]
|
||||
}
|
||||
@ -4628,7 +4638,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
itemGroups[groupIndex].items.append(resultItem)
|
||||
} else {
|
||||
itemGroupIndexById[groupId] = itemGroups.count
|
||||
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: nil, subtitle: nil, isPremiumLocked: false, isFeatured: false, isExpandable: true, headerItem: nil, items: [resultItem]))
|
||||
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: nil, subtitle: nil, isPremiumLocked: false, isFeatured: false, isExpandable: true, isClearable: false, headerItem: nil, items: [resultItem]))
|
||||
}
|
||||
|
||||
var existingIds = Set<MediaId>()
|
||||
@ -4706,56 +4716,107 @@ public final class EmojiPagerContentComponent: Component {
|
||||
let groupId = "recent"
|
||||
if let groupIndex = itemGroupIndexById[groupId] {
|
||||
itemGroups[groupIndex].items.append(resultItem)
|
||||
|
||||
if itemGroups[groupIndex].items.count >= 16 {
|
||||
break
|
||||
}
|
||||
} else {
|
||||
itemGroupIndexById[groupId] = itemGroups.count
|
||||
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: nil, subtitle: nil, isPremiumLocked: false, isFeatured: false, isExpandable: false, headerItem: nil, items: [resultItem]))
|
||||
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: nil, subtitle: nil, isPremiumLocked: false, isFeatured: false, isExpandable: false, isClearable: false, headerItem: nil, items: [resultItem]))
|
||||
}
|
||||
}
|
||||
|
||||
if let recentReactions = recentReactions {
|
||||
for item in recentReactions.items {
|
||||
guard let item = item.contents.get(RecentReactionItem.self) else {
|
||||
continue
|
||||
}
|
||||
|
||||
let animationFile: TelegramMediaFile
|
||||
switch item.content {
|
||||
case let .builtin(value):
|
||||
if existingIds.contains(.builtin(value)) {
|
||||
if hasPremium {
|
||||
var hasRecent = false
|
||||
if let recentReactions = recentReactions, !recentReactions.items.isEmpty {
|
||||
hasRecent = true
|
||||
}
|
||||
|
||||
let maxRecentLineCount = 4
|
||||
|
||||
//TODO:localize
|
||||
let popularTitle = hasRecent ? "Recently Used" : "Popular"
|
||||
|
||||
if let availableReactions = availableReactions {
|
||||
for reactionItem in availableReactions.reactions {
|
||||
if !reactionItem.isEnabled {
|
||||
continue
|
||||
}
|
||||
existingIds.insert(.builtin(value))
|
||||
if let availableReactions = availableReactions, let availableReaction = availableReactions.reactions.first(where: { $0.value == .builtin(value) }) {
|
||||
if let centerAnimation = availableReaction.centerAnimation {
|
||||
animationFile = centerAnimation
|
||||
if existingIds.contains(reactionItem.value) {
|
||||
continue
|
||||
}
|
||||
existingIds.insert(reactionItem.value)
|
||||
|
||||
let animationFile = reactionItem.selectAnimation
|
||||
let animationData = EntityKeyboardAnimationData(file: animationFile, isReaction: true)
|
||||
let resultItem = EmojiPagerContentComponent.Item(
|
||||
animationData: animationData,
|
||||
content: .animation(animationData),
|
||||
itemFile: animationFile,
|
||||
subgroupId: nil
|
||||
)
|
||||
|
||||
let groupId = "popular"
|
||||
if let groupIndex = itemGroupIndexById[groupId] {
|
||||
itemGroups[groupIndex].items.append(resultItem)
|
||||
} else {
|
||||
itemGroupIndexById[groupId] = itemGroups.count
|
||||
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: popularTitle, subtitle: nil, isPremiumLocked: false, isFeatured: false, isExpandable: false, isClearable: hasRecent, headerItem: nil, items: [resultItem]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let recentReactions = recentReactions {
|
||||
var popularInsertIndex = 0
|
||||
for item in recentReactions.items {
|
||||
guard let item = item.contents.get(RecentReactionItem.self) else {
|
||||
continue
|
||||
}
|
||||
|
||||
let animationFile: TelegramMediaFile
|
||||
switch item.content {
|
||||
case let .builtin(value):
|
||||
if existingIds.contains(.builtin(value)) {
|
||||
continue
|
||||
}
|
||||
existingIds.insert(.builtin(value))
|
||||
if let availableReactions = availableReactions, let availableReaction = availableReactions.reactions.first(where: { $0.value == .builtin(value) }) {
|
||||
if let centerAnimation = availableReaction.centerAnimation {
|
||||
animationFile = centerAnimation
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
case let .custom(file):
|
||||
if existingIds.contains(.custom(file.fileId.id)) {
|
||||
continue
|
||||
}
|
||||
existingIds.insert(.custom(file.fileId.id))
|
||||
animationFile = file
|
||||
}
|
||||
|
||||
let animationData = EntityKeyboardAnimationData(file: animationFile, isReaction: true)
|
||||
let resultItem = EmojiPagerContentComponent.Item(
|
||||
animationData: animationData,
|
||||
content: .animation(animationData),
|
||||
itemFile: animationFile,
|
||||
subgroupId: nil
|
||||
)
|
||||
|
||||
let groupId = "popular"
|
||||
if let groupIndex = itemGroupIndexById[groupId] {
|
||||
if itemGroups[groupIndex].items.count + 1 >= maxRecentLineCount * 8 {
|
||||
break
|
||||
}
|
||||
|
||||
itemGroups[groupIndex].items.insert(resultItem, at: popularInsertIndex)
|
||||
popularInsertIndex += 1
|
||||
} else {
|
||||
continue
|
||||
itemGroupIndexById[groupId] = itemGroups.count
|
||||
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: popularTitle, subtitle: nil, isPremiumLocked: false, isFeatured: false, isExpandable: false, isClearable: hasRecent, headerItem: nil, items: [resultItem]))
|
||||
}
|
||||
case let .custom(file):
|
||||
if existingIds.contains(.custom(file.fileId.id)) {
|
||||
continue
|
||||
}
|
||||
existingIds.insert(.custom(file.fileId.id))
|
||||
animationFile = file
|
||||
}
|
||||
|
||||
let animationData = EntityKeyboardAnimationData(file: animationFile, isReaction: true)
|
||||
let resultItem = EmojiPagerContentComponent.Item(
|
||||
animationData: animationData,
|
||||
content: .animation(animationData),
|
||||
itemFile: animationFile,
|
||||
subgroupId: nil
|
||||
)
|
||||
|
||||
let groupId = "recent"
|
||||
if let groupIndex = itemGroupIndexById[groupId] {
|
||||
itemGroups[groupIndex].items.append(resultItem)
|
||||
} else {
|
||||
itemGroupIndexById[groupId] = itemGroups.count
|
||||
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: nil, subtitle: nil, isPremiumLocked: false, isFeatured: false, isExpandable: false, headerItem: nil, items: [resultItem]))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4799,7 +4860,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
itemGroups[groupIndex].items.append(resultItem)
|
||||
} else {
|
||||
itemGroupIndexById[groupId] = itemGroups.count
|
||||
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: strings.Emoji_FrequentlyUsed, subtitle: nil, isPremiumLocked: false, isFeatured: false, isExpandable: false, headerItem: nil, items: [resultItem]))
|
||||
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: strings.Emoji_FrequentlyUsed, subtitle: nil, isPremiumLocked: false, isFeatured: false, isExpandable: false, isClearable: true, headerItem: nil, items: [resultItem]))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4819,7 +4880,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
itemGroups[groupIndex].items.append(resultItem)
|
||||
} else {
|
||||
itemGroupIndexById[groupId] = itemGroups.count
|
||||
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: strings.EmojiInput_SectionTitleEmoji, subtitle: nil, isPremiumLocked: false, isFeatured: false, isExpandable: false, headerItem: nil, items: [resultItem]))
|
||||
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: strings.EmojiInput_SectionTitleEmoji, subtitle: nil, isPremiumLocked: false, isFeatured: false, isExpandable: false, isClearable: false, headerItem: nil, items: [resultItem]))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4883,7 +4944,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
break inner
|
||||
}
|
||||
}
|
||||
itemGroups.append(ItemGroup(supergroupId: supergroupId, id: groupId, title: title, subtitle: nil, isPremiumLocked: isPremiumLocked, isFeatured: false, isExpandable: false, headerItem: headerItem, items: [resultItem]))
|
||||
itemGroups.append(ItemGroup(supergroupId: supergroupId, id: groupId, title: title, subtitle: nil, isPremiumLocked: isPremiumLocked, isFeatured: false, isExpandable: false, isClearable: false, headerItem: headerItem, items: [resultItem]))
|
||||
}
|
||||
}
|
||||
|
||||
@ -4937,7 +4998,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
)
|
||||
}
|
||||
|
||||
itemGroups.append(ItemGroup(supergroupId: supergroupId, id: groupId, title: featuredEmojiPack.info.title, subtitle: nil, isPremiumLocked: isPremiumLocked, isFeatured: true, isExpandable: true, headerItem: headerItem, items: [resultItem]))
|
||||
itemGroups.append(ItemGroup(supergroupId: supergroupId, id: groupId, title: featuredEmojiPack.info.title, subtitle: nil, isPremiumLocked: isPremiumLocked, isFeatured: true, isExpandable: true, isClearable: false, headerItem: headerItem, items: [resultItem]))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4952,11 +5013,6 @@ public final class EmojiPagerContentComponent: Component {
|
||||
animationRenderer: animationRenderer,
|
||||
inputInteractionHolder: EmojiPagerContentComponent.InputInteractionHolder(),
|
||||
itemGroups: itemGroups.map { group -> EmojiPagerContentComponent.ItemGroup in
|
||||
var hasClear = false
|
||||
if group.id == AnyHashable("recent") {
|
||||
hasClear = true
|
||||
}
|
||||
|
||||
var headerItem = group.headerItem
|
||||
|
||||
if let groupId = group.id.base as? ItemCollectionId {
|
||||
@ -4983,7 +5039,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
isFeatured: group.isFeatured,
|
||||
isPremiumLocked: group.isPremiumLocked,
|
||||
isEmbedded: false,
|
||||
hasClear: hasClear,
|
||||
hasClear: group.isClearable,
|
||||
isExpandable: group.isExpandable,
|
||||
displayPremiumBadges: false,
|
||||
headerItem: headerItem,
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -19,6 +19,8 @@ import PresentationDataUtils
|
||||
import MeshAnimationCache
|
||||
import FetchManagerImpl
|
||||
import InAppPurchaseManager
|
||||
import AnimationCache
|
||||
import MultiAnimationRenderer
|
||||
|
||||
private final class DeviceSpecificContactImportContext {
|
||||
let disposable = MetaDisposable()
|
||||
@ -160,6 +162,9 @@ public final class AccountContextImpl: AccountContext {
|
||||
public let cachedGroupCallContexts: AccountGroupCallContextCache
|
||||
public let meshAnimationCache: MeshAnimationCache
|
||||
|
||||
public let animationCache: AnimationCache
|
||||
public let animationRenderer: MultiAnimationRenderer
|
||||
|
||||
private var animatedEmojiStickersDisposable: Disposable?
|
||||
public private(set) var animatedEmojiStickers: [String: [StickerPackItem]] = [:]
|
||||
|
||||
@ -204,6 +209,11 @@ public final class AccountContextImpl: AccountContext {
|
||||
self.cachedGroupCallContexts = AccountGroupCallContextCacheImpl()
|
||||
self.meshAnimationCache = MeshAnimationCache(mediaBox: account.postbox.mediaBox)
|
||||
|
||||
self.animationCache = AnimationCacheImpl(basePath: self.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: {
|
||||
return TempBox.shared.tempFile(fileName: "file").path
|
||||
})
|
||||
self.animationRenderer = MultiAnimationRendererImpl()
|
||||
|
||||
let updatedLimitsConfiguration = account.postbox.preferencesView(keys: [PreferencesKeys.limitsConfiguration])
|
||||
|> map { preferences -> LimitsConfiguration in
|
||||
return preferences.values[PreferencesKeys.limitsConfiguration]?.get(LimitsConfiguration.self) ?? LimitsConfiguration.defaultValue
|
||||
|
@ -1336,7 +1336,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
}
|
||||
|
||||
let _ = updateMessageReactionsInteractively(account: strongSelf.context.account, messageId: message.id, reactions: mappedUpdatedReactions, isLarge: isLarge).start()
|
||||
let _ = updateMessageReactionsInteractively(account: strongSelf.context.account, messageId: message.id, reactions: mappedUpdatedReactions, isLarge: isLarge, storeAsRecentlyUsed: true).start()
|
||||
|
||||
/*let currentReactions = mergedMessageReactions(attributes: message.attributes)?.reactions ?? []
|
||||
var updatedReactions: [MessageReaction.Reaction] = currentReactions.filter(\.isSelected).map(\.value)
|
||||
@ -1743,7 +1743,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
}
|
||||
|
||||
let _ = updateMessageReactionsInteractively(account: strongSelf.context.account, messageId: message.id, reactions: mappedUpdatedReactions, isLarge: false).start()
|
||||
let _ = updateMessageReactionsInteractively(account: strongSelf.context.account, messageId: message.id, reactions: mappedUpdatedReactions, isLarge: false, storeAsRecentlyUsed: false).start()
|
||||
}
|
||||
})
|
||||
}, activateMessagePinch: { [weak self] sourceNode in
|
||||
|
@ -87,15 +87,8 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
|
||||
let isPremiumDisabled = premiumConfiguration.isPremiumDisabled
|
||||
|
||||
let animationCache = AnimationCacheImpl(basePath: context.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: {
|
||||
return TempBox.shared.tempFile(fileName: "file").path
|
||||
})
|
||||
let animationRenderer: MultiAnimationRenderer
|
||||
/*if #available(iOS 13.0, *) {
|
||||
animationRenderer = MultiAnimationMetalRendererImpl()
|
||||
} else {*/
|
||||
animationRenderer = MultiAnimationRendererImpl()
|
||||
//}
|
||||
let animationCache = context.animationCache
|
||||
let animationRenderer = context.animationRenderer
|
||||
|
||||
let emojiItems = EmojiPagerContentComponent.emojiInputData(context: context, animationCache: animationCache, animationRenderer: animationRenderer, isStandalone: false, isStatusSelection: false, isReactionSelection: false, topReactionItems: [], areUnicodeEmojiEnabled: true, areCustomEmojiEnabled: areCustomEmojiEnabled, chatPeerId: chatPeerId)
|
||||
|
||||
@ -1902,10 +1895,8 @@ final class EntityInputView: UIView, AttachmentTextInputPanelInputView, UIInputV
|
||||
) {
|
||||
self.context = context
|
||||
|
||||
self.animationCache = AnimationCacheImpl(basePath: context.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: {
|
||||
return TempBox.shared.tempFile(fileName: "file").path
|
||||
})
|
||||
self.animationRenderer = MultiAnimationRendererImpl()
|
||||
self.animationCache = context.animationCache
|
||||
self.animationRenderer = context.animationRenderer
|
||||
|
||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
if isDark {
|
||||
|
@ -2410,6 +2410,8 @@ private final class ChatReadReportContextItemNode: ASDisplayNode, ContextMenuCus
|
||||
}
|
||||
|
||||
if let currentStats = self.currentStats {
|
||||
reactionCount = currentStats.reactionCount
|
||||
|
||||
if currentStats.peers.isEmpty {
|
||||
if reactionCount != 0 {
|
||||
let text: String = self.presentationData.strings.Chat_ContextReactionCount(Int32(reactionCount))
|
||||
|
@ -298,18 +298,8 @@ class ChatPresentationContext {
|
||||
init(context: AccountContext, backgroundNode: WallpaperBackgroundNode?) {
|
||||
self.backgroundNode = backgroundNode
|
||||
|
||||
self.animationCache = AnimationCacheImpl(basePath: context.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: {
|
||||
return TempBox.shared.tempFile(fileName: "file").path
|
||||
})
|
||||
|
||||
let animationRenderer: MultiAnimationRenderer
|
||||
/*if #available(iOS 13.0, *) {
|
||||
animationRenderer = MultiAnimationMetalRendererImpl()
|
||||
} else {*/
|
||||
animationRenderer = MultiAnimationRendererImpl()
|
||||
//}
|
||||
|
||||
self.animationRenderer = animationRenderer
|
||||
self.animationCache = context.animationCache
|
||||
self.animationRenderer = context.animationRenderer
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -113,12 +113,7 @@ final class ChatMessageNotificationItemNode: NotificationItemNode {
|
||||
func setupItem(_ item: ChatMessageNotificationItem, compact: Bool) {
|
||||
self.item = item
|
||||
|
||||
if self.animationCache == nil {
|
||||
self.animationCache = AnimationCacheImpl(basePath: item.context.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: {
|
||||
return TempBox.shared.tempFile(fileName: "file").path
|
||||
})
|
||||
self.multiAnimationRenderer = MultiAnimationRendererImpl()
|
||||
}
|
||||
self.animationCache = item.context.animationCache
|
||||
|
||||
self.compact = compact
|
||||
if compact {
|
||||
|
@ -1411,10 +1411,20 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
|
||||
var entities: [MessageTextEntity] = []
|
||||
|
||||
let rawText: PresentationStrings.FormattedString
|
||||
if !updatedValue.isEmpty {
|
||||
let emojiString = updatedValue.joined(separator: ", ")
|
||||
switch updatedValue {
|
||||
case .all:
|
||||
rawText = self.presentationData.strings.Channel_AdminLog_ReactionsEnabled(author.flatMap(EnginePeer.init)?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? "")
|
||||
case let .limited(reactions):
|
||||
let emojiString = reactions.compactMap({ reaction -> String? in
|
||||
switch reaction {
|
||||
case let .builtin(value):
|
||||
return value
|
||||
case .custom:
|
||||
return nil
|
||||
}
|
||||
}).joined(separator: ", ")
|
||||
rawText = self.presentationData.strings.Channel_AdminLog_AllowedReactionsUpdated(author.flatMap(EnginePeer.init)?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? "", emojiString)
|
||||
} else {
|
||||
case .empty:
|
||||
rawText = self.presentationData.strings.Channel_AdminLog_ReactionsDisabled(author.flatMap(EnginePeer.init)?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? "")
|
||||
}
|
||||
|
||||
|
@ -173,10 +173,8 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, UIScrollViewDe
|
||||
self.presentationData = presentationData
|
||||
self.presentationDataPromise = Promise(ChatListPresentationData(theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: true))
|
||||
|
||||
self.animationCache = AnimationCacheImpl(basePath: context.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: {
|
||||
return TempBox.shared.tempFile(fileName: "file").path
|
||||
})
|
||||
self.animationRenderer = MultiAnimationRendererImpl()
|
||||
self.animationCache = context.animationCache
|
||||
self.animationRenderer = context.animationRenderer
|
||||
|
||||
self.listNode = ListView()
|
||||
self.listNode.verticalScrollIndicatorColor = self.presentationData.theme.list.scrollIndicatorColor
|
||||
|
@ -77,10 +77,8 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
|
||||
self.context = context
|
||||
self.presentationData = presentationData
|
||||
|
||||
self.animationCache = AnimationCacheImpl(basePath: context.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: {
|
||||
return TempBox.shared.tempFile(fileName: "file").path
|
||||
})
|
||||
self.animationRenderer = MultiAnimationRendererImpl()
|
||||
self.animationCache = context.animationCache
|
||||
self.animationRenderer = context.animationRenderer
|
||||
|
||||
var placeholder: String
|
||||
var includeChatList = false
|
||||
|
@ -2109,10 +2109,8 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
self.separatorNode = ASDisplayNode()
|
||||
self.separatorNode.isLayerBacked = true
|
||||
|
||||
self.animationCache = AnimationCacheImpl(basePath: context.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: {
|
||||
return TempBox.shared.tempFile(fileName: "file").path
|
||||
})
|
||||
self.animationRenderer = MultiAnimationRendererImpl()
|
||||
self.animationCache = context.animationCache
|
||||
self.animationRenderer = context.animationRenderer
|
||||
|
||||
super.init()
|
||||
|
||||
@ -2676,7 +2674,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
collapsedTransitionOffset = -10.0 * navigationTransition.fraction
|
||||
}
|
||||
|
||||
transition.updateFrame(view: self.titleCredibilityIconView, frame: CGRect(origin: CGPoint(x: titleSize.width + 4.0 + collapsedTransitionOffset, y: floor((titleSize.height - credibilityIconSize.height) / 2.0)), size: credibilityIconSize))
|
||||
transition.updateFrame(view: self.titleCredibilityIconView, frame: CGRect(origin: CGPoint(x: titleSize.width + 4.0 + collapsedTransitionOffset, y: floor((titleSize.height - credibilityIconSize.height) / 2.0) + 2.0), size: credibilityIconSize))
|
||||
transition.updateFrame(view: self.titleExpandedCredibilityIconView, frame: CGRect(origin: CGPoint(x: titleExpandedSize.width + 4.0, y: floor((titleExpandedSize.height - titleExpandedCredibilityIconSize.height) / 2.0) + 1.0), size: titleExpandedCredibilityIconSize))
|
||||
}
|
||||
|
||||
@ -3155,9 +3153,6 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
guard let result = super.hitTest(point, with: event) else {
|
||||
return nil
|
||||
}
|
||||
if result.isDescendant(of: self.navigationButtonContainer.view) {
|
||||
return result
|
||||
}
|
||||
if !self.backgroundNode.frame.contains(point) {
|
||||
return nil
|
||||
}
|
||||
@ -3177,6 +3172,10 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
if result.isDescendant(of: self.navigationButtonContainer.view) {
|
||||
return result
|
||||
}
|
||||
|
||||
if result == self.view || result == self.regularContentNode.view || result == self.editingContentNode.view {
|
||||
return nil
|
||||
}
|
||||
|
@ -624,16 +624,19 @@ private func settingsItems(data: PeerInfoScreenData?, context: AccountContext, p
|
||||
displaySetPhoto = true
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
var setStatusTitle: String = ""
|
||||
let displaySetStatus: Bool
|
||||
var hasEmojiStatus = false
|
||||
if let peer = data.peer as? TelegramUser, peer.isPremium {
|
||||
if peer.emojiStatus != nil {
|
||||
hasEmojiStatus = true
|
||||
//TODO:localize
|
||||
setStatusTitle = "Change Emoji Status"
|
||||
} else {
|
||||
//TODO:localize
|
||||
setStatusTitle = "Set Emoji Status"
|
||||
}
|
||||
displaySetStatus = true
|
||||
setStatusTitle = "Set Emoji Status"
|
||||
} else {
|
||||
displaySetStatus = false
|
||||
}
|
||||
@ -972,7 +975,7 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
|
||||
}))
|
||||
}
|
||||
}
|
||||
if let reactionSourceMessageId = reactionSourceMessageId {
|
||||
if let reactionSourceMessageId = reactionSourceMessageId, !data.isContact {
|
||||
items[.peerInfo]!.append(PeerInfoScreenActionItem(id: 3, text: presentationData.strings.UserInfo_SendMessage, action: {
|
||||
interaction.openChat()
|
||||
}))
|
||||
@ -3089,10 +3092,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
return
|
||||
}
|
||||
|
||||
let animationCache = AnimationCacheImpl(basePath: context.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: {
|
||||
return TempBox.shared.tempFile(fileName: "file").path
|
||||
})
|
||||
let animationRenderer = MultiAnimationRendererImpl()
|
||||
let animationCache = context.animationCache
|
||||
let animationRenderer = context.animationRenderer
|
||||
|
||||
strongSelf.controller?.present(EmojiStatusSelectionController(
|
||||
context: strongSelf.context,
|
||||
sourceView: sourceView,
|
||||
@ -3107,7 +3109,10 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
areUnicodeEmojiEnabled: false,
|
||||
areCustomEmojiEnabled: true,
|
||||
chatPeerId: strongSelf.context.account.peerId
|
||||
)
|
||||
),
|
||||
destinationItemView: { [weak sourceView] in
|
||||
return sourceView
|
||||
}
|
||||
), in: .window(.root))
|
||||
}
|
||||
} else {
|
||||
@ -3128,20 +3133,18 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if let emojiStatusFile = emojiStatusFile {
|
||||
let source: PremiumSource
|
||||
if let peerStatus = peerStatus {
|
||||
source = .emojiStatus(strongSelf.peerId, peerStatus.fileId, emojiStatusFile, emojiPackTitle)
|
||||
} else {
|
||||
source = .profile(strongSelf.peerId)
|
||||
}
|
||||
|
||||
let controller = PremiumIntroScreen(context: strongSelf.context, source: source)
|
||||
controller.sourceView = sourceView
|
||||
controller.containerView = strongSelf.controller?.navigationController?.view
|
||||
controller.animationColor = white ? .white : strongSelf.presentationData.theme.list.itemAccentColor
|
||||
strongSelf.controller?.push(controller)
|
||||
let source: PremiumSource
|
||||
if let peerStatus = peerStatus, let emojiStatusFile = emojiStatusFile {
|
||||
source = .emojiStatus(strongSelf.peerId, peerStatus.fileId, emojiStatusFile, emojiPackTitle)
|
||||
} else {
|
||||
source = .profile(strongSelf.peerId)
|
||||
}
|
||||
|
||||
let controller = PremiumIntroScreen(context: strongSelf.context, source: source)
|
||||
controller.sourceView = sourceView
|
||||
controller.containerView = strongSelf.controller?.navigationController?.view
|
||||
controller.animationColor = white ? .white : strongSelf.presentationData.theme.list.itemAccentColor
|
||||
strongSelf.controller?.push(controller)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -98,10 +98,8 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
||||
|
||||
self.presentationData = presentationData
|
||||
|
||||
self.animationCache = AnimationCacheImpl(basePath: context.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: {
|
||||
return TempBox.shared.tempFile(fileName: "file").path
|
||||
})
|
||||
self.animationRenderer = MultiAnimationRendererImpl()
|
||||
self.animationCache = context.animationCache
|
||||
self.animationRenderer = context.animationRenderer
|
||||
|
||||
self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: .builtin(WallpaperSettings()), theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: self.context.currentLimitsConfiguration.with { $0 }, fontSize: self.presentationData.chatFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: self.context.account.peerId, mode: .standard(previewing: false), chatLocation: .peer(id: PeerId(0)), subject: nil, peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, importState: nil)
|
||||
|
||||
|
@ -16,15 +16,37 @@ func topMessageReactions(context: AccountContext, message: Message) -> Signal<[R
|
||||
return item.contents.get(RecentReactionItem.self)
|
||||
}
|
||||
}
|
||||
|
||||
let allowedReactionsWithFiles: Signal<(reactions: AllowedReactions, files: [Int64: TelegramMediaFile])?, NoError> = peerMessageAllowedReactions(context: context, message: message)
|
||||
|> mapToSignal { allowedReactions -> Signal<(reactions: AllowedReactions, files: [Int64: TelegramMediaFile])?, NoError> in
|
||||
guard let allowedReactions = allowedReactions else {
|
||||
return .single(nil)
|
||||
}
|
||||
if case let .set(reactions) = allowedReactions {
|
||||
return context.engine.stickers.resolveInlineStickers(fileIds: reactions.compactMap { item -> Int64? in
|
||||
switch item {
|
||||
case .builtin:
|
||||
return nil
|
||||
case let .custom(fileId):
|
||||
return fileId
|
||||
}
|
||||
})
|
||||
|> map { files -> (reactions: AllowedReactions, files: [Int64: TelegramMediaFile]) in
|
||||
return (allowedReactions, files)
|
||||
}
|
||||
} else {
|
||||
return .single((allowedReactions, [:]))
|
||||
}
|
||||
}
|
||||
|
||||
return combineLatest(
|
||||
context.engine.stickers.availableReactions(),
|
||||
peerMessageAllowedReactions(context: context, message: message),
|
||||
allowedReactionsWithFiles,
|
||||
topReactions
|
||||
)
|
||||
|> take(1)
|
||||
|> map { availableReactions, allowedReactions, topReactions -> [ReactionItem] in
|
||||
guard let availableReactions = availableReactions, let allowedReactions = allowedReactions else {
|
||||
|> map { availableReactions, allowedReactionsAndFiles, topReactions -> [ReactionItem] in
|
||||
guard let availableReactions = availableReactions, let allowedReactionsAndFiles = allowedReactionsAndFiles else {
|
||||
return []
|
||||
}
|
||||
|
||||
@ -48,7 +70,7 @@ func topMessageReactions(context: AccountContext, message: Message) -> Signal<[R
|
||||
}
|
||||
existingIds.insert(reaction.value)
|
||||
|
||||
switch allowedReactions {
|
||||
switch allowedReactionsAndFiles.reactions {
|
||||
case let .set(set):
|
||||
if !set.contains(reaction.value) {
|
||||
continue
|
||||
@ -71,7 +93,7 @@ func topMessageReactions(context: AccountContext, message: Message) -> Signal<[R
|
||||
continue
|
||||
}
|
||||
case let .custom(file):
|
||||
switch allowedReactions {
|
||||
switch allowedReactionsAndFiles.reactions {
|
||||
case let .set(set):
|
||||
if !set.contains(.custom(file.fileId.id)) {
|
||||
continue
|
||||
@ -109,13 +131,13 @@ func topMessageReactions(context: AccountContext, message: Message) -> Signal<[R
|
||||
continue
|
||||
}
|
||||
|
||||
switch allowedReactions {
|
||||
switch allowedReactionsAndFiles.reactions {
|
||||
case let .set(set):
|
||||
if !set.contains(reaction.value) {
|
||||
continue
|
||||
}
|
||||
case .all:
|
||||
break
|
||||
continue
|
||||
}
|
||||
|
||||
if existingIds.contains(reaction.value) {
|
||||
@ -134,6 +156,33 @@ func topMessageReactions(context: AccountContext, message: Message) -> Signal<[R
|
||||
isCustom: false
|
||||
))
|
||||
}
|
||||
|
||||
if case let .set(reactions) = allowedReactionsAndFiles.reactions {
|
||||
for reaction in reactions {
|
||||
if existingIds.contains(reaction) {
|
||||
continue
|
||||
}
|
||||
existingIds.insert(reaction)
|
||||
|
||||
switch reaction {
|
||||
case .builtin:
|
||||
break
|
||||
case let .custom(fileId):
|
||||
if let file = allowedReactionsAndFiles.files[fileId] {
|
||||
result.append(ReactionItem(
|
||||
reaction: ReactionItem.Reaction(rawValue: .custom(file.fileId.id)),
|
||||
appearAnimation: file,
|
||||
stillAnimation: file,
|
||||
listAnimation: file,
|
||||
largeListAnimation: file,
|
||||
applicationAnimation: nil,
|
||||
largeApplicationAnimation: nil,
|
||||
isCustom: true
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user