mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +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.AllowedReactionsUpdated" = "%1$@ updated the list of allowed reactions to: %2$@";
|
||||||
"Channel.AdminLog.ReactionsDisabled" = "%1$@ disabled reactions";
|
"Channel.AdminLog.ReactionsDisabled" = "%1$@ disabled reactions";
|
||||||
|
"Channel.AdminLog.ReactionsEnabled" = "%1$@ enabled all reactions";
|
||||||
|
|
||||||
"Contacts.ScanQrCode" = "Scan QR Code";
|
"Contacts.ScanQrCode" = "Scan QR Code";
|
||||||
"Contacts.QrCode.MyCode" = "My QR Code";
|
"Contacts.QrCode.MyCode" = "My QR Code";
|
||||||
|
@ -23,6 +23,8 @@ swift_library(
|
|||||||
"//submodules/MeshAnimationCache:MeshAnimationCache",
|
"//submodules/MeshAnimationCache:MeshAnimationCache",
|
||||||
"//submodules/Utils/RangeSet:RangeSet",
|
"//submodules/Utils/RangeSet:RangeSet",
|
||||||
"//submodules/InAppPurchaseManager:InAppPurchaseManager",
|
"//submodules/InAppPurchaseManager:InAppPurchaseManager",
|
||||||
|
"//submodules/TelegramUI/Components/AnimationCache:AnimationCache",
|
||||||
|
"//submodules/TelegramUI/Components/MultiAnimationRenderer:MultiAnimationRenderer",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -12,6 +12,8 @@ import DeviceLocationManager
|
|||||||
import TemporaryCachedPeerDataManager
|
import TemporaryCachedPeerDataManager
|
||||||
import MeshAnimationCache
|
import MeshAnimationCache
|
||||||
import InAppPurchaseManager
|
import InAppPurchaseManager
|
||||||
|
import AnimationCache
|
||||||
|
import MultiAnimationRenderer
|
||||||
|
|
||||||
public final class TelegramApplicationOpenUrlCompletion {
|
public final class TelegramApplicationOpenUrlCompletion {
|
||||||
public let completion: (Bool) -> Void
|
public let completion: (Bool) -> Void
|
||||||
@ -885,6 +887,9 @@ public protocol AccountContext: AnyObject {
|
|||||||
var cachedGroupCallContexts: AccountGroupCallContextCache { get }
|
var cachedGroupCallContexts: AccountGroupCallContextCache { get }
|
||||||
var meshAnimationCache: MeshAnimationCache { get }
|
var meshAnimationCache: MeshAnimationCache { get }
|
||||||
|
|
||||||
|
var animationCache: AnimationCache { get }
|
||||||
|
var animationRenderer: MultiAnimationRenderer { get }
|
||||||
|
|
||||||
var animatedEmojiStickers: [String: [StickerPackItem]] { get }
|
var animatedEmojiStickers: [String: [StickerPackItem]] { get }
|
||||||
|
|
||||||
var userLimits: EngineConfiguration.UserLimits { get }
|
var userLimits: EngineConfiguration.UserLimits { get }
|
||||||
|
@ -36,6 +36,7 @@ swift_library(
|
|||||||
"//submodules/TelegramCore:TelegramCore",
|
"//submodules/TelegramCore:TelegramCore",
|
||||||
"//submodules/AnimationCompression:AnimationCompression",
|
"//submodules/AnimationCompression:AnimationCompression",
|
||||||
"//submodules/Components/MetalImageView:MetalImageView",
|
"//submodules/Components/MetalImageView:MetalImageView",
|
||||||
|
"//submodules/WebPBinding:WebPBinding",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -7,6 +7,7 @@ import CoreMedia
|
|||||||
import ManagedFile
|
import ManagedFile
|
||||||
import Accelerate
|
import Accelerate
|
||||||
import TelegramCore
|
import TelegramCore
|
||||||
|
import WebPBinding
|
||||||
|
|
||||||
private let sharedStoreQueue = Queue.concurrentDefaultQueue()
|
private let sharedStoreQueue = Queue.concurrentDefaultQueue()
|
||||||
|
|
||||||
@ -282,6 +283,7 @@ final class VideoStickerDirectFrameSource: AnimatedStickerFrameSource {
|
|||||||
private let width: Int
|
private let width: Int
|
||||||
private let height: Int
|
private let height: Int
|
||||||
private let cache: VideoStickerFrameSourceCache?
|
private let cache: VideoStickerFrameSourceCache?
|
||||||
|
private let image: UIImage?
|
||||||
private let bytesPerRow: Int
|
private let bytesPerRow: Int
|
||||||
var frameCount: Int
|
var frameCount: Int
|
||||||
let frameRate: Int
|
let frameRate: Int
|
||||||
@ -290,8 +292,12 @@ final class VideoStickerDirectFrameSource: AnimatedStickerFrameSource {
|
|||||||
private let source: SoftwareVideoSource?
|
private let source: SoftwareVideoSource?
|
||||||
|
|
||||||
var frameIndex: Int {
|
var frameIndex: Int {
|
||||||
|
if self.frameCount == 0 {
|
||||||
|
return 0
|
||||||
|
} else {
|
||||||
return self.currentFrame % self.frameCount
|
return self.currentFrame % self.frameCount
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
init?(queue: Queue, path: String, width: Int, height: Int, cachePathPrefix: String?, unpremultiplyAlpha: Bool = true) {
|
init?(queue: Queue, path: String, width: Int, height: Int, cachePathPrefix: String?, unpremultiplyAlpha: Bool = true) {
|
||||||
self.queue = queue
|
self.queue = queue
|
||||||
@ -307,11 +313,18 @@ final class VideoStickerDirectFrameSource: AnimatedStickerFrameSource {
|
|||||||
|
|
||||||
if useCache, let cache = self.cache, cache.frameCount > 0 {
|
if useCache, let cache = self.cache, cache.frameCount > 0 {
|
||||||
self.source = nil
|
self.source = nil
|
||||||
|
self.image = nil
|
||||||
self.frameRate = Int(cache.frameRate)
|
self.frameRate = Int(cache.frameRate)
|
||||||
self.frameCount = Int(cache.frameCount)
|
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 {
|
} else {
|
||||||
let source = SoftwareVideoSource(path: path, hintVP9: true, unpremultiplyAlpha: unpremultiplyAlpha)
|
let source = SoftwareVideoSource(path: path, hintVP9: true, unpremultiplyAlpha: unpremultiplyAlpha)
|
||||||
self.source = source
|
self.source = source
|
||||||
|
self.image = nil
|
||||||
self.frameRate = min(30, source.getFramerate())
|
self.frameRate = min(30, source.getFramerate())
|
||||||
self.frameCount = 0
|
self.frameCount = 0
|
||||||
}
|
}
|
||||||
@ -331,7 +344,15 @@ final class VideoStickerDirectFrameSource: AnimatedStickerFrameSource {
|
|||||||
|
|
||||||
self.currentFrame += 1
|
self.currentFrame += 1
|
||||||
if draw {
|
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)
|
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 {
|
} else if let source = self.source {
|
||||||
let frameAndLoop = source.readFrame(maxPts: nil)
|
let frameAndLoop = source.readFrame(maxPts: nil)
|
||||||
|
@ -336,10 +336,8 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
|
|||||||
self.presentController = presentController
|
self.presentController = presentController
|
||||||
self.makeEntityInputView = makeEntityInputView
|
self.makeEntityInputView = makeEntityInputView
|
||||||
|
|
||||||
self.animationCache = AnimationCacheImpl(basePath: context.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: {
|
self.animationCache = context.animationCache
|
||||||
return TempBox.shared.tempFile(fileName: "file").path
|
self.animationRenderer = context.animationRenderer
|
||||||
})
|
|
||||||
self.animationRenderer = MultiAnimationRendererImpl()
|
|
||||||
|
|
||||||
var hasSpoilers = true
|
var hasSpoilers = true
|
||||||
if presentationInterfaceState.chatLocation.peerId?.namespace == Namespaces.Peer.SecretChat {
|
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.presentationData = (context.sharedContext.currentPresentationData.with { $0 })
|
||||||
self.presentationDataValue.set(.single(self.presentationData))
|
self.presentationDataValue.set(.single(self.presentationData))
|
||||||
|
|
||||||
self.animationCache = AnimationCacheImpl(basePath: context.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: {
|
self.animationCache = context.animationCache
|
||||||
return TempBox.shared.tempFile(fileName: "file").path
|
self.animationRenderer = context.animationRenderer
|
||||||
})
|
|
||||||
self.animationRenderer = MultiAnimationRendererImpl()
|
|
||||||
|
|
||||||
self.titleView = ChatListTitleView(
|
self.titleView = ChatListTitleView(
|
||||||
context: context,
|
context: context,
|
||||||
@ -327,7 +325,11 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
return (data.isLockable, false)
|
return (data.isLockable, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
let peerStatus: Signal<NetworkStatusTitle.Status?, NoError> = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|
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
|
|> map { peer -> NetworkStatusTitle.Status? in
|
||||||
guard case let .user(user) = peer else {
|
guard case let .user(user) = peer else {
|
||||||
return nil
|
return nil
|
||||||
@ -341,6 +343,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|> distinctUntilChanged
|
|> distinctUntilChanged
|
||||||
|
}
|
||||||
|
|
||||||
let previousEditingAndNetworkStateValue = Atomic<(Bool, AccountNetworkState)?>(value: nil)
|
let previousEditingAndNetworkStateValue = Atomic<(Bool, AccountNetworkState)?>(value: nil)
|
||||||
if !self.hideNetworkActivityStatus {
|
if !self.hideNetworkActivityStatus {
|
||||||
@ -858,7 +861,10 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
areUnicodeEmojiEnabled: false,
|
areUnicodeEmojiEnabled: false,
|
||||||
areCustomEmojiEnabled: true,
|
areCustomEmojiEnabled: true,
|
||||||
chatPeerId: self.context.account.peerId
|
chatPeerId: self.context.account.peerId
|
||||||
)
|
),
|
||||||
|
destinationItemView: { [weak sourceView] in
|
||||||
|
return sourceView
|
||||||
|
}
|
||||||
), in: .window(.root))
|
), in: .window(.root))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -384,6 +384,7 @@ final class ChatListTitleView: UIView, NavigationBarTitleView, NavigationBarTitl
|
|||||||
containerSize: CGSize(width: 22.0, height: 22.0)
|
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))
|
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 {
|
} else {
|
||||||
if let titleCredibilityIconView = self.titleCredibilityIconView {
|
if let titleCredibilityIconView = self.titleCredibilityIconView {
|
||||||
self.titleCredibilityIconView = nil
|
self.titleCredibilityIconView = nil
|
||||||
|
@ -607,10 +607,6 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*for _ in 0 ..< 5 {
|
|
||||||
mergedItems.append(contentsOf: mergedItems)
|
|
||||||
}*/
|
|
||||||
|
|
||||||
self.mergedItems = mergedItems
|
self.mergedItems = mergedItems
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -974,7 +974,10 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
|||||||
strongSelf.statusNode.frame = statusFrame
|
strongSelf.statusNode.frame = statusFrame
|
||||||
transition.animatePositionAdditive(node: strongSelf.statusNode, offset: CGPoint(x: previousStatusFrame.minX - statusFrame.minX, y: 0))
|
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>
|
let credibilityIconView: ComponentHostView<Empty>
|
||||||
if let current = strongSelf.credibilityIconView {
|
if let current = strongSelf.credibilityIconView {
|
||||||
credibilityIconView = current
|
credibilityIconView = current
|
||||||
|
@ -576,12 +576,15 @@ final class InnerTextSelectionTipContainerNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.highlightBackgroundNode.backgroundColor = presentationData.theme.contextMenu.itemHighlightedBackgroundColor
|
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
|
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) {
|
func updateTheme(presentationData: PresentationData) {
|
||||||
self.backgroundColor = presentationData.theme.contextMenu.backgroundColor
|
self.backgroundColor = presentationData.theme.contextMenu.backgroundColor
|
||||||
}
|
}
|
||||||
@ -802,6 +805,7 @@ final class ContextActionsContainerNode: ASDisplayNode {
|
|||||||
contentSize.height += 8.0
|
contentSize.height += 8.0
|
||||||
let textSelectionTipSize = textSelectionTipNode.updateLayout(widthClass: widthClass, width: actionsSize.width, transition: transition)
|
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))
|
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
|
contentSize.height += textSelectionTipSize.height
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -963,7 +963,7 @@ final class ContextControllerActionsStackNode: ASDisplayNode {
|
|||||||
return (size, apparentHeight)
|
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 {
|
if let tip = self.tip {
|
||||||
var updatedTransition = transition
|
var updatedTransition = transition
|
||||||
if let tipNode = self.tipNode, tipNode.tip == tip {
|
if let tipNode = self.tipNode, tipNode.tip == tip {
|
||||||
@ -1172,7 +1172,7 @@ final class ContextControllerActionsStackNode: ASDisplayNode {
|
|||||||
let animateAppearingContainers = transition.isAnimated && !self.dismissingItemContainers.isEmpty
|
let animateAppearingContainers = transition.isAnimated && !self.dismissingItemContainers.isEmpty
|
||||||
|
|
||||||
struct TipLayout {
|
struct TipLayout {
|
||||||
var tipNode: ASDisplayNode
|
var tipNode: InnerTextSelectionTipContainerNode
|
||||||
var tipHeight: CGFloat
|
var tipHeight: CGFloat
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1320,11 +1320,13 @@ final class ContextControllerActionsStackNode: ASDisplayNode {
|
|||||||
self.addSubnode(tip.tipNode)
|
self.addSubnode(tip.tipNode)
|
||||||
animateTipIn = transition.isAnimated
|
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.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
|
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)
|
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 {
|
if animateTipIn {
|
||||||
tip.tipNode.alpha = tipAlpha
|
tip.tipNode.alpha = tipAlpha
|
||||||
|
@ -671,7 +671,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
|||||||
reactionContextNodeTransition = .immediate
|
reactionContextNodeTransition = .immediate
|
||||||
}
|
}
|
||||||
reactionContextNodeTransition.updateFrame(node: reactionContextNode, frame: CGRect(origin: CGPoint(), size: layout.size), beginWithCurrentState: true)
|
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
|
self.proposedReactionsPositionLock = contentRect.minY - 18.0 - reactionContextNode.contentHeight - 46.0
|
||||||
} else {
|
} else {
|
||||||
|
@ -356,10 +356,8 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
|||||||
self.statusNode = RadialStatusNode(backgroundNodeColor: .clear)
|
self.statusNode = RadialStatusNode(backgroundNodeColor: .clear)
|
||||||
self.statusNode.isUserInteractionEnabled = false
|
self.statusNode.isUserInteractionEnabled = false
|
||||||
|
|
||||||
self.animationCache = AnimationCacheImpl(basePath: context.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: {
|
self.animationCache = context.animationCache
|
||||||
return TempBox.shared.tempFile(fileName: "file").path
|
self.animationRenderer = context.animationRenderer
|
||||||
})
|
|
||||||
self.animationRenderer = MultiAnimationRendererImpl()
|
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
|
@ -36,10 +36,8 @@ public final class HashtagSearchController: TelegramBaseController {
|
|||||||
self.peer = peer
|
self.peer = peer
|
||||||
self.query = query
|
self.query = query
|
||||||
|
|
||||||
self.animationCache = AnimationCacheImpl(basePath: context.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: {
|
self.animationCache = context.animationCache
|
||||||
return TempBox.shared.tempFile(fileName: "file").path
|
self.animationRenderer = context.animationRenderer
|
||||||
})
|
|
||||||
self.animationRenderer = MultiAnimationRendererImpl()
|
|
||||||
|
|
||||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
|
||||||
|
@ -23,6 +23,8 @@ swift_library(
|
|||||||
"//submodules/ContextUI:ContextUI",
|
"//submodules/ContextUI:ContextUI",
|
||||||
"//submodules/PresentationDataUtils:PresentationDataUtils",
|
"//submodules/PresentationDataUtils:PresentationDataUtils",
|
||||||
"//submodules/AccountContext:AccountContext",
|
"//submodules/AccountContext:AccountContext",
|
||||||
|
"//submodules/ComponentFlow:ComponentFlow",
|
||||||
|
"//submodules/TelegramUI/Components/EmojiStatusComponent:EmojiStatusComponent",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -13,6 +13,8 @@ import TelegramStringFormatting
|
|||||||
import PeerPresenceStatusManager
|
import PeerPresenceStatusManager
|
||||||
import ContextUI
|
import ContextUI
|
||||||
import AccountContext
|
import AccountContext
|
||||||
|
import ComponentFlow
|
||||||
|
import EmojiStatusComponent
|
||||||
|
|
||||||
private final class ShimmerEffectNode: ASDisplayNode {
|
private final class ShimmerEffectNode: ASDisplayNode {
|
||||||
private var currentBackgroundColor: UIColor?
|
private var currentBackgroundColor: UIColor?
|
||||||
@ -460,7 +462,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
|
|||||||
private let labelBadgeNode: ASImageNode
|
private let labelBadgeNode: ASImageNode
|
||||||
private var labelArrowNode: ASImageNode?
|
private var labelArrowNode: ASImageNode?
|
||||||
private let statusNode: TextNode
|
private let statusNode: TextNode
|
||||||
private var credibilityIconNode: ASImageNode?
|
private var credibilityIconView: ComponentHostView<Empty>?
|
||||||
private var switchNode: SwitchNode?
|
private var switchNode: SwitchNode?
|
||||||
private var checkNode: ASImageNode?
|
private var checkNode: ASImageNode?
|
||||||
|
|
||||||
@ -601,27 +603,34 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
|
|||||||
let labelDisclosureFont = Font.regular(item.presentationData.fontSize.itemListBaseFontSize)
|
let labelDisclosureFont = Font.regular(item.presentationData.fontSize.itemListBaseFontSize)
|
||||||
|
|
||||||
var updatedLabelBadgeImage: UIImage?
|
var updatedLabelBadgeImage: UIImage?
|
||||||
var currentCredibilityIconImage: UIImage?
|
var credibilityIcon: EmojiStatusComponent.Content?
|
||||||
|
|
||||||
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: item.context.currentAppConfiguration.with { $0 })
|
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: item.context.currentAppConfiguration.with { $0 })
|
||||||
|
|
||||||
if case .threatSelfAsSaved = item.aliasHandling, item.peer.id == item.context.account.peerId {
|
if case .threatSelfAsSaved = item.aliasHandling, item.peer.id == item.context.account.peerId {
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if item.peer.isScam {
|
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 {
|
} 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 {
|
} 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 {
|
} 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
|
var titleIconsWidth: CGFloat = 0.0
|
||||||
if let currentCredibilityIconImage = currentCredibilityIconImage {
|
if let credibilityIcon = credibilityIcon {
|
||||||
titleIconsWidth += 4.0 + currentCredibilityIconImage.size.width
|
titleIconsWidth += 4.0
|
||||||
|
switch credibilityIcon {
|
||||||
|
case .scam, .fake:
|
||||||
|
titleIconsWidth += 30.0
|
||||||
|
default:
|
||||||
|
titleIconsWidth += 16.0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var badgeColor: UIColor?
|
var badgeColor: UIColor?
|
||||||
@ -1063,23 +1072,38 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
|
|||||||
transition.updateFrame(node: strongSelf.titleNode, frame: titleFrame)
|
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))
|
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 {
|
if let credibilityIcon = credibilityIcon {
|
||||||
let iconNode: ASImageNode
|
let animationCache = item.context.animationCache
|
||||||
if let current = strongSelf.credibilityIconNode {
|
let animationRenderer = item.context.animationRenderer
|
||||||
iconNode = current
|
|
||||||
|
let credibilityIconView: ComponentHostView<Empty>
|
||||||
|
if let current = strongSelf.credibilityIconView {
|
||||||
|
credibilityIconView = current
|
||||||
} else {
|
} else {
|
||||||
iconNode = ASImageNode()
|
credibilityIconView = ComponentHostView<Empty>()
|
||||||
iconNode.isLayerBacked = true
|
strongSelf.containerNode.view.addSubview(credibilityIconView)
|
||||||
iconNode.displaysAsynchronously = false
|
strongSelf.credibilityIconView = credibilityIconView
|
||||||
iconNode.displayWithoutProcessing = true
|
|
||||||
strongSelf.containerNode.addSubnode(iconNode)
|
|
||||||
strongSelf.credibilityIconNode = iconNode
|
|
||||||
}
|
}
|
||||||
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))
|
let iconSize = credibilityIconView.update(
|
||||||
} else if let credibilityIconNode = strongSelf.credibilityIconNode {
|
transition: .immediate,
|
||||||
strongSelf.credibilityIconNode = nil
|
component: AnyComponent(EmojiStatusComponent(
|
||||||
credibilityIconNode.removeFromSupernode()
|
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 {
|
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.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))
|
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 {
|
if let credibilityIconView = self.credibilityIconView {
|
||||||
transition.updateFrame(node: credibilityIconNode, frame: CGRect(origin: CGPoint(x: self.titleNode.frame.maxX + 4.0, y: credibilityIconNode.frame.minY), size: credibilityIconNode.bounds.size))
|
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
|
var rightLabelInset: CGFloat = 15.0 + params.rightInset
|
||||||
|
@ -1667,10 +1667,8 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
|||||||
self.present = present
|
self.present = present
|
||||||
self.completion = completion
|
self.completion = completion
|
||||||
|
|
||||||
self.animationCache = AnimationCacheImpl(basePath: context.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: {
|
self.animationCache = context.animationCache
|
||||||
return TempBox.shared.tempFile(fileName: "file").path
|
self.animationRenderer = context.animationRenderer
|
||||||
})
|
|
||||||
self.animationRenderer = MultiAnimationRendererImpl()
|
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
|
@ -127,10 +127,8 @@ private class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
self.context = context
|
self.context = context
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
|
|
||||||
self.animationCache = AnimationCacheImpl(basePath: self.context.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: {
|
self.animationCache = context.animationCache
|
||||||
return TempBox.shared.tempFile(fileName: "file").path
|
self.animationRenderer = context.animationRenderer
|
||||||
})
|
|
||||||
self.animationRenderer = MultiAnimationRendererImpl()
|
|
||||||
|
|
||||||
var reactionMap: [MessageReaction.Reaction: AvailableReactions.Reaction] = [:]
|
var reactionMap: [MessageReaction.Reaction: AvailableReactions.Reaction] = [:]
|
||||||
for reaction in reactions {
|
for reaction in reactions {
|
||||||
@ -362,7 +360,7 @@ private class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
applicationAnimation: aroundAnimation,
|
applicationAnimation: aroundAnimation,
|
||||||
largeApplicationAnimation: reaction.effectAnimation,
|
largeApplicationAnimation: reaction.effectAnimation,
|
||||||
isCustom: false
|
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.isUserInteractionEnabled = false
|
||||||
containerNode.addSubnode(itemNode)
|
containerNode.addSubnode(itemNode)
|
||||||
self.addSubnode(containerNode)
|
self.addSubnode(containerNode)
|
||||||
|
@ -193,6 +193,9 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
|
|
||||||
private var animateInInfo: (centerX: CGFloat, width: CGFloat)?
|
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) {
|
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.context = context
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
@ -294,10 +297,20 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
self.view.addGestureRecognizer(horizontalExpandRecognizer)
|
self.view.addGestureRecognizer(horizontalExpandRecognizer)
|
||||||
self.horizontalExpandRecognizer = 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 {
|
deinit {
|
||||||
self.emojiContentDisposable?.dispose()
|
self.emojiContentDisposable?.dispose()
|
||||||
|
self.availableReactionsDisposable?.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
override public func didLoad() {
|
override public func didLoad() {
|
||||||
@ -371,7 +384,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
contentSize.width = max(46.0, contentSize.width)
|
contentSize.width = max(46.0, contentSize.width)
|
||||||
contentSize.height = self.currentContentHeight
|
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)
|
let backgroundOffset: CGPoint = CGPoint(x: 22.0, y: -7.0)
|
||||||
|
|
||||||
var rect: CGRect
|
var rect: CGRect
|
||||||
@ -459,14 +472,23 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
|
|
||||||
var topVisibleItems: Int
|
var topVisibleItems: Int
|
||||||
if self.getEmojiContent != nil {
|
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 {
|
} else {
|
||||||
topVisibleItems = self.items.count
|
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 validIndices = Set<Int>()
|
||||||
var nextX: CGFloat = sideInset
|
var nextX: CGFloat = sideInset
|
||||||
for i in 0 ..< self.items.count {
|
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)) {
|
if appearBounds.intersects(baseItemFrame) || (self.visibleItemNodes[i] != nil && visibleBounds.intersects(baseItemFrame)) {
|
||||||
validIndices.insert(i)
|
validIndices.insert(i)
|
||||||
|
|
||||||
let itemFrame = baseItemFrame
|
var itemFrame = baseItemFrame
|
||||||
|
|
||||||
var isPreviewing = false
|
var isPreviewing = false
|
||||||
if let highlightedReaction = self.highlightedReaction, highlightedReaction == self.items[i].reaction {
|
if let highlightedReaction = self.highlightedReaction, highlightedReaction == self.items[i].reaction {
|
||||||
@ -540,7 +562,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
itemTransition = .immediate
|
itemTransition = .immediate
|
||||||
|
|
||||||
if case let .reaction(item) = self.items[i] {
|
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
|
maskNode = nil
|
||||||
} else {
|
} else {
|
||||||
itemNode = PremiumReactionsNode(theme: self.presentationData.theme)
|
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
|
itemTransition.updateFrame(node: itemNode, frame: itemFrame, beginWithCurrentState: true, completion: { [weak self, weak itemNode] completed in
|
||||||
guard let strongSelf = self, let itemNode = itemNode else {
|
guard let strongSelf = self, let itemNode = itemNode else {
|
||||||
return
|
return
|
||||||
@ -588,6 +617,10 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
if animateIn {
|
if animateIn {
|
||||||
itemNode.appear(animated: !self.context.sharedContext.currentPresentationData.with({ $0 }).reduceMotion)
|
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,27 +940,46 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
|
|
||||||
emojiContent.inputInteractionHolder.inputInteraction = EmojiPagerContentComponent.InputInteraction(
|
emojiContent.inputInteractionHolder.inputInteraction = EmojiPagerContentComponent.InputInteraction(
|
||||||
performItemAction: { [weak self] groupId, item, sourceView, sourceRect, sourceLayer, isLongPress in
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
strongSelf.didTriggerExpandedReaction = isLongPress
|
strongSelf.didTriggerExpandedReaction = isLongPress
|
||||||
|
|
||||||
var found = false
|
var found = false
|
||||||
if let groupId = groupId.base as? String, groupId == "recent" {
|
for reaction in availableReactions.reactions {
|
||||||
for reactionItem in strongSelf.items {
|
guard let centerAnimation = reaction.centerAnimation, let aroundAnimation = reaction.aroundAnimation else {
|
||||||
if case let .reaction(reactionItem) = reactionItem {
|
continue
|
||||||
if reactionItem.stillAnimation.fileId == itemFile.fileId {
|
}
|
||||||
|
|
||||||
|
if reaction.selectAnimation.fileId == itemFile.fileId {
|
||||||
found = true
|
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.customReactionSource = (sourceView, sourceRect, sourceLayer, reactionItem)
|
||||||
strongSelf.reactionSelected?(reactionItem.updateMessageReaction, isLongPress)
|
strongSelf.reactionSelected?(updateReaction, isLongPress)
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
if !found {
|
||||||
let reactionItem = ReactionItem(
|
let reactionItem = ReactionItem(
|
||||||
reaction: ReactionItem.Reaction(rawValue: .custom(itemFile.fileId.id)),
|
reaction: ReactionItem.Reaction(rawValue: .custom(itemFile.fileId.id)),
|
||||||
@ -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
|
pushController: { _ in
|
||||||
},
|
},
|
||||||
@ -1159,7 +1230,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let customReactionSource = self.customReactionSource {
|
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 {
|
if let contents = customReactionSource.layer.contents {
|
||||||
itemNode.setCustomContents(contents: contents)
|
itemNode.setCustomContents(contents: contents)
|
||||||
}
|
}
|
||||||
@ -1176,7 +1247,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let switchToInlineImmediately: Bool
|
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 {
|
switch itemNode.item.reaction.rawValue {
|
||||||
case .builtin:
|
case .builtin:
|
||||||
switchToInlineImmediately = false
|
switchToInlineImmediately = false
|
||||||
@ -1196,11 +1267,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
targetView.isHidden = true
|
targetView.isHidden = true
|
||||||
} else {
|
} else {
|
||||||
targetView.alpha = 0.0
|
targetView.alpha = 0.0
|
||||||
targetView.layer.animateAlpha(from: targetView.alpha, to: 0.0, duration: 0.2, completion: { [weak targetView] completed in
|
targetView.layer.animateAlpha(from: targetView.alpha, to: 0.0, duration: 0.2)
|
||||||
if completed {
|
|
||||||
targetView?.isHidden = true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1216,7 +1283,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
|
|
||||||
var expandedSize: CGSize = selfTargetRect.size
|
var expandedSize: CGSize = selfTargetRect.size
|
||||||
if self.didTriggerExpandedReaction {
|
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)
|
expandedSize = CGSize(width: 80.0, height: 80.0)
|
||||||
} else {
|
} else {
|
||||||
expandedSize = CGSize(width: 120.0, height: 120.0)
|
expandedSize = CGSize(width: 120.0, height: 120.0)
|
||||||
@ -1267,7 +1334,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
} else if itemNode.item.isCustom {
|
} else if itemNode.item.isCustom {
|
||||||
additionalAnimationNode = nil
|
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))
|
let view = AnimationView(animation: composition, configuration: LottieConfiguration(renderingEngine: .mainThread, decodingStrategy: .codable))
|
||||||
view.animationSpeed = 1.0
|
view.animationSpeed = 1.0
|
||||||
view.backgroundColor = nil
|
view.backgroundColor = nil
|
||||||
@ -1279,12 +1346,11 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
|
|
||||||
genericAnimationView = view
|
genericAnimationView = view
|
||||||
|
|
||||||
let animationCache = AnimationCacheImpl(basePath: itemNode.context.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: {
|
let animationCache = itemNode.context.animationCache
|
||||||
return TempBox.shared.tempFile(fileName: "file").path
|
let animationRenderer = itemNode.context.animationRenderer
|
||||||
})
|
|
||||||
let animationRenderer = MultiAnimationRendererImpl()
|
|
||||||
|
|
||||||
let allLayers = view.allLayers(forKeypath: AnimationKeypath(keypath: "BODY 1 Precomp"))
|
for i in 1 ... 32 {
|
||||||
|
let allLayers = view.allLayers(forKeypath: AnimationKeypath(keypath: "placeholder_\(i)"))
|
||||||
for animationLayer in allLayers {
|
for animationLayer in allLayers {
|
||||||
let baseItemLayer = InlineStickerItemLayer(
|
let baseItemLayer = InlineStickerItemLayer(
|
||||||
context: itemNode.context,
|
context: itemNode.context,
|
||||||
@ -1307,8 +1373,13 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
baseItemLayer.frame = CGRect(origin: CGPoint(x: -0.0, y: -0.0), size: CGSize(width: 500.0, height: 500.0))
|
baseItemLayer.frame = CGRect(origin: CGPoint(x: -0.0, y: -0.0), size: CGSize(width: 500.0, height: 500.0))
|
||||||
animationLayer.addSublayer(baseItemLayer)
|
animationLayer.addSublayer(baseItemLayer)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.didTriggerExpandedReaction {
|
||||||
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: -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)
|
self.view.addSubview(view)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -1348,14 +1419,19 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
guard let itemNode = itemNode else {
|
guard let itemNode = itemNode else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard let targetView = targetView as? ReactionIconView else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if let animateTargetContainer = animateTargetContainer {
|
if let animateTargetContainer = animateTargetContainer {
|
||||||
animateTargetContainer.isHidden = false
|
animateTargetContainer.isHidden = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let targetView = targetView {
|
||||||
targetView.isHidden = false
|
targetView.isHidden = false
|
||||||
targetView.alpha = 1.0
|
targetView.alpha = 1.0
|
||||||
|
targetView.layer.removeAnimation(forKey: "opacity")
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let targetView = targetView as? ReactionIconView else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if switchToInlineImmediately {
|
if switchToInlineImmediately {
|
||||||
targetView.updateIsAnimationHidden(isAnimationHidden: false, transition: .immediate)
|
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) {
|
if itemNode.supernode === self.scrollNode && !self.scrollNode.bounds.intersects(itemNode.frame) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if !itemNode.isUserInteractionEnabled {
|
||||||
|
continue
|
||||||
|
}
|
||||||
let itemPoint = self.view.convert(point, to: itemNode.view)
|
let itemPoint = self.view.convert(point, to: itemNode.view)
|
||||||
if itemNode.bounds.insetBy(dx: -touchInset, dy: -touchInset).contains(itemPoint) {
|
if itemNode.bounds.insetBy(dx: -touchInset, dy: -touchInset).contains(itemPoint) {
|
||||||
return itemNode
|
return itemNode
|
||||||
@ -1704,7 +1783,7 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
|
|||||||
itemNode = currentItemNode
|
itemNode = currentItemNode
|
||||||
} else {
|
} else {
|
||||||
let animationRenderer = MultiAnimationRendererImpl()
|
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
|
self.itemNode = itemNode
|
||||||
|
|
||||||
@ -1812,7 +1891,7 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
|
|||||||
} else if itemNode.item.isCustom {
|
} else if itemNode.item.isCustom {
|
||||||
additionalAnimationNode = nil
|
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))
|
let view = AnimationView(animation: composition, configuration: LottieConfiguration(renderingEngine: .mainThread, decodingStrategy: .codable))
|
||||||
view.animationSpeed = 1.0
|
view.animationSpeed = 1.0
|
||||||
view.backgroundColor = nil
|
view.backgroundColor = nil
|
||||||
@ -1824,12 +1903,11 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
|
|||||||
|
|
||||||
genericAnimationView = view
|
genericAnimationView = view
|
||||||
|
|
||||||
let animationCache = AnimationCacheImpl(basePath: itemNode.context.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: {
|
let animationCache = itemNode.context.animationCache
|
||||||
return TempBox.shared.tempFile(fileName: "file").path
|
let animationRenderer = itemNode.context.animationRenderer
|
||||||
})
|
|
||||||
let animationRenderer = MultiAnimationRendererImpl()
|
|
||||||
|
|
||||||
let allLayers = view.allLayers(forKeypath: AnimationKeypath(keypath: "BODY 1 Precomp"))
|
for i in 1 ... 7 {
|
||||||
|
let allLayers = view.allLayers(forKeypath: AnimationKeypath(keypath: "placeholder_\(i)"))
|
||||||
for animationLayer in allLayers {
|
for animationLayer in allLayers {
|
||||||
let baseItemLayer = InlineStickerItemLayer(
|
let baseItemLayer = InlineStickerItemLayer(
|
||||||
context: itemNode.context,
|
context: itemNode.context,
|
||||||
@ -1852,8 +1930,9 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
|
|||||||
baseItemLayer.frame = CGRect(origin: CGPoint(x: -0.0, y: -0.0), size: CGSize(width: 500.0, height: 500.0))
|
baseItemLayer.frame = CGRect(origin: CGPoint(x: -0.0, y: -0.0), size: CGSize(width: 500.0, height: 500.0))
|
||||||
animationLayer.addSublayer(baseItemLayer)
|
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)
|
self.view.addSubview(view)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -49,6 +49,7 @@ protocol ReactionItemNode: ASDisplayNode {
|
|||||||
public final class ReactionNode: ASDisplayNode, ReactionItemNode {
|
public final class ReactionNode: ASDisplayNode, ReactionItemNode {
|
||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
let item: ReactionItem
|
let item: ReactionItem
|
||||||
|
private let loopIdle: Bool
|
||||||
private let hasAppearAnimation: Bool
|
private let hasAppearAnimation: Bool
|
||||||
private let useDirectRendering: Bool
|
private let useDirectRendering: Bool
|
||||||
|
|
||||||
@ -79,9 +80,10 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode {
|
|||||||
return self.staticAnimationNode.currentFrameImage
|
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.context = context
|
||||||
self.item = item
|
self.item = item
|
||||||
|
self.loopIdle = loopIdle
|
||||||
self.hasAppearAnimation = hasAppearAnimation
|
self.hasAppearAnimation = hasAppearAnimation
|
||||||
self.useDirectRendering = useDirectRendering
|
self.useDirectRendering = useDirectRendering
|
||||||
|
|
||||||
@ -105,8 +107,10 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode {
|
|||||||
}
|
}
|
||||||
if strongSelf.animationNode == nil {
|
if strongSelf.animationNode == nil {
|
||||||
strongSelf.staticAnimationNode.isHidden = false
|
strongSelf.staticAnimationNode.isHidden = false
|
||||||
|
if strongSelf.loopIdle {
|
||||||
strongSelf.staticAnimationNode.playLoop()
|
strongSelf.staticAnimationNode.playLoop()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
strongSelf.animateInAnimationNode?.removeFromSupernode()
|
strongSelf.animateInAnimationNode?.removeFromSupernode()
|
||||||
strongSelf.animateInAnimationNode = nil
|
strongSelf.animateInAnimationNode = nil
|
||||||
@ -133,6 +137,13 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode {
|
|||||||
if animated {
|
if animated {
|
||||||
if self.item.isCustom {
|
if self.item.isCustom {
|
||||||
self.layer.animateSpring(from: 0.01 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4)
|
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 {
|
} else {
|
||||||
self.animateInAnimationNode?.visibility = true
|
self.animateInAnimationNode?.visibility = true
|
||||||
}
|
}
|
||||||
@ -184,11 +195,11 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if largeExpanded {
|
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)))
|
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 {
|
} 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.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
|
animationNode.frame = expandedAnimationFrame
|
||||||
@ -240,7 +251,7 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode {
|
|||||||
transition.animateTransformScale(node: customContentsNode, from: customContentsNode.bounds.width / animationFrame.width)
|
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))
|
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.alpha = 0.0
|
||||||
customContentsNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2)
|
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.stillAnimationNode = stillAnimationNode
|
||||||
self.addSubnode(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.position = animationFrame.center
|
||||||
stillAnimationNode.bounds = CGRect(origin: CGPoint(), size: animationFrame.size)
|
stillAnimationNode.bounds = CGRect(origin: CGPoint(), size: animationFrame.size)
|
||||||
stillAnimationNode.updateLayout(size: animationFrame.size)
|
stillAnimationNode.updateLayout(size: animationFrame.size)
|
||||||
@ -288,9 +299,11 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode {
|
|||||||
animateInAnimationNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1)
|
animateInAnimationNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1)
|
||||||
|
|
||||||
strongSelf.staticAnimationNode.isHidden = false
|
strongSelf.staticAnimationNode.isHidden = false
|
||||||
|
if strongSelf.loopIdle {
|
||||||
strongSelf.staticAnimationNode.playLoop()
|
strongSelf.staticAnimationNode.playLoop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
stillAnimationNode.visibility = true
|
stillAnimationNode.visibility = true
|
||||||
|
|
||||||
transition.animateTransformScale(node: stillAnimationNode, from: self.staticAnimationNode.bounds.width / animationFrame.width)
|
transition.animateTransformScale(node: stillAnimationNode, from: self.staticAnimationNode.bounds.width / animationFrame.width)
|
||||||
@ -329,9 +342,9 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode {
|
|||||||
|
|
||||||
self.staticAnimationNode.automaticallyLoadFirstFrame = true
|
self.staticAnimationNode.automaticallyLoadFirstFrame = true
|
||||||
if !self.hasAppearAnimation {
|
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 {
|
} 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.position = animationFrame.center
|
||||||
self.staticAnimationNode.bounds = CGRect(origin: CGPoint(), size: animationFrame.size)
|
self.staticAnimationNode.bounds = CGRect(origin: CGPoint(), size: animationFrame.size)
|
||||||
@ -339,7 +352,7 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode {
|
|||||||
self.staticAnimationNode.visibility = true
|
self.staticAnimationNode.visibility = true
|
||||||
|
|
||||||
if let animateInAnimationNode = self.animateInAnimationNode {
|
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.position = animationFrame.center
|
||||||
animateInAnimationNode.bounds = CGRect(origin: CGPoint(), size: animationFrame.size)
|
animateInAnimationNode.bounds = CGRect(origin: CGPoint(), size: animationFrame.size)
|
||||||
animateInAnimationNode.updateLayout(size: animationFrame.size)
|
animateInAnimationNode.updateLayout(size: animationFrame.size)
|
||||||
|
@ -169,15 +169,7 @@ class ReactionChatPreviewItemNode: ListViewItemNode {
|
|||||||
let standaloneReactionAnimation = StandaloneReactionAnimation()
|
let standaloneReactionAnimation = StandaloneReactionAnimation()
|
||||||
self.standaloneReactionAnimation = standaloneReactionAnimation
|
self.standaloneReactionAnimation = standaloneReactionAnimation
|
||||||
|
|
||||||
let animationCache: AnimationCache
|
let animationCache = item.context.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
|
|
||||||
}
|
|
||||||
|
|
||||||
supernode.addSubnode(standaloneReactionAnimation)
|
supernode.addSubnode(standaloneReactionAnimation)
|
||||||
standaloneReactionAnimation.frame = supernode.bounds
|
standaloneReactionAnimation.frame = supernode.bounds
|
||||||
|
@ -63,10 +63,8 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView
|
|||||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
self.presentationThemeSettings = presentationThemeSettings
|
self.presentationThemeSettings = presentationThemeSettings
|
||||||
|
|
||||||
self.animationCache = AnimationCacheImpl(basePath: context.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: {
|
self.animationCache = context.animationCache
|
||||||
return TempBox.shared.tempFile(fileName: "file").path
|
self.animationRenderer = context.animationRenderer
|
||||||
})
|
|
||||||
self.animationRenderer = MultiAnimationRendererImpl()
|
|
||||||
|
|
||||||
let calendar = Calendar(identifier: .gregorian)
|
let calendar = Calendar(identifier: .gregorian)
|
||||||
var components = calendar.dateComponents(Set([.era, .year, .month, .day, .hour, .minute, .second]), from: Date())
|
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
|
self.wallpaper = self.presentationData.chatWallpaper
|
||||||
let bubbleCorners = self.presentationData.chatBubbleCorners
|
let bubbleCorners = self.presentationData.chatBubbleCorners
|
||||||
|
|
||||||
self.animationCache = AnimationCacheImpl(basePath: context.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: {
|
self.animationCache = context.animationCache
|
||||||
return TempBox.shared.tempFile(fileName: "file").path
|
self.animationRenderer = context.animationRenderer
|
||||||
})
|
|
||||||
self.animationRenderer = MultiAnimationRendererImpl()
|
|
||||||
|
|
||||||
self.ready = ready
|
self.ready = ready
|
||||||
|
|
||||||
|
@ -88,10 +88,8 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
|
|
||||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
|
||||||
self.animationCache = AnimationCacheImpl(basePath: context.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: {
|
self.animationCache = context.animationCache
|
||||||
return TempBox.shared.tempFile(fileName: "file").path
|
self.animationRenderer = context.animationRenderer
|
||||||
})
|
|
||||||
self.animationRenderer = MultiAnimationRendererImpl()
|
|
||||||
|
|
||||||
let calendar = Calendar(identifier: .gregorian)
|
let calendar = Calendar(identifier: .gregorian)
|
||||||
var components = calendar.dateComponents(Set([.era, .year, .month, .day, .hour, .minute, .second]), from: Date())
|
var components = calendar.dateComponents(Set([.era, .year, .month, .day, .hour, .minute, .second]), from: Date())
|
||||||
|
@ -1548,18 +1548,8 @@ public final class StickerPackScreenImpl: ViewController {
|
|||||||
self.sendSticker = sendSticker
|
self.sendSticker = sendSticker
|
||||||
self.actionPerformed = actionPerformed
|
self.actionPerformed = actionPerformed
|
||||||
|
|
||||||
self.animationCache = AnimationCacheImpl(basePath: context.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: {
|
self.animationCache = context.animationCache
|
||||||
return TempBox.shared.tempFile(fileName: "file").path
|
self.animationRenderer = context.animationRenderer
|
||||||
})
|
|
||||||
|
|
||||||
let animationRenderer: MultiAnimationRenderer
|
|
||||||
/*if #available(iOS 13.0, *) {
|
|
||||||
animationRenderer = MultiAnimationMetalRendererImpl()
|
|
||||||
} else {*/
|
|
||||||
animationRenderer = MultiAnimationRendererImpl()
|
|
||||||
//}
|
|
||||||
|
|
||||||
self.animationRenderer = animationRenderer
|
|
||||||
|
|
||||||
super.init(navigationBarPresentationData: nil)
|
super.init(navigationBarPresentationData: nil)
|
||||||
|
|
||||||
|
@ -68,8 +68,11 @@ private extension PremiumPromoConfiguration {
|
|||||||
case let .premiumPromo(statusText, statusEntities, videoSections, videoFiles, periodOptions, _):
|
case let .premiumPromo(statusText, statusEntities, videoSections, videoFiles, periodOptions, _):
|
||||||
self.status = statusText
|
self.status = statusText
|
||||||
self.statusEntities = messageTextEntitiesFromApiEntities(statusEntities)
|
self.statusEntities = messageTextEntitiesFromApiEntities(statusEntities)
|
||||||
self.currency = currency
|
let _ = periodOptions
|
||||||
self.monthlyAmount = monthlyAmount
|
self.currency = "USD"
|
||||||
|
self.monthlyAmount = 500
|
||||||
|
//self.currency = currency
|
||||||
|
//self.monthlyAmount = monthlyAmount
|
||||||
var videos: [String: TelegramMediaFile] = [:]
|
var videos: [String: TelegramMediaFile] = [:]
|
||||||
for (key, document) in zip(videoSections, videoFiles) {
|
for (key, document) in zip(videoSections, videoFiles) {
|
||||||
if let file = telegramMediaFileFromApiDocument(document) {
|
if let file = telegramMediaFileFromApiDocument(document) {
|
||||||
|
@ -270,7 +270,7 @@ func managedRecentReactions(postbox: Postbox, network: Network) -> Signal<Void,
|
|||||||
return fileId.id
|
return fileId.id
|
||||||
}
|
}
|
||||||
}, reverseHashOrder: false, forceFetch: false, fetch: { hash in
|
}, 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
|
|> retryRequest
|
||||||
|> mapToSignal { result -> Signal<[OrderedItemListEntry]?, NoError> in
|
|> mapToSignal { result -> Signal<[OrderedItemListEntry]?, NoError> in
|
||||||
switch result {
|
switch result {
|
||||||
@ -321,7 +321,7 @@ func managedTopReactions(postbox: Postbox, network: Network) -> Signal<Void, NoE
|
|||||||
return fileId.id
|
return fileId.id
|
||||||
}
|
}
|
||||||
}, reverseHashOrder: false, forceFetch: false, fetch: { hash in
|
}, 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
|
|> retryRequest
|
||||||
|> mapToSignal { result -> Signal<[OrderedItemListEntry]?, NoError> in
|
|> mapToSignal { result -> Signal<[OrderedItemListEntry]?, NoError> in
|
||||||
switch result {
|
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
|
return account.postbox.transaction { transaction -> Void in
|
||||||
let isPremium = (transaction.getPeer(account.peerId) as? TelegramUser)?.isPremium ?? false
|
let isPremium = (transaction.getPeer(account.peerId) as? TelegramUser)?.isPremium ?? false
|
||||||
let appConfiguration = transaction.getPreferencesEntry(key: PreferencesKeys.appConfiguration)?.get(AppConfiguration.self) ?? .defaultValue
|
let appConfiguration = transaction.getPreferencesEntry(key: PreferencesKeys.appConfiguration)?.get(AppConfiguration.self) ?? .defaultValue
|
||||||
@ -57,6 +57,7 @@ public func updateMessageReactionsInteractively(account: Account, messageId: Mes
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if storeAsRecentlyUsed {
|
||||||
for attribute in currentMessage.attributes {
|
for attribute in currentMessage.attributes {
|
||||||
if let attribute = attribute as? ReactionsMessageAttribute {
|
if let attribute = attribute as? ReactionsMessageAttribute {
|
||||||
for updatedReaction in reactions {
|
for updatedReaction in reactions {
|
||||||
@ -81,10 +82,11 @@ public func updateMessageReactionsInteractively(account: Account, messageId: Mes
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var mappedReactions = mappedReactions
|
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)
|
let updatedOutgoingReactions = updatedReactions.filter(\.isSelected)
|
||||||
if updatedOutgoingReactions.count > maxCount {
|
if updatedOutgoingReactions.count > maxCount {
|
||||||
let sortedOutgoingReactions = updatedOutgoingReactions.sorted(by: { $0.chosenOrder! < $1.chosenOrder! })
|
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))
|
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> {
|
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 {
|
guard let peer = transaction.getPeer(messageId.peerId) else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -115,20 +117,22 @@ private func requestUpdateMessageReaction(postbox: Postbox, network: Network, st
|
|||||||
}
|
}
|
||||||
var reactions: [MessageReaction.Reaction]?
|
var reactions: [MessageReaction.Reaction]?
|
||||||
var isLarge: Bool = false
|
var isLarge: Bool = false
|
||||||
|
var storeAsRecentlyUsed: Bool = false
|
||||||
for attribute in message.attributes {
|
for attribute in message.attributes {
|
||||||
if let attribute = attribute as? PendingReactionsMessageAttribute {
|
if let attribute = attribute as? PendingReactionsMessageAttribute {
|
||||||
if !attribute.reactions.isEmpty {
|
if !attribute.reactions.isEmpty {
|
||||||
reactions = attribute.reactions.map(\.value)
|
reactions = attribute.reactions.map(\.value)
|
||||||
}
|
}
|
||||||
isLarge = attribute.isLarge
|
isLarge = attribute.isLarge
|
||||||
|
storeAsRecentlyUsed = attribute.storeAsRecentlyUsed
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (peer, reactions, isLarge)
|
return (peer, reactions, isLarge, storeAsRecentlyUsed)
|
||||||
}
|
}
|
||||||
|> castError(RequestUpdateMessageReactionError.self)
|
|> castError(RequestUpdateMessageReactionError.self)
|
||||||
|> mapToSignal { peerAndValue in
|
|> mapToSignal { peerAndValue in
|
||||||
guard let (peer, reactions, isLarge) = peerAndValue else {
|
guard let (peer, reactions, isLarge, storeAsRecentlyUsed) = peerAndValue else {
|
||||||
return .fail(.generic)
|
return .fail(.generic)
|
||||||
}
|
}
|
||||||
guard let inputPeer = apiInputPeer(peer) else {
|
guard let inputPeer = apiInputPeer(peer) else {
|
||||||
@ -144,6 +148,9 @@ private func requestUpdateMessageReaction(postbox: Postbox, network: Network, st
|
|||||||
if isLarge {
|
if isLarge {
|
||||||
flags |= 1 << 1
|
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)))
|
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 {
|
guard let strongSelf = self else {
|
||||||
return
|
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 {
|
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 {
|
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
|
continue
|
||||||
}
|
}
|
||||||
existingPeerIds.insert(item.peer.id)
|
existingItems.insert(itemHash)
|
||||||
strongSelf.state.items.append(item)
|
strongSelf.state.items.append(item)
|
||||||
}
|
}
|
||||||
if state.canLoadMore {
|
if state.canLoadMore {
|
||||||
|
@ -250,6 +250,7 @@ public final class PendingReactionsMessageAttribute: MessageAttribute {
|
|||||||
public let accountPeerId: PeerId?
|
public let accountPeerId: PeerId?
|
||||||
public let reactions: [PendingReaction]
|
public let reactions: [PendingReaction]
|
||||||
public let isLarge: Bool
|
public let isLarge: Bool
|
||||||
|
public let storeAsRecentlyUsed: Bool
|
||||||
|
|
||||||
public var associatedPeerIds: [PeerId] {
|
public var associatedPeerIds: [PeerId] {
|
||||||
if let accountPeerId = self.accountPeerId {
|
if let accountPeerId = self.accountPeerId {
|
||||||
@ -277,16 +278,18 @@ public final class PendingReactionsMessageAttribute: MessageAttribute {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(accountPeerId: PeerId?, reactions: [PendingReaction], isLarge: Bool) {
|
public init(accountPeerId: PeerId?, reactions: [PendingReaction], isLarge: Bool, storeAsRecentlyUsed: Bool) {
|
||||||
self.accountPeerId = accountPeerId
|
self.accountPeerId = accountPeerId
|
||||||
self.reactions = reactions
|
self.reactions = reactions
|
||||||
self.isLarge = isLarge
|
self.isLarge = isLarge
|
||||||
|
self.storeAsRecentlyUsed = storeAsRecentlyUsed
|
||||||
}
|
}
|
||||||
|
|
||||||
required public init(decoder: PostboxDecoder) {
|
required public init(decoder: PostboxDecoder) {
|
||||||
self.accountPeerId = decoder.decodeOptionalInt64ForKey("ap").flatMap(PeerId.init)
|
self.accountPeerId = decoder.decodeOptionalInt64ForKey("ap").flatMap(PeerId.init)
|
||||||
self.reactions = decoder.decodeObjectArrayWithDecoderForKey("reac")
|
self.reactions = decoder.decodeObjectArrayWithDecoderForKey("reac")
|
||||||
self.isLarge = decoder.decodeInt32ForKey("l", orElse: 0) != 0
|
self.isLarge = decoder.decodeInt32ForKey("l", orElse: 0) != 0
|
||||||
|
self.storeAsRecentlyUsed = decoder.decodeInt32ForKey("used", orElse: 0) != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
public func encode(_ encoder: PostboxEncoder) {
|
public func encode(_ encoder: PostboxEncoder) {
|
||||||
@ -299,5 +302,6 @@ public final class PendingReactionsMessageAttribute: MessageAttribute {
|
|||||||
encoder.encodeObjectArray(self.reactions, forKey: "reac")
|
encoder.encodeObjectArray(self.reactions, forKey: "reac")
|
||||||
|
|
||||||
encoder.encodeInt32(self.isLarge ? 1 : 0, forKey: "l")
|
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
|
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 {
|
public var isVideo: Bool {
|
||||||
for attribute in self.attributes {
|
for attribute in self.attributes {
|
||||||
if case .Video = attribute {
|
if case .Video = attribute {
|
||||||
|
@ -140,6 +140,18 @@ public extension TelegramEngine {
|
|||||||
|> ignoreValues
|
|> 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> {
|
public func reorderStickerPacks(namespace: ItemCollectionId.Namespace, itemIds: [ItemCollectionId]) -> Signal<Never, NoError> {
|
||||||
return self.account.postbox.transaction { transaction -> Void in
|
return self.account.postbox.transaction { transaction -> Void in
|
||||||
let infos = transaction.getItemCollectionsInfos(namespace: namespace)
|
let infos = transaction.getItemCollectionsInfos(namespace: namespace)
|
||||||
|
@ -19,10 +19,14 @@ swift_library(
|
|||||||
"//submodules/TelegramUI/Components/AnimationCache:AnimationCache",
|
"//submodules/TelegramUI/Components/AnimationCache:AnimationCache",
|
||||||
"//submodules/TelegramUI/Components/MultiAnimationRenderer:MultiAnimationRenderer",
|
"//submodules/TelegramUI/Components/MultiAnimationRenderer:MultiAnimationRenderer",
|
||||||
"//submodules/TelegramUI/Components/EntityKeyboard:EntityKeyboard",
|
"//submodules/TelegramUI/Components/EntityKeyboard:EntityKeyboard",
|
||||||
|
"//submodules/TelegramUI/Components/EmojiTextAttachmentView:EmojiTextAttachmentView",
|
||||||
"//submodules/Components/ComponentDisplayAdapters:ComponentDisplayAdapters",
|
"//submodules/Components/ComponentDisplayAdapters:ComponentDisplayAdapters",
|
||||||
"//submodules/Components/PagerComponent:PagerComponent",
|
"//submodules/Components/PagerComponent:PagerComponent",
|
||||||
"//submodules/TelegramPresentationData:TelegramPresentationData",
|
"//submodules/TelegramPresentationData:TelegramPresentationData",
|
||||||
"//submodules/AccountContext:AccountContext",
|
"//submodules/AccountContext:AccountContext",
|
||||||
|
"//submodules/lottie-ios:Lottie",
|
||||||
|
"//submodules/TextFormat:TextFormat",
|
||||||
|
"//submodules/AppBundle:AppBundle",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -13,6 +13,10 @@ import AccountContext
|
|||||||
import PagerComponent
|
import PagerComponent
|
||||||
import Postbox
|
import Postbox
|
||||||
import TelegramCore
|
import TelegramCore
|
||||||
|
import Lottie
|
||||||
|
import EmojiTextAttachmentView
|
||||||
|
import TextFormat
|
||||||
|
import AppBundle
|
||||||
|
|
||||||
public final class EmojiStatusSelectionComponent: Component {
|
public final class EmojiStatusSelectionComponent: Component {
|
||||||
public typealias EnvironmentType = Empty
|
public typealias EnvironmentType = Empty
|
||||||
@ -184,6 +188,11 @@ public final class EmojiStatusSelectionController: ViewController {
|
|||||||
private var emojiContent: EmojiPagerContentComponent?
|
private var emojiContent: EmojiPagerContentComponent?
|
||||||
private var scheduledEmojiContentAnimationHint: EmojiPagerContentComponent.ContentAnimation?
|
private var scheduledEmojiContentAnimationHint: EmojiPagerContentComponent.ContentAnimation?
|
||||||
|
|
||||||
|
private var availableReactions: AvailableReactions?
|
||||||
|
private var availableReactionsDisposable: Disposable?
|
||||||
|
|
||||||
|
private var hapticFeedback: HapticFeedback?
|
||||||
|
|
||||||
private var isDismissed: Bool = false
|
private var isDismissed: Bool = false
|
||||||
|
|
||||||
init(controller: EmojiStatusSelectionController, context: AccountContext, sourceView: UIView?, emojiContent: Signal<EmojiPagerContentComponent, NoError>) {
|
init(controller: EmojiStatusSelectionController, context: AccountContext, sourceView: UIView?, emojiContent: Signal<EmojiPagerContentComponent, NoError>) {
|
||||||
@ -235,11 +244,11 @@ public final class EmojiStatusSelectionController: ViewController {
|
|||||||
strongSelf.emojiContent = emojiContent
|
strongSelf.emojiContent = emojiContent
|
||||||
|
|
||||||
emojiContent.inputInteractionHolder.inputInteraction = EmojiPagerContentComponent.InputInteraction(
|
emojiContent.inputInteractionHolder.inputInteraction = EmojiPagerContentComponent.InputInteraction(
|
||||||
performItemAction: { _, item, _, _, _, _ in
|
performItemAction: { groupId, item, _, _, _, _ in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
strongSelf.applyItem(item: item)
|
strongSelf.applyItem(groupId: groupId, item: item)
|
||||||
},
|
},
|
||||||
deleteBackwards: {
|
deleteBackwards: {
|
||||||
},
|
},
|
||||||
@ -291,10 +300,20 @@ public final class EmojiStatusSelectionController: ViewController {
|
|||||||
|
|
||||||
strongSelf.refreshLayout(transition: .immediate)
|
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 {
|
deinit {
|
||||||
self.emojiContentDisposable?.dispose()
|
self.emojiContentDisposable?.dispose()
|
||||||
|
self.availableReactionsDisposable?.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func refreshLayout(transition: Transition) {
|
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)
|
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) {
|
func containerLayoutUpdated(layout: ContainerViewLayout, transition: Transition) {
|
||||||
self.validLayout = layout
|
self.validLayout = layout
|
||||||
|
|
||||||
@ -465,30 +612,42 @@ public final class EmojiStatusSelectionController: ViewController {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
private func applyItem(item: EmojiPagerContentComponent.Item?) {
|
private func applyItem(groupId: AnyHashable, item: EmojiPagerContentComponent.Item?) {
|
||||||
self.controller?.dismiss()
|
guard let controller = self.controller else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
let _ = (self.context.engine.accountData.setEmojiStatus(file: item?.itemFile)
|
let _ = (self.context.engine.accountData.setEmojiStatus(file: item?.itemFile)
|
||||||
|> deliverOnMainQueue).start()
|
|> 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 let context: AccountContext
|
||||||
private weak var sourceView: UIView?
|
private weak var sourceView: UIView?
|
||||||
private let emojiContent: Signal<EmojiPagerContentComponent, NoError>
|
private let emojiContent: Signal<EmojiPagerContentComponent, NoError>
|
||||||
|
private let destinationItemView: () -> UIView?
|
||||||
|
|
||||||
fileprivate let _ready = Promise<Bool>()
|
fileprivate let _ready = Promise<Bool>()
|
||||||
override public var ready: Promise<Bool> {
|
override public var ready: Promise<Bool> {
|
||||||
return self._ready
|
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.context = context
|
||||||
self.sourceView = sourceView
|
self.sourceView = sourceView
|
||||||
self.emojiContent = emojiContent
|
self.emojiContent = emojiContent
|
||||||
|
self.destinationItemView = destinationItemView
|
||||||
|
|
||||||
super.init(navigationBarPresentationData: nil)
|
super.init(navigationBarPresentationData: nil)
|
||||||
|
|
||||||
|
self.lockOrientation = true
|
||||||
|
|
||||||
self.statusBar.statusBarStyle = .Ignore
|
self.statusBar.statusBarStyle = .Ignore
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -500,6 +659,10 @@ public final class EmojiStatusSelectionController: ViewController {
|
|||||||
super.viewDidAppear(animated)
|
super.viewDidAppear(animated)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func dismissNow() {
|
||||||
|
self.presentingViewController?.dismiss(animated: false, completion: nil)
|
||||||
|
}
|
||||||
|
|
||||||
override public func dismiss(completion: (() -> Void)? = nil) {
|
override public func dismiss(completion: (() -> Void)? = nil) {
|
||||||
(self.displayNode as! Node).animateOut(completion: { [weak self] in
|
(self.displayNode as! Node).animateOut(completion: { [weak self] in
|
||||||
self?.presentingViewController?.dismiss(animated: false, completion: nil)
|
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))
|
(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?) {
|
public func scrollToItemGroup(id supergroupId: AnyHashable, subgroupId: Int32?) {
|
||||||
guard let component = self.component, let pagerEnvironment = self.pagerEnvironment, let itemLayout = self.itemLayout else {
|
guard let component = self.component, let pagerEnvironment = self.pagerEnvironment, let itemLayout = self.itemLayout else {
|
||||||
return
|
return
|
||||||
@ -4593,6 +4602,7 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
var isPremiumLocked: Bool
|
var isPremiumLocked: Bool
|
||||||
var isFeatured: Bool
|
var isFeatured: Bool
|
||||||
var isExpandable: Bool
|
var isExpandable: Bool
|
||||||
|
var isClearable: Bool
|
||||||
var headerItem: EntityKeyboardAnimationData?
|
var headerItem: EntityKeyboardAnimationData?
|
||||||
var items: [EmojiPagerContentComponent.Item]
|
var items: [EmojiPagerContentComponent.Item]
|
||||||
}
|
}
|
||||||
@ -4628,7 +4638,7 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
itemGroups[groupIndex].items.append(resultItem)
|
itemGroups[groupIndex].items.append(resultItem)
|
||||||
} else {
|
} else {
|
||||||
itemGroupIndexById[groupId] = itemGroups.count
|
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>()
|
var existingIds = Set<MediaId>()
|
||||||
@ -4706,13 +4716,58 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
let groupId = "recent"
|
let groupId = "recent"
|
||||||
if let groupIndex = itemGroupIndexById[groupId] {
|
if let groupIndex = itemGroupIndexById[groupId] {
|
||||||
itemGroups[groupIndex].items.append(resultItem)
|
itemGroups[groupIndex].items.append(resultItem)
|
||||||
|
|
||||||
|
if itemGroups[groupIndex].items.count >= 16 {
|
||||||
|
break
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
itemGroupIndexById[groupId] = itemGroups.count
|
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 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
|
||||||
|
}
|
||||||
|
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 {
|
if let recentReactions = recentReactions {
|
||||||
|
var popularInsertIndex = 0
|
||||||
for item in recentReactions.items {
|
for item in recentReactions.items {
|
||||||
guard let item = item.contents.get(RecentReactionItem.self) else {
|
guard let item = item.contents.get(RecentReactionItem.self) else {
|
||||||
continue
|
continue
|
||||||
@ -4750,12 +4805,18 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
subgroupId: nil
|
subgroupId: nil
|
||||||
)
|
)
|
||||||
|
|
||||||
let groupId = "recent"
|
let groupId = "popular"
|
||||||
if let groupIndex = itemGroupIndexById[groupId] {
|
if let groupIndex = itemGroupIndexById[groupId] {
|
||||||
itemGroups[groupIndex].items.append(resultItem)
|
if itemGroups[groupIndex].items.count + 1 >= maxRecentLineCount * 8 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
itemGroups[groupIndex].items.insert(resultItem, at: popularInsertIndex)
|
||||||
|
popularInsertIndex += 1
|
||||||
} else {
|
} else {
|
||||||
itemGroupIndexById[groupId] = itemGroups.count
|
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: popularTitle, subtitle: nil, isPremiumLocked: false, isFeatured: false, isExpandable: false, isClearable: hasRecent, headerItem: nil, items: [resultItem]))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4799,7 +4860,7 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
itemGroups[groupIndex].items.append(resultItem)
|
itemGroups[groupIndex].items.append(resultItem)
|
||||||
} else {
|
} else {
|
||||||
itemGroupIndexById[groupId] = itemGroups.count
|
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)
|
itemGroups[groupIndex].items.append(resultItem)
|
||||||
} else {
|
} else {
|
||||||
itemGroupIndexById[groupId] = itemGroups.count
|
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
|
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,
|
animationRenderer: animationRenderer,
|
||||||
inputInteractionHolder: EmojiPagerContentComponent.InputInteractionHolder(),
|
inputInteractionHolder: EmojiPagerContentComponent.InputInteractionHolder(),
|
||||||
itemGroups: itemGroups.map { group -> EmojiPagerContentComponent.ItemGroup in
|
itemGroups: itemGroups.map { group -> EmojiPagerContentComponent.ItemGroup in
|
||||||
var hasClear = false
|
|
||||||
if group.id == AnyHashable("recent") {
|
|
||||||
hasClear = true
|
|
||||||
}
|
|
||||||
|
|
||||||
var headerItem = group.headerItem
|
var headerItem = group.headerItem
|
||||||
|
|
||||||
if let groupId = group.id.base as? ItemCollectionId {
|
if let groupId = group.id.base as? ItemCollectionId {
|
||||||
@ -4983,7 +5039,7 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
isFeatured: group.isFeatured,
|
isFeatured: group.isFeatured,
|
||||||
isPremiumLocked: group.isPremiumLocked,
|
isPremiumLocked: group.isPremiumLocked,
|
||||||
isEmbedded: false,
|
isEmbedded: false,
|
||||||
hasClear: hasClear,
|
hasClear: group.isClearable,
|
||||||
isExpandable: group.isExpandable,
|
isExpandable: group.isExpandable,
|
||||||
displayPremiumBadges: false,
|
displayPremiumBadges: false,
|
||||||
headerItem: headerItem,
|
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 MeshAnimationCache
|
||||||
import FetchManagerImpl
|
import FetchManagerImpl
|
||||||
import InAppPurchaseManager
|
import InAppPurchaseManager
|
||||||
|
import AnimationCache
|
||||||
|
import MultiAnimationRenderer
|
||||||
|
|
||||||
private final class DeviceSpecificContactImportContext {
|
private final class DeviceSpecificContactImportContext {
|
||||||
let disposable = MetaDisposable()
|
let disposable = MetaDisposable()
|
||||||
@ -160,6 +162,9 @@ public final class AccountContextImpl: AccountContext {
|
|||||||
public let cachedGroupCallContexts: AccountGroupCallContextCache
|
public let cachedGroupCallContexts: AccountGroupCallContextCache
|
||||||
public let meshAnimationCache: MeshAnimationCache
|
public let meshAnimationCache: MeshAnimationCache
|
||||||
|
|
||||||
|
public let animationCache: AnimationCache
|
||||||
|
public let animationRenderer: MultiAnimationRenderer
|
||||||
|
|
||||||
private var animatedEmojiStickersDisposable: Disposable?
|
private var animatedEmojiStickersDisposable: Disposable?
|
||||||
public private(set) var animatedEmojiStickers: [String: [StickerPackItem]] = [:]
|
public private(set) var animatedEmojiStickers: [String: [StickerPackItem]] = [:]
|
||||||
|
|
||||||
@ -204,6 +209,11 @@ public final class AccountContextImpl: AccountContext {
|
|||||||
self.cachedGroupCallContexts = AccountGroupCallContextCacheImpl()
|
self.cachedGroupCallContexts = AccountGroupCallContextCacheImpl()
|
||||||
self.meshAnimationCache = MeshAnimationCache(mediaBox: account.postbox.mediaBox)
|
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])
|
let updatedLimitsConfiguration = account.postbox.preferencesView(keys: [PreferencesKeys.limitsConfiguration])
|
||||||
|> map { preferences -> LimitsConfiguration in
|
|> map { preferences -> LimitsConfiguration in
|
||||||
return preferences.values[PreferencesKeys.limitsConfiguration]?.get(LimitsConfiguration.self) ?? LimitsConfiguration.defaultValue
|
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 ?? []
|
/*let currentReactions = mergedMessageReactions(attributes: message.attributes)?.reactions ?? []
|
||||||
var updatedReactions: [MessageReaction.Reaction] = currentReactions.filter(\.isSelected).map(\.value)
|
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
|
}, activateMessagePinch: { [weak self] sourceNode in
|
||||||
|
@ -87,15 +87,8 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
|
|||||||
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
|
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
|
||||||
let isPremiumDisabled = premiumConfiguration.isPremiumDisabled
|
let isPremiumDisabled = premiumConfiguration.isPremiumDisabled
|
||||||
|
|
||||||
let animationCache = AnimationCacheImpl(basePath: context.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: {
|
let animationCache = context.animationCache
|
||||||
return TempBox.shared.tempFile(fileName: "file").path
|
let animationRenderer = context.animationRenderer
|
||||||
})
|
|
||||||
let animationRenderer: MultiAnimationRenderer
|
|
||||||
/*if #available(iOS 13.0, *) {
|
|
||||||
animationRenderer = MultiAnimationMetalRendererImpl()
|
|
||||||
} else {*/
|
|
||||||
animationRenderer = MultiAnimationRendererImpl()
|
|
||||||
//}
|
|
||||||
|
|
||||||
let emojiItems = EmojiPagerContentComponent.emojiInputData(context: context, animationCache: animationCache, animationRenderer: animationRenderer, isStandalone: false, isStatusSelection: false, isReactionSelection: false, topReactionItems: [], areUnicodeEmojiEnabled: true, areCustomEmojiEnabled: areCustomEmojiEnabled, chatPeerId: chatPeerId)
|
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.context = context
|
||||||
|
|
||||||
self.animationCache = AnimationCacheImpl(basePath: context.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: {
|
self.animationCache = context.animationCache
|
||||||
return TempBox.shared.tempFile(fileName: "file").path
|
self.animationRenderer = context.animationRenderer
|
||||||
})
|
|
||||||
self.animationRenderer = MultiAnimationRendererImpl()
|
|
||||||
|
|
||||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
if isDark {
|
if isDark {
|
||||||
|
@ -2410,6 +2410,8 @@ private final class ChatReadReportContextItemNode: ASDisplayNode, ContextMenuCus
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let currentStats = self.currentStats {
|
if let currentStats = self.currentStats {
|
||||||
|
reactionCount = currentStats.reactionCount
|
||||||
|
|
||||||
if currentStats.peers.isEmpty {
|
if currentStats.peers.isEmpty {
|
||||||
if reactionCount != 0 {
|
if reactionCount != 0 {
|
||||||
let text: String = self.presentationData.strings.Chat_ContextReactionCount(Int32(reactionCount))
|
let text: String = self.presentationData.strings.Chat_ContextReactionCount(Int32(reactionCount))
|
||||||
|
@ -298,18 +298,8 @@ class ChatPresentationContext {
|
|||||||
init(context: AccountContext, backgroundNode: WallpaperBackgroundNode?) {
|
init(context: AccountContext, backgroundNode: WallpaperBackgroundNode?) {
|
||||||
self.backgroundNode = backgroundNode
|
self.backgroundNode = backgroundNode
|
||||||
|
|
||||||
self.animationCache = AnimationCacheImpl(basePath: context.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: {
|
self.animationCache = context.animationCache
|
||||||
return TempBox.shared.tempFile(fileName: "file").path
|
self.animationRenderer = context.animationRenderer
|
||||||
})
|
|
||||||
|
|
||||||
let animationRenderer: MultiAnimationRenderer
|
|
||||||
/*if #available(iOS 13.0, *) {
|
|
||||||
animationRenderer = MultiAnimationMetalRendererImpl()
|
|
||||||
} else {*/
|
|
||||||
animationRenderer = MultiAnimationRendererImpl()
|
|
||||||
//}
|
|
||||||
|
|
||||||
self.animationRenderer = animationRenderer
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,12 +113,7 @@ final class ChatMessageNotificationItemNode: NotificationItemNode {
|
|||||||
func setupItem(_ item: ChatMessageNotificationItem, compact: Bool) {
|
func setupItem(_ item: ChatMessageNotificationItem, compact: Bool) {
|
||||||
self.item = item
|
self.item = item
|
||||||
|
|
||||||
if self.animationCache == nil {
|
self.animationCache = item.context.animationCache
|
||||||
self.animationCache = AnimationCacheImpl(basePath: item.context.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: {
|
|
||||||
return TempBox.shared.tempFile(fileName: "file").path
|
|
||||||
})
|
|
||||||
self.multiAnimationRenderer = MultiAnimationRendererImpl()
|
|
||||||
}
|
|
||||||
|
|
||||||
self.compact = compact
|
self.compact = compact
|
||||||
if compact {
|
if compact {
|
||||||
|
@ -1411,10 +1411,20 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
|
|||||||
var entities: [MessageTextEntity] = []
|
var entities: [MessageTextEntity] = []
|
||||||
|
|
||||||
let rawText: PresentationStrings.FormattedString
|
let rawText: PresentationStrings.FormattedString
|
||||||
if !updatedValue.isEmpty {
|
switch updatedValue {
|
||||||
let emojiString = updatedValue.joined(separator: ", ")
|
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)
|
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) ?? "")
|
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.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.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: {
|
self.animationCache = context.animationCache
|
||||||
return TempBox.shared.tempFile(fileName: "file").path
|
self.animationRenderer = context.animationRenderer
|
||||||
})
|
|
||||||
self.animationRenderer = MultiAnimationRendererImpl()
|
|
||||||
|
|
||||||
self.listNode = ListView()
|
self.listNode = ListView()
|
||||||
self.listNode.verticalScrollIndicatorColor = self.presentationData.theme.list.scrollIndicatorColor
|
self.listNode.verticalScrollIndicatorColor = self.presentationData.theme.list.scrollIndicatorColor
|
||||||
|
@ -77,10 +77,8 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
|
|||||||
self.context = context
|
self.context = context
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
|
|
||||||
self.animationCache = AnimationCacheImpl(basePath: context.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: {
|
self.animationCache = context.animationCache
|
||||||
return TempBox.shared.tempFile(fileName: "file").path
|
self.animationRenderer = context.animationRenderer
|
||||||
})
|
|
||||||
self.animationRenderer = MultiAnimationRendererImpl()
|
|
||||||
|
|
||||||
var placeholder: String
|
var placeholder: String
|
||||||
var includeChatList = false
|
var includeChatList = false
|
||||||
|
@ -2109,10 +2109,8 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||||||
self.separatorNode = ASDisplayNode()
|
self.separatorNode = ASDisplayNode()
|
||||||
self.separatorNode.isLayerBacked = true
|
self.separatorNode.isLayerBacked = true
|
||||||
|
|
||||||
self.animationCache = AnimationCacheImpl(basePath: context.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: {
|
self.animationCache = context.animationCache
|
||||||
return TempBox.shared.tempFile(fileName: "file").path
|
self.animationRenderer = context.animationRenderer
|
||||||
})
|
|
||||||
self.animationRenderer = MultiAnimationRendererImpl()
|
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
@ -2676,7 +2674,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||||||
collapsedTransitionOffset = -10.0 * navigationTransition.fraction
|
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))
|
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 {
|
guard let result = super.hitTest(point, with: event) else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if result.isDescendant(of: self.navigationButtonContainer.view) {
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
if !self.backgroundNode.frame.contains(point) {
|
if !self.backgroundNode.frame.contains(point) {
|
||||||
return nil
|
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 {
|
if result == self.view || result == self.regularContentNode.view || result == self.editingContentNode.view {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -624,16 +624,19 @@ private func settingsItems(data: PeerInfoScreenData?, context: AccountContext, p
|
|||||||
displaySetPhoto = true
|
displaySetPhoto = true
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO:localize
|
|
||||||
var setStatusTitle: String = ""
|
var setStatusTitle: String = ""
|
||||||
let displaySetStatus: Bool
|
let displaySetStatus: Bool
|
||||||
var hasEmojiStatus = false
|
var hasEmojiStatus = false
|
||||||
if let peer = data.peer as? TelegramUser, peer.isPremium {
|
if let peer = data.peer as? TelegramUser, peer.isPremium {
|
||||||
if peer.emojiStatus != nil {
|
if peer.emojiStatus != nil {
|
||||||
hasEmojiStatus = true
|
hasEmojiStatus = true
|
||||||
|
//TODO:localize
|
||||||
|
setStatusTitle = "Change Emoji Status"
|
||||||
|
} else {
|
||||||
|
//TODO:localize
|
||||||
|
setStatusTitle = "Set Emoji Status"
|
||||||
}
|
}
|
||||||
displaySetStatus = true
|
displaySetStatus = true
|
||||||
setStatusTitle = "Set Emoji Status"
|
|
||||||
} else {
|
} else {
|
||||||
displaySetStatus = false
|
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: {
|
items[.peerInfo]!.append(PeerInfoScreenActionItem(id: 3, text: presentationData.strings.UserInfo_SendMessage, action: {
|
||||||
interaction.openChat()
|
interaction.openChat()
|
||||||
}))
|
}))
|
||||||
@ -3089,10 +3092,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let animationCache = AnimationCacheImpl(basePath: context.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: {
|
let animationCache = context.animationCache
|
||||||
return TempBox.shared.tempFile(fileName: "file").path
|
let animationRenderer = context.animationRenderer
|
||||||
})
|
|
||||||
let animationRenderer = MultiAnimationRendererImpl()
|
|
||||||
strongSelf.controller?.present(EmojiStatusSelectionController(
|
strongSelf.controller?.present(EmojiStatusSelectionController(
|
||||||
context: strongSelf.context,
|
context: strongSelf.context,
|
||||||
sourceView: sourceView,
|
sourceView: sourceView,
|
||||||
@ -3107,7 +3109,10 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
areUnicodeEmojiEnabled: false,
|
areUnicodeEmojiEnabled: false,
|
||||||
areCustomEmojiEnabled: true,
|
areCustomEmojiEnabled: true,
|
||||||
chatPeerId: strongSelf.context.account.peerId
|
chatPeerId: strongSelf.context.account.peerId
|
||||||
)
|
),
|
||||||
|
destinationItemView: { [weak sourceView] in
|
||||||
|
return sourceView
|
||||||
|
}
|
||||||
), in: .window(.root))
|
), in: .window(.root))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -3128,9 +3133,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if let emojiStatusFile = emojiStatusFile {
|
|
||||||
let source: PremiumSource
|
let source: PremiumSource
|
||||||
if let peerStatus = peerStatus {
|
if let peerStatus = peerStatus, let emojiStatusFile = emojiStatusFile {
|
||||||
source = .emojiStatus(strongSelf.peerId, peerStatus.fileId, emojiStatusFile, emojiPackTitle)
|
source = .emojiStatus(strongSelf.peerId, peerStatus.fileId, emojiStatusFile, emojiPackTitle)
|
||||||
} else {
|
} else {
|
||||||
source = .profile(strongSelf.peerId)
|
source = .profile(strongSelf.peerId)
|
||||||
@ -3141,7 +3145,6 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
controller.containerView = strongSelf.controller?.navigationController?.view
|
controller.containerView = strongSelf.controller?.navigationController?.view
|
||||||
controller.animationColor = white ? .white : strongSelf.presentationData.theme.list.itemAccentColor
|
controller.animationColor = white ? .white : strongSelf.presentationData.theme.list.itemAccentColor
|
||||||
strongSelf.controller?.push(controller)
|
strongSelf.controller?.push(controller)
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,10 +98,8 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
|||||||
|
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
|
|
||||||
self.animationCache = AnimationCacheImpl(basePath: context.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: {
|
self.animationCache = context.animationCache
|
||||||
return TempBox.shared.tempFile(fileName: "file").path
|
self.animationRenderer = context.animationRenderer
|
||||||
})
|
|
||||||
self.animationRenderer = MultiAnimationRendererImpl()
|
|
||||||
|
|
||||||
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)
|
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)
|
||||||
|
|
||||||
|
@ -17,14 +17,36 @@ func topMessageReactions(context: AccountContext, message: Message) -> Signal<[R
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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(
|
return combineLatest(
|
||||||
context.engine.stickers.availableReactions(),
|
context.engine.stickers.availableReactions(),
|
||||||
peerMessageAllowedReactions(context: context, message: message),
|
allowedReactionsWithFiles,
|
||||||
topReactions
|
topReactions
|
||||||
)
|
)
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> map { availableReactions, allowedReactions, topReactions -> [ReactionItem] in
|
|> map { availableReactions, allowedReactionsAndFiles, topReactions -> [ReactionItem] in
|
||||||
guard let availableReactions = availableReactions, let allowedReactions = allowedReactions else {
|
guard let availableReactions = availableReactions, let allowedReactionsAndFiles = allowedReactionsAndFiles else {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,7 +70,7 @@ func topMessageReactions(context: AccountContext, message: Message) -> Signal<[R
|
|||||||
}
|
}
|
||||||
existingIds.insert(reaction.value)
|
existingIds.insert(reaction.value)
|
||||||
|
|
||||||
switch allowedReactions {
|
switch allowedReactionsAndFiles.reactions {
|
||||||
case let .set(set):
|
case let .set(set):
|
||||||
if !set.contains(reaction.value) {
|
if !set.contains(reaction.value) {
|
||||||
continue
|
continue
|
||||||
@ -71,7 +93,7 @@ func topMessageReactions(context: AccountContext, message: Message) -> Signal<[R
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
case let .custom(file):
|
case let .custom(file):
|
||||||
switch allowedReactions {
|
switch allowedReactionsAndFiles.reactions {
|
||||||
case let .set(set):
|
case let .set(set):
|
||||||
if !set.contains(.custom(file.fileId.id)) {
|
if !set.contains(.custom(file.fileId.id)) {
|
||||||
continue
|
continue
|
||||||
@ -109,13 +131,13 @@ func topMessageReactions(context: AccountContext, message: Message) -> Signal<[R
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
switch allowedReactions {
|
switch allowedReactionsAndFiles.reactions {
|
||||||
case let .set(set):
|
case let .set(set):
|
||||||
if !set.contains(reaction.value) {
|
if !set.contains(reaction.value) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
case .all:
|
case .all:
|
||||||
break
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if existingIds.contains(reaction.value) {
|
if existingIds.contains(reaction.value) {
|
||||||
@ -135,6 +157,33 @@ func topMessageReactions(context: AccountContext, message: Message) -> Signal<[R
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
return result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user