Files

2353 lines
116 KiB
Swift

import Foundation
import UIKit
import AccountContext
import TelegramCore
import Postbox
import SwiftSignalKit
import AnimationCache
import MultiAnimationRenderer
import TelegramNotices
import FlatBuffers
import FlatSerialization
public extension EmojiPagerContentComponent {
private static func hasPremium(context: AccountContext, chatPeerId: EnginePeer.Id?, premiumIfSavedMessages: Bool) -> Signal<Bool, NoError> {
let hasPremium: Signal<Bool, NoError>
if premiumIfSavedMessages, let chatPeerId = chatPeerId, chatPeerId == context.account.peerId {
hasPremium = .single(true)
} else {
hasPremium = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|> map { peer -> Bool in
guard case let .user(user) = peer else {
return false
}
return user.isPremium
}
|> distinctUntilChanged
}
return hasPremium
}
enum Subject: Equatable {
case generic
case status
case channelStatus
case reaction(onlyTop: Bool)
case messageTag
case emoji
case topicIcon
case quickReaction
case profilePhoto
case groupPhoto
case backgroundIcon
case reactionList
case stickerAlt
}
static func emojiInputData(
context: AccountContext,
animationCache: AnimationCache,
animationRenderer: MultiAnimationRenderer,
isStandalone: Bool,
subject: Subject,
hasTrending: Bool,
topReactionItems: [EmojiComponentReactionItem],
topEmojiItems: [String] = [],
areUnicodeEmojiEnabled: Bool,
areCustomEmojiEnabled: Bool,
chatPeerId: EnginePeer.Id?,
selectedItems: Set<AnyHashable> = Set(),
topStatusTitle: String? = nil,
topicTitle: String? = nil,
topicColor: Int32? = nil,
backgroundIconColor: UIColor? = nil,
hasSearch: Bool = true,
hasRecent: Bool = true,
forceHasPremium: Bool = false,
premiumIfSavedMessages: Bool = true,
hideBackground: Bool = false,
maskEdge: EmojiPagerContentComponent.MaskEdgeMode = .none
) -> Signal<EmojiPagerContentComponent, NoError> {
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
let isPremiumDisabled = premiumConfiguration.isPremiumDisabled
let strings = context.sharedContext.currentPresentationData.with({ $0 }).strings
struct PeerSpecificPackData: Equatable {
var info: StickerPackCollectionInfo.Accessor
var items: [StickerPackItem]
var peer: EnginePeer
static func ==(lhs: PeerSpecificPackData, rhs: PeerSpecificPackData) -> Bool {
if lhs.info.id != rhs.info.id {
return false
}
if lhs.items != rhs.items {
return false
}
if lhs.peer != rhs.peer {
return false
}
return true
}
}
let peerSpecificPack: Signal<PeerSpecificPackData?, NoError>
if let chatPeerId = chatPeerId {
peerSpecificPack = combineLatest(
context.engine.peers.peerSpecificEmojiPack(peerId: chatPeerId),
context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: chatPeerId))
)
|> map { packData, peer -> PeerSpecificPackData? in
guard let peer = peer else {
return nil
}
guard let (info, items) = packData.packInfo else {
return nil
}
return PeerSpecificPackData(info: info, items: items.compactMap { $0 as? StickerPackItem }, peer: peer)
}
|> distinctUntilChanged
} else {
peerSpecificPack = .single(nil)
}
var orderedItemListCollectionIds: [Int32] = []
switch subject {
case .backgroundIcon, .reactionList:
break
default:
orderedItemListCollectionIds.append(Namespaces.OrderedItemList.LocalRecentEmoji)
}
var iconStatusEmoji: Signal<[TelegramMediaFile], NoError> = .single([])
if case .status = subject {
orderedItemListCollectionIds.append(Namespaces.OrderedItemList.CloudFeaturedStatusEmoji)
orderedItemListCollectionIds.append(Namespaces.OrderedItemList.CloudRecentStatusEmoji)
orderedItemListCollectionIds.append(Namespaces.OrderedItemList.CloudUniqueStarGifts)
iconStatusEmoji = context.engine.stickers.loadedStickerPack(reference: .iconStatusEmoji, forceActualized: false)
|> map { result -> [TelegramMediaFile] in
switch result {
case let .result(_, items, _):
return items.map({ $0.file._parse() })
default:
return []
}
}
|> take(1)
} else if case .channelStatus = subject {
orderedItemListCollectionIds.append(Namespaces.OrderedItemList.CloudFeaturedChannelStatusEmoji)
orderedItemListCollectionIds.append(Namespaces.OrderedItemList.CloudDisabledChannelStatusEmoji)
iconStatusEmoji = context.engine.stickers.loadedStickerPack(reference: .iconChannelStatusEmoji, forceActualized: false)
|> map { result -> [TelegramMediaFile] in
switch result {
case let .result(_, items, _):
return items.map({ $0.file._parse() })
default:
return []
}
}
|> take(1)
} else if [.reaction(onlyTop: false), .quickReaction].contains(subject) {
orderedItemListCollectionIds.append(Namespaces.OrderedItemList.CloudTopReactions)
orderedItemListCollectionIds.append(Namespaces.OrderedItemList.CloudRecentReactions)
} else if case .messageTag = subject {
orderedItemListCollectionIds.append(Namespaces.OrderedItemList.CloudDefaultTagReactions)
} else if case .topicIcon = subject {
iconStatusEmoji = context.engine.stickers.loadedStickerPack(reference: .iconTopicEmoji, forceActualized: false)
|> map { result -> [TelegramMediaFile] in
switch result {
case let .result(_, items, _):
return items.map({ $0.file._parse() })
default:
return []
}
}
|> take(1)
} else if case .profilePhoto = subject {
orderedItemListCollectionIds.append(Namespaces.OrderedItemList.CloudFeaturedProfilePhotoEmoji)
} else if case .groupPhoto = subject {
orderedItemListCollectionIds.append(Namespaces.OrderedItemList.CloudFeaturedGroupPhotoEmoji)
} else if case .backgroundIcon = subject {
orderedItemListCollectionIds.append(Namespaces.OrderedItemList.CloudFeaturedBackgroundIconEmoji)
}
let availableReactions: Signal<AvailableReactions?, NoError>
if [.reaction(onlyTop: false), .quickReaction, .reactionList].contains(subject) {
availableReactions = context.engine.stickers.availableReactions()
} else {
availableReactions = .single(nil)
}
let searchCategories: Signal<EmojiSearchCategories?, NoError>
if [.emoji, .reaction(onlyTop: false), .reactionList, .messageTag].contains(subject) {
searchCategories = context.engine.stickers.emojiSearchCategories(kind: .emoji)
} else if case .status = subject {
searchCategories = context.engine.stickers.emojiSearchCategories(kind: .status)
} else if case .channelStatus = subject {
searchCategories = .single(nil)
} else if [.profilePhoto, .groupPhoto].contains(subject) {
searchCategories = context.engine.stickers.emojiSearchCategories(kind: .avatar)
} else {
searchCategories = .single(nil)
}
let emojiItems: Signal<EmojiPagerContentComponent, NoError> = combineLatest(
context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: orderedItemListCollectionIds, namespaces: [Namespaces.ItemCollection.CloudEmojiPacks], aroundIndex: nil, count: 10000000),
forceHasPremium ? .single(true) : hasPremium(context: context, chatPeerId: chatPeerId, premiumIfSavedMessages: premiumIfSavedMessages),
context.account.viewTracker.featuredEmojiPacks(),
availableReactions,
searchCategories,
iconStatusEmoji,
peerSpecificPack,
ApplicationSpecificNotice.dismissedTrendingEmojiPacks(accountManager: context.sharedContext.accountManager)
)
|> map { view, hasPremium, featuredEmojiPacks, availableReactions, searchCategories, iconStatusEmoji, peerSpecificPack, dismissedTrendingEmojiPacks -> EmojiPagerContentComponent in
struct ItemGroup {
var supergroupId: AnyHashable
var id: AnyHashable
var title: String?
var subtitle: String?
var badge: String?
var isPremiumLocked: Bool
var isFeatured: Bool
var collapsedLineCount: Int?
var isClearable: Bool
var headerItem: EntityKeyboardAnimationData?
var items: [EmojiPagerContentComponent.Item]
}
var itemGroups: [ItemGroup] = []
var itemGroupIndexById: [AnyHashable: Int] = [:]
let maybeAppendUnicodeEmoji = {
let groupId: AnyHashable = "static"
if itemGroupIndexById[groupId] != nil {
return
}
if areUnicodeEmojiEnabled {
for (subgroupId, list) in staticEmojiMapping {
for emojiString in list {
let resultItem = EmojiPagerContentComponent.Item(
animationData: nil,
content: .staticEmoji(emojiString),
itemFile: nil,
subgroupId: subgroupId.rawValue,
icon: .none,
tintMode: .none
)
if let groupIndex = itemGroupIndexById[groupId] {
itemGroups[groupIndex].items.append(resultItem)
} else {
itemGroupIndexById[groupId] = itemGroups.count
let title: String?
if case .stickerAlt = subject {
title = nil
} else {
title = strings.EmojiInput_SectionTitleEmoji
}
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: title, subtitle: nil, badge: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: nil, isClearable: false, headerItem: nil, items: [resultItem]))
}
}
}
}
}
var installedCollectionIds = Set<ItemCollectionId>()
for (id, _, _) in view.collectionInfos {
installedCollectionIds.insert(id)
}
let dismissedTrendingEmojiPacksSet = Set(dismissedTrendingEmojiPacks ?? [])
let featuredEmojiPacksSet = Set(featuredEmojiPacks.map(\.info.id.id))
if dismissedTrendingEmojiPacksSet != featuredEmojiPacksSet && hasTrending {
for featuredEmojiPack in featuredEmojiPacks {
if installedCollectionIds.contains(featuredEmojiPack.info.id) {
continue
}
guard let item = featuredEmojiPack.topItems.first else {
continue
}
let animationData: EntityKeyboardAnimationData
if let thumbnailDimensions = featuredEmojiPack.info.thumbnailDimensions {
let type: EntityKeyboardAnimationData.ItemType
if item.file.isAnimatedSticker {
type = .lottie
} else if item.file.isVideoEmoji || item.file.isVideoSticker {
type = .video(isVP9: true)
} else if item.file.isVideo {
type = .video(isVP9: false)
} else {
type = .still
}
animationData = EntityKeyboardAnimationData(
id: .stickerPackThumbnail(featuredEmojiPack.info.id),
type: type,
resource: .stickerPackThumbnail(id: featuredEmojiPack.info.id.id, accessHash: featuredEmojiPack.info.accessHash, info: featuredEmojiPack.info),
dimensions: thumbnailDimensions.cgSize,
immediateThumbnailData: featuredEmojiPack.info.immediateThumbnailData,
isReaction: false,
isTemplate: false
)
} else {
animationData = EntityKeyboardAnimationData(file: item.file)
}
var tintMode: Item.TintMode = .none
if item.file.isCustomTemplateEmoji {
tintMode = .primary
}
let resultItem = EmojiPagerContentComponent.Item(
animationData: animationData,
content: .animation(animationData),
itemFile: item.file,
subgroupId: nil,
icon: .none,
tintMode: tintMode
)
let supergroupId = "featuredTop"
let groupId: AnyHashable = supergroupId
let isPremiumLocked: Bool = item.file.isPremiumSticker && !hasPremium
if isPremiumLocked && isPremiumDisabled {
continue
}
if let groupIndex = itemGroupIndexById[groupId] {
itemGroups[groupIndex].items.append(resultItem)
} else {
itemGroupIndexById[groupId] = itemGroups.count
let title = strings.EmojiInput_TrendingEmoji
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: title, subtitle: nil, badge: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: 0, isClearable: false, headerItem: nil, items: [resultItem]))
}
}
}
var recentEmoji: OrderedItemListView?
var featuredStatusEmoji: OrderedItemListView?
var featuredChannelStatusEmoji: OrderedItemListView?
var disabledChannelStatusEmoji: OrderedItemListView?
var recentStatusEmoji: OrderedItemListView?
var topReactions: OrderedItemListView?
var recentReactions: OrderedItemListView?
var featuredAvatarEmoji: OrderedItemListView?
var featuredBackgroundIconEmoji: OrderedItemListView?
var defaultTagReactions: OrderedItemListView?
var uniqueGifts: OrderedItemListView?
for orderedView in view.orderedItemListsViews {
if orderedView.collectionId == Namespaces.OrderedItemList.CloudUniqueStarGifts {
uniqueGifts = orderedView
} else if orderedView.collectionId == Namespaces.OrderedItemList.LocalRecentEmoji {
recentEmoji = orderedView
} else if orderedView.collectionId == Namespaces.OrderedItemList.CloudFeaturedStatusEmoji {
featuredStatusEmoji = orderedView
} else if orderedView.collectionId == Namespaces.OrderedItemList.CloudFeaturedChannelStatusEmoji {
featuredChannelStatusEmoji = orderedView
} else if orderedView.collectionId == Namespaces.OrderedItemList.CloudDisabledChannelStatusEmoji {
disabledChannelStatusEmoji = orderedView
} else if orderedView.collectionId == Namespaces.OrderedItemList.CloudRecentStatusEmoji {
recentStatusEmoji = orderedView
} else if orderedView.collectionId == Namespaces.OrderedItemList.CloudRecentReactions {
recentReactions = orderedView
} else if orderedView.collectionId == Namespaces.OrderedItemList.CloudTopReactions {
topReactions = orderedView
} else if orderedView.collectionId == Namespaces.OrderedItemList.CloudFeaturedProfilePhotoEmoji {
featuredAvatarEmoji = orderedView
} else if orderedView.collectionId == Namespaces.OrderedItemList.CloudFeaturedGroupPhotoEmoji {
featuredAvatarEmoji = orderedView
} else if orderedView.collectionId == Namespaces.OrderedItemList.CloudFeaturedBackgroundIconEmoji {
featuredBackgroundIconEmoji = orderedView
} else if orderedView.collectionId == Namespaces.OrderedItemList.CloudDefaultTagReactions {
defaultTagReactions = orderedView
}
}
if case .stickerAlt = subject {
for emoji in topEmojiItems {
let resultItem = EmojiPagerContentComponent.Item(
animationData: nil,
content: .staticEmoji(emoji),
itemFile: nil,
subgroupId: nil,
icon: .none,
tintMode: .none
)
let groupId = "recent"
if let groupIndex = itemGroupIndexById[groupId] {
itemGroups[groupIndex].items.append(resultItem)
} else {
itemGroupIndexById[groupId] = itemGroups.count
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: nil, subtitle: nil, badge: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: 5, isClearable: false, headerItem: nil, items: [resultItem]))
}
}
} else if case .topicIcon = subject {
let resultItem = EmojiPagerContentComponent.Item(
animationData: nil,
content: .icon(.topic(String((topicTitle ?? "").prefix(1)), topicColor ?? 0)),
itemFile: nil,
subgroupId: nil,
icon: .none,
tintMode: .none
)
let groupId = "recent"
if let groupIndex = itemGroupIndexById[groupId] {
itemGroups[groupIndex].items.append(resultItem)
} else {
itemGroupIndexById[groupId] = itemGroups.count
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: nil, subtitle: nil, badge: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: 5, isClearable: false, headerItem: nil, items: [resultItem]))
}
var existingIds = Set<MediaId>()
for file in iconStatusEmoji {
if existingIds.contains(file.fileId) {
continue
}
existingIds.insert(file.fileId)
var tintMode: Item.TintMode = .none
if file.isCustomTemplateEmoji {
tintMode = .accent
}
for attribute in file.attributes {
if case let .CustomEmoji(_, _, _, packReference) = attribute {
switch packReference {
case let .id(id, _):
if id == 773947703670341676 || id == 2964141614563343 {
tintMode = .accent
}
default:
break
}
}
}
let resultItem: EmojiPagerContentComponent.Item
let animationData = EntityKeyboardAnimationData(file: TelegramMediaFile.Accessor(file))
resultItem = EmojiPagerContentComponent.Item(
animationData: animationData,
content: .animation(animationData),
itemFile: TelegramMediaFile.Accessor(file),
subgroupId: nil,
icon: .none,
tintMode: tintMode
)
if let groupIndex = itemGroupIndexById[groupId] {
itemGroups[groupIndex].items.append(resultItem)
}
}
} else if case .status = subject {
let resultItem = EmojiPagerContentComponent.Item(
animationData: nil,
content: .icon(.premiumStar),
itemFile: nil,
subgroupId: nil,
icon: .none,
tintMode: .none
)
let groupId = "recent"
if let groupIndex = itemGroupIndexById[groupId] {
itemGroups[groupIndex].items.append(resultItem)
} else {
itemGroupIndexById[groupId] = itemGroups.count
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: topStatusTitle?.uppercased(), subtitle: nil, badge: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: 5, isClearable: false, headerItem: nil, items: [resultItem]))
}
var existingIds = Set<MediaId>()
for file in iconStatusEmoji.prefix(7) {
if existingIds.contains(file.fileId) {
continue
}
existingIds.insert(file.fileId)
var tintMode: Item.TintMode = .none
if file.isCustomTemplateEmoji {
tintMode = .accent
}
for attribute in file.attributes {
if case let .CustomEmoji(_, _, _, packReference) = attribute {
switch packReference {
case let .id(id, _):
if id == 773947703670341676 || id == 2964141614563343 {
tintMode = .accent
}
default:
break
}
}
}
let resultItem: EmojiPagerContentComponent.Item
let animationData = EntityKeyboardAnimationData(file: TelegramMediaFile.Accessor(file))
resultItem = EmojiPagerContentComponent.Item(
animationData: animationData,
content: .animation(animationData),
itemFile: TelegramMediaFile.Accessor(file),
subgroupId: nil,
icon: .none,
tintMode: tintMode
)
if let groupIndex = itemGroupIndexById[groupId] {
itemGroups[groupIndex].items.append(resultItem)
}
}
if let recentStatusEmoji = recentStatusEmoji {
for item in recentStatusEmoji.items {
guard let item = item.contents.get(RecentMediaItem.self) else {
continue
}
let file = item.media
if existingIds.contains(file.fileId) {
continue
}
existingIds.insert(file.fileId)
var tintMode: Item.TintMode = .none
if file.isCustomTemplateEmoji {
tintMode = .accent
}
if file.internal_isHardcodedTemplateEmoji {
tintMode = .accent
}
let resultItem: EmojiPagerContentComponent.Item
let animationData = EntityKeyboardAnimationData(file: file)
resultItem = EmojiPagerContentComponent.Item(
animationData: animationData,
content: .animation(animationData),
itemFile: file,
subgroupId: nil,
icon: .none,
tintMode: tintMode
)
if let groupIndex = itemGroupIndexById[groupId] {
if itemGroups[groupIndex].items.count >= (5 + 8) * 8 {
break
}
itemGroups[groupIndex].items.append(resultItem)
}
}
}
if let featuredStatusEmoji = featuredStatusEmoji {
for item in featuredStatusEmoji.items {
guard let item = item.contents.get(RecentMediaItem.self) else {
continue
}
let file = item.media
if existingIds.contains(file.fileId) {
continue
}
existingIds.insert(file.fileId)
let resultItem: EmojiPagerContentComponent.Item
var tintMode: Item.TintMode = .none
if file.isCustomTemplateEmoji {
tintMode = .accent
}
if file.internal_isHardcodedTemplateEmoji {
tintMode = .accent
}
let animationData = EntityKeyboardAnimationData(file: file)
resultItem = EmojiPagerContentComponent.Item(
animationData: animationData,
content: .animation(animationData),
itemFile: file,
subgroupId: nil,
icon: .none,
tintMode: tintMode
)
if let groupIndex = itemGroupIndexById[groupId] {
if itemGroups[groupIndex].items.count >= (5 + 8) * 8 {
break
}
itemGroups[groupIndex].items.append(resultItem)
}
}
}
if let uniqueGifts, !uniqueGifts.items.isEmpty {
let groupId = "collectible"
let groupIndex: Int
if let current = itemGroupIndexById[groupId] {
groupIndex = current
} else {
groupIndex = itemGroups.count
itemGroupIndexById[groupId] = groupIndex
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: strings.EmojiInput_SectionTitleCollectibles.uppercased(), subtitle: nil, badge: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: 2, isClearable: false, headerItem: nil, items: []))
}
for item in uniqueGifts.items {
guard let item = item.contents.get(RecentStarGiftItem.self) else {
continue
}
guard let animationData = EntityKeyboardAnimationData(gift: item.starGift) else {
continue
}
let resultItem = EmojiPagerContentComponent.Item(
animationData: animationData,
content: .animation(animationData),
itemFile: nil,
itemGift: item.starGift,
subgroupId: nil,
icon: .none,
tintMode: .none
)
itemGroups[groupIndex].items.append(resultItem)
}
}
} else if case .channelStatus = subject {
let resultItem = EmojiPagerContentComponent.Item(
animationData: nil,
content: .icon(.stop),
itemFile: nil,
subgroupId: nil,
icon: .none,
tintMode: .accent
)
let groupId = "recent"
if let groupIndex = itemGroupIndexById[groupId] {
itemGroups[groupIndex].items.append(resultItem)
} else {
itemGroupIndexById[groupId] = itemGroups.count
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: topStatusTitle?.uppercased(), subtitle: nil, badge: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: 5, isClearable: false, headerItem: nil, items: [resultItem]))
}
var existingIds = Set<MediaId>()
if let disabledChannelStatusEmoji {
for item in disabledChannelStatusEmoji.items {
guard let item = item.contents.get(RecentMediaItem.self) else {
continue
}
let file = item.media
existingIds.insert(file.fileId)
}
}
for file in iconStatusEmoji.prefix(7) {
if existingIds.contains(file.fileId) {
continue
}
existingIds.insert(file.fileId)
var tintMode: Item.TintMode = .none
if file.isCustomTemplateEmoji {
tintMode = .accent
}
for attribute in file.attributes {
if case let .CustomEmoji(_, _, _, packReference) = attribute {
switch packReference {
case let .id(id, _):
if id == 773947703670341676 || id == 2964141614563343 {
tintMode = .accent
}
default:
break
}
}
}
let resultItem: EmojiPagerContentComponent.Item
let animationData = EntityKeyboardAnimationData(file: TelegramMediaFile.Accessor(file))
resultItem = EmojiPagerContentComponent.Item(
animationData: animationData,
content: .animation(animationData),
itemFile: TelegramMediaFile.Accessor(file),
subgroupId: nil,
icon: .none,
tintMode: tintMode
)
if let groupIndex = itemGroupIndexById[groupId] {
itemGroups[groupIndex].items.append(resultItem)
}
}
if let featuredChannelStatusEmoji {
for item in featuredChannelStatusEmoji.items {
guard let item = item.contents.get(RecentMediaItem.self) else {
continue
}
let file = item.media
if existingIds.contains(file.fileId) {
continue
}
existingIds.insert(file.fileId)
let resultItem: EmojiPagerContentComponent.Item
var tintMode: Item.TintMode = .none
if file.isCustomTemplateEmoji {
tintMode = .accent
}
if file.internal_isHardcodedTemplateEmoji {
tintMode = .accent
}
let animationData = EntityKeyboardAnimationData(file: file)
resultItem = EmojiPagerContentComponent.Item(
animationData: animationData,
content: .animation(animationData),
itemFile: file,
subgroupId: nil,
icon: .none,
tintMode: tintMode
)
if let groupIndex = itemGroupIndexById[groupId] {
itemGroups[groupIndex].items.append(resultItem)
}
}
}
} else if case .reactionList = subject {
var existingIds = Set<MessageReaction.Reaction>()
if let availableReactions = availableReactions {
for reactionItem in availableReactions.reactions {
if !reactionItem.isEnabled {
continue
}
if existingIds.contains(reactionItem.value) {
continue
}
existingIds.insert(reactionItem.value)
let icon: EmojiPagerContentComponent.Item.Icon
if !hasPremium, case .custom = reactionItem.value {
icon = .locked
} else {
icon = .none
}
var tintMode: Item.TintMode = .none
if reactionItem.selectAnimation.isCustomTemplateEmoji {
tintMode = .primary
}
let animationFile = reactionItem.selectAnimation
let animationData = EntityKeyboardAnimationData(file: animationFile, isReaction: true)
let resultItem = EmojiPagerContentComponent.Item(
animationData: animationData,
content: .animation(animationData),
itemFile: animationFile,
subgroupId: nil,
icon: icon,
tintMode: tintMode
)
let groupId = "liked"
if let groupIndex = itemGroupIndexById[groupId] {
itemGroups[groupIndex].items.append(resultItem)
} else {
itemGroupIndexById[groupId] = itemGroups.count
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: nil, subtitle: nil, badge: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: nil, isClearable: false, headerItem: nil, items: [resultItem]))
}
}
}
} else if [.reaction(onlyTop: true), .reaction(onlyTop: false), .quickReaction].contains(subject) {
var existingIds = Set<MessageReaction.Reaction>()
var topReactionItems = topReactionItems
if topReactionItems.isEmpty {
if let topReactions = topReactions {
for item in topReactions.items {
guard let topReaction = item.contents.get(RecentReactionItem.self) else {
continue
}
switch topReaction.content {
case let .builtin(value):
if let reaction = availableReactions?.reactions.first(where: { $0.value == .builtin(value) }) {
topReactionItems.append(EmojiComponentReactionItem(reaction: .builtin(value), file: reaction.selectAnimation))
} else {
continue
}
case let .custom(file):
topReactionItems.append(EmojiComponentReactionItem(reaction: .custom(file.fileId.id), file: file))
case .stars:
if let reaction = availableReactions?.reactions.first(where: { $0.value == .stars }) {
topReactionItems.append(EmojiComponentReactionItem(reaction: .stars, file: reaction.selectAnimation))
}
}
}
}
}
let maxTopLineCount: Int
if case .reaction(onlyTop: true) = subject {
maxTopLineCount = 1000
} else if hasPremium {
maxTopLineCount = 2
} else {
maxTopLineCount = 6
}
for reactionItem in topReactionItems {
if existingIds.contains(reactionItem.reaction) {
continue
}
existingIds.insert(reactionItem.reaction)
let icon: EmojiPagerContentComponent.Item.Icon
if case .reaction(onlyTop: true) = subject {
icon = .none
} else if !hasPremium, case .custom = reactionItem.reaction {
icon = .locked
} else {
icon = .none
}
var tintMode: Item.TintMode = .none
if reactionItem.file.isCustomTemplateEmoji {
tintMode = .primary
}
let animationFile = reactionItem.file
let animationData = EntityKeyboardAnimationData(file: animationFile, isReaction: true)
let resultItem = EmojiPagerContentComponent.Item(
animationData: animationData,
content: .animation(animationData),
itemFile: animationFile,
subgroupId: nil,
icon: icon,
tintMode: tintMode
)
let groupId = "recent"
if let groupIndex = itemGroupIndexById[groupId] {
itemGroups[groupIndex].items.append(resultItem)
if itemGroups[groupIndex].items.count >= 8 * maxTopLineCount {
break
}
} else {
itemGroupIndexById[groupId] = itemGroups.count
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: nil, subtitle: nil, badge: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: nil, isClearable: false, headerItem: nil, items: [resultItem]))
}
}
if case .reaction(onlyTop: false) = subject {
var hasRecent = false
if let recentReactions = recentReactions, !recentReactions.items.isEmpty {
hasRecent = true
}
let maxRecentLineCount: Int
if hasPremium {
maxRecentLineCount = 10
} else {
maxRecentLineCount = 10
}
let popularTitle = hasRecent ? strings.Chat_ReactionSection_Recent : strings.Chat_ReactionSection_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 icon: EmojiPagerContentComponent.Item.Icon
if !hasPremium, case .custom = reactionItem.value {
icon = .locked
} else {
icon = .none
}
var tintMode: Item.TintMode = .none
if reactionItem.selectAnimation.isCustomTemplateEmoji {
tintMode = .primary
}
let animationFile = reactionItem.selectAnimation
let animationData = EntityKeyboardAnimationData(file: animationFile, isReaction: true)
let resultItem = EmojiPagerContentComponent.Item(
animationData: animationData,
content: .animation(animationData),
itemFile: animationFile,
subgroupId: nil,
icon: icon,
tintMode: tintMode
)
if hasPremium {
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, badge: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: nil, isClearable: hasRecent && subject != .quickReaction, headerItem: nil, items: [resultItem]))
}
} else {
let groupId = "recent"
if let groupIndex = itemGroupIndexById[groupId] {
itemGroups[groupIndex].items.append(resultItem)
if itemGroups[groupIndex].items.count >= maxRecentLineCount * 8 {
break
}
} else {
itemGroupIndexById[groupId] = itemGroups.count
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: nil, subtitle: nil, badge: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: nil, isClearable: false, headerItem: nil, items: [resultItem]))
}
}
}
}
if let recentReactions = recentReactions {
var popularInsertIndex = 0
for item in recentReactions.items {
guard let item = item.contents.get(RecentReactionItem.self) else {
continue
}
let animationFile: TelegramMediaFile.Accessor
let icon: EmojiPagerContentComponent.Item.Icon
switch item.content {
case let .builtin(value):
if existingIds.contains(.builtin(value)) {
continue
}
existingIds.insert(.builtin(value))
if let availableReactions = availableReactions, let availableReaction = availableReactions.reactions.first(where: { $0.value == .builtin(value) }) {
if let centerAnimation = availableReaction.centerAnimation {
animationFile = centerAnimation
} else {
continue
}
} else {
continue
}
icon = .none
case let .custom(file):
if existingIds.contains(.custom(file.fileId.id)) {
continue
}
existingIds.insert(.custom(file.fileId.id))
animationFile = file
if !hasPremium {
icon = .locked
} else {
icon = .none
}
case .stars:
if existingIds.contains(.stars) {
continue
}
existingIds.insert(.stars)
if let availableReactions = availableReactions, let availableReaction = availableReactions.reactions.first(where: { $0.value == .stars }) {
if let centerAnimation = availableReaction.centerAnimation {
animationFile = centerAnimation
} else {
continue
}
} else {
continue
}
icon = .none
}
var tintMode: Item.TintMode = .none
if animationFile.isCustomTemplateEmoji {
tintMode = .primary
}
let animationData = EntityKeyboardAnimationData(file: animationFile, isReaction: true)
let resultItem = EmojiPagerContentComponent.Item(
animationData: animationData,
content: .animation(animationData),
itemFile: animationFile,
subgroupId: nil,
icon: icon,
tintMode: tintMode
)
let groupId = "popular"
if let groupIndex = itemGroupIndexById[groupId] {
if itemGroups[groupIndex].items.count + 1 >= maxRecentLineCount * 8 {
break
}
itemGroups[groupIndex].items.insert(resultItem, at: popularInsertIndex)
popularInsertIndex += 1
} else {
itemGroupIndexById[groupId] = itemGroups.count
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: popularTitle, subtitle: nil, badge: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: nil, isClearable: hasRecent && subject != .quickReaction, headerItem: nil, items: [resultItem]))
}
}
}
}
} else if case .messageTag = subject {
var existingIds = Set<MessageReaction.Reaction>()
var topReactionItems = topReactionItems
if topReactionItems.isEmpty {
if let defaultTagReactions {
for item in defaultTagReactions.items {
guard let topReaction = item.contents.get(RecentReactionItem.self) else {
continue
}
switch topReaction.content {
case let .builtin(value):
if let reaction = availableReactions?.reactions.first(where: { $0.value == .builtin(value) }) {
topReactionItems.append(EmojiComponentReactionItem(reaction: .builtin(value), file: reaction.selectAnimation))
} else {
continue
}
case let .custom(file):
topReactionItems.append(EmojiComponentReactionItem(reaction: .custom(file.fileId.id), file: file))
case .stars:
if let reaction = availableReactions?.reactions.first(where: { $0.value == .stars }) {
topReactionItems.append(EmojiComponentReactionItem(reaction: .stars, file: reaction.selectAnimation))
} else {
continue
}
}
}
}
}
let maxTopLineCount: Int = 1000
for reactionItem in topReactionItems {
if existingIds.contains(reactionItem.reaction) {
continue
}
existingIds.insert(reactionItem.reaction)
let icon: EmojiPagerContentComponent.Item.Icon = .none
var tintMode: Item.TintMode = .none
if reactionItem.file.isCustomTemplateEmoji {
tintMode = .primary
}
let animationFile = reactionItem.file
let animationData = EntityKeyboardAnimationData(file: animationFile, isReaction: true)
let resultItem = EmojiPagerContentComponent.Item(
animationData: animationData,
content: .animation(animationData),
itemFile: animationFile,
subgroupId: nil,
icon: icon,
tintMode: tintMode
)
let groupId = "recent"
if let groupIndex = itemGroupIndexById[groupId] {
itemGroups[groupIndex].items.append(resultItem)
if itemGroups[groupIndex].items.count >= 8 * maxTopLineCount {
break
}
} else {
itemGroupIndexById[groupId] = itemGroups.count
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: nil, subtitle: nil, badge: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: nil, isClearable: false, headerItem: nil, items: [resultItem]))
}
}
} else if [.profilePhoto, .groupPhoto].contains(subject) {
var existingIds = Set<MediaId>()
let groupId = "recent"
itemGroupIndexById[groupId] = itemGroups.count
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: topStatusTitle?.uppercased(), subtitle: nil, badge: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: 5, isClearable: false, headerItem: nil, items: []))
if let featuredAvatarEmoji = featuredAvatarEmoji {
for item in featuredAvatarEmoji.items {
guard let item = item.contents.get(RecentMediaItem.self) else {
continue
}
let file = item.media
if existingIds.contains(file.fileId) {
continue
}
existingIds.insert(file.fileId)
let resultItem: EmojiPagerContentComponent.Item
var tintMode: Item.TintMode = .none
if file.isCustomTemplateEmoji {
tintMode = .accent
}
if file.internal_isHardcodedTemplateEmoji {
tintMode = .accent
}
let animationData = EntityKeyboardAnimationData(file: file)
resultItem = EmojiPagerContentComponent.Item(
animationData: animationData,
content: .animation(animationData),
itemFile: file,
subgroupId: nil,
icon: .none,
tintMode: tintMode
)
if let groupIndex = itemGroupIndexById[groupId] {
if itemGroups[groupIndex].items.count >= (5 + 8) * 8 {
break
}
itemGroups[groupIndex].items.append(resultItem)
}
}
}
} else if case .backgroundIcon = subject {
var existingIds = Set<MediaId>()
let resultItem = EmojiPagerContentComponent.Item(
animationData: nil,
content: .icon(.stop),
itemFile: nil,
subgroupId: nil,
icon: .none,
tintMode: .accent
)
let groupId = "recent"
if let groupIndex = itemGroupIndexById[groupId] {
itemGroups[groupIndex].items.append(resultItem)
} else {
itemGroupIndexById[groupId] = itemGroups.count
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: nil, subtitle: nil, badge: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: 5, isClearable: false, headerItem: nil, items: [resultItem]))
}
if let featuredBackgroundIconEmoji {
for item in featuredBackgroundIconEmoji.items {
guard let item = item.contents.get(RecentMediaItem.self) else {
continue
}
let file = item.media
if existingIds.contains(file.fileId) {
continue
}
existingIds.insert(file.fileId)
let resultItem: EmojiPagerContentComponent.Item
var tintMode: Item.TintMode = .none
if file.isCustomTemplateEmoji {
if let backgroundIconColor {
tintMode = .custom(backgroundIconColor)
} else {
tintMode = .accent
}
}
if file.internal_isHardcodedTemplateEmoji {
tintMode = .accent
}
let animationData = EntityKeyboardAnimationData(file: file)
resultItem = EmojiPagerContentComponent.Item(
animationData: animationData,
content: .animation(animationData),
itemFile: file,
subgroupId: nil,
icon: .none,
tintMode: tintMode
)
if let groupIndex = itemGroupIndexById[groupId] {
if itemGroups[groupIndex].items.count >= (5 + 8) * 8 {
break
}
itemGroups[groupIndex].items.append(resultItem)
}
}
}
}
let hasRecentEmoji = ![.reaction(onlyTop: true), .reaction(onlyTop: false), .quickReaction, .status, .profilePhoto, .groupPhoto, .topicIcon, .backgroundIcon, .reactionList, .messageTag, .stickerAlt].contains(subject)
if let recentEmoji = recentEmoji, hasRecentEmoji {
for item in recentEmoji.items {
guard let item = item.contents.get(RecentEmojiItem.self) else {
continue
}
if case let .file(file) = item.content, isPremiumDisabled, file.isPremiumEmoji {
continue
}
if !areCustomEmojiEnabled, case .file = item.content {
continue
}
let resultItem: EmojiPagerContentComponent.Item
switch item.content {
case let .file(file):
var tintMode: Item.TintMode = .none
if file.isCustomTemplateEmoji {
tintMode = .primary
}
let animationData = EntityKeyboardAnimationData(file: TelegramMediaFile.Accessor(file))
resultItem = EmojiPagerContentComponent.Item(
animationData: animationData,
content: .animation(animationData),
itemFile: TelegramMediaFile.Accessor(file),
subgroupId: nil,
icon: .none,
tintMode: tintMode
)
case let .text(text):
resultItem = EmojiPagerContentComponent.Item(
animationData: nil,
content: .staticEmoji(text),
itemFile: nil,
subgroupId: nil,
icon: .none,
tintMode: .none
)
}
let groupId = "recent"
if let groupIndex = itemGroupIndexById[groupId] {
itemGroups[groupIndex].items.append(resultItem)
} else {
itemGroupIndexById[groupId] = itemGroups.count
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: strings.Emoji_FrequentlyUsed, subtitle: nil, badge: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: nil, isClearable: true, headerItem: nil, items: [resultItem]))
}
}
}
var itemCollectionMapping: [ItemCollectionId: StickerPackCollectionInfo] = [:]
for (id, info, _) in view.collectionInfos {
if let info = info as? StickerPackCollectionInfo {
itemCollectionMapping[id] = info
}
}
var skippedCollectionIds = Set<AnyHashable>()
var avatarPeer: EnginePeer?
if let peerSpecificPack = peerSpecificPack {
avatarPeer = peerSpecificPack.peer
var processedIds = Set<MediaId>()
for item in peerSpecificPack.items {
if isPremiumDisabled && item.file.isPremiumSticker {
continue
}
if processedIds.contains(item.file.fileId) {
continue
}
processedIds.insert(item.file.fileId)
var tintMode: Item.TintMode = .none
if item.file.isCustomTemplateEmoji {
tintMode = .primary
}
let animationData = EntityKeyboardAnimationData(file: item.file)
let resultItem = EmojiPagerContentComponent.Item(
animationData: animationData,
content: .animation(animationData),
itemFile: item.file,
subgroupId: nil,
icon: .none,
tintMode: tintMode
)
let groupId = "peerSpecific"
if let groupIndex = itemGroupIndexById[groupId] {
itemGroups[groupIndex].items.append(resultItem)
} else {
itemGroupIndexById[groupId] = itemGroups.count
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: peerSpecificPack.info.title, subtitle: nil, badge: strings.Emoji_GroupEmoji, isPremiumLocked: false, isFeatured: false, collapsedLineCount: nil, isClearable: false, headerItem: nil, items: [resultItem]))
}
}
let supergroupId: AnyHashable = peerSpecificPack.info.id
skippedCollectionIds.insert(supergroupId)
}
if !hasPremium {
maybeAppendUnicodeEmoji()
}
if areCustomEmojiEnabled {
for entry in view.entries {
guard let item = entry.item as? StickerPackItem else {
continue
}
var icon: EmojiPagerContentComponent.Item.Icon = .none
if [.reaction(onlyTop: false), .quickReaction].contains(subject), !hasPremium {
icon = .locked
}
let supergroupId = entry.index.collectionId
let groupId: AnyHashable = supergroupId
if skippedCollectionIds.contains(groupId) {
continue
}
if case .channelStatus = subject {
guard let collection = itemCollectionMapping[entry.index.collectionId] else {
continue
}
if !collection.flags.contains(.isAvailableAsChannelStatus) {
continue
}
}
var isTemplate = false
var tintMode: Item.TintMode = .none
if item.file.isCustomTemplateEmoji {
if [.status, .channelStatus, .backgroundIcon].contains(subject) {
if let backgroundIconColor {
tintMode = .custom(backgroundIconColor)
} else {
tintMode = .accent
}
} else {
tintMode = .primary
}
if case .backgroundIcon = subject {
isTemplate = true
}
} else if case .backgroundIcon = subject {
skippedCollectionIds.insert(groupId)
continue
}
let animationData = EntityKeyboardAnimationData(file: item.file)
let resultItem = EmojiPagerContentComponent.Item(
animationData: animationData,
content: .animation(animationData),
itemFile: item.file,
subgroupId: nil,
icon: icon,
tintMode: tintMode
)
let isPremiumLocked: Bool = item.file.isPremiumEmoji && !hasPremium
if isPremiumLocked && isPremiumDisabled {
continue
}
if let groupIndex = itemGroupIndexById[groupId] {
itemGroups[groupIndex].items.append(resultItem)
} else {
itemGroupIndexById[groupId] = itemGroups.count
var title = ""
var headerItem: EntityKeyboardAnimationData?
inner: for (id, info, _) in view.collectionInfos {
if id == entry.index.collectionId, let info = info as? StickerPackCollectionInfo {
title = info.title
if let thumbnail = info.thumbnail {
let type: EntityKeyboardAnimationData.ItemType
if item.file.isAnimatedSticker {
type = .lottie
} else if item.file.isVideoEmoji || item.file.isVideoSticker {
type = .video(isVP9: true)
} else if item.file.isVideo {
type = .video(isVP9: false)
} else {
type = .still
}
headerItem = EntityKeyboardAnimationData(
id: .stickerPackThumbnail(info.id),
type: type,
resource: .resource(.stickerPackThumbnail(stickerPack: .id(id: info.id.id, accessHash: info.accessHash), resource: thumbnail.resource)),
dimensions: thumbnail.dimensions.cgSize,
immediateThumbnailData: info.immediateThumbnailData,
isReaction: false,
isTemplate: isTemplate
)
}
break inner
}
}
itemGroups.append(ItemGroup(supergroupId: supergroupId, id: groupId, title: title, subtitle: nil, badge: nil, isPremiumLocked: isPremiumLocked, isFeatured: false, collapsedLineCount: nil, isClearable: false, headerItem: headerItem, items: [resultItem]))
}
}
if !isStandalone {
for featuredEmojiPack in featuredEmojiPacks {
if installedCollectionIds.contains(featuredEmojiPack.info.id) {
continue
}
let supergroupId = featuredEmojiPack.info.id
let groupId: AnyHashable = supergroupId
if skippedCollectionIds.contains(groupId) {
continue
}
if case .channelStatus = subject {
if !featuredEmojiPack.info.flags.contains(.isAvailableAsChannelStatus) {
continue
}
}
for item in featuredEmojiPack.topItems {
var tintMode: Item.TintMode = .none
if item.file.isCustomTemplateEmoji {
if [.status, .channelStatus, .backgroundIcon].contains(subject) {
if let backgroundIconColor {
tintMode = .custom(backgroundIconColor)
} else {
tintMode = .accent
}
} else {
tintMode = .primary
}
} else if case .backgroundIcon = subject {
skippedCollectionIds.insert(groupId)
continue
}
let animationData = EntityKeyboardAnimationData(file: item.file)
let resultItem = EmojiPagerContentComponent.Item(
animationData: animationData,
content: .animation(animationData),
itemFile: item.file,
subgroupId: nil,
icon: .none,
tintMode: tintMode
)
let isPremiumLocked: Bool = item.file.isPremiumEmoji && !hasPremium
if isPremiumLocked && isPremiumDisabled {
continue
}
if let groupIndex = itemGroupIndexById[groupId] {
itemGroups[groupIndex].items.append(resultItem)
} else {
itemGroupIndexById[groupId] = itemGroups.count
var headerItem: EntityKeyboardAnimationData?
if let thumbnailFileId = featuredEmojiPack.info.thumbnailFileId, let file = featuredEmojiPack.topItems.first(where: { $0.file.fileId.id == thumbnailFileId }) {
headerItem = EntityKeyboardAnimationData(file: file.file)
} else if let thumbnailDimensions = featuredEmojiPack.info.thumbnailDimensions {
let info = featuredEmojiPack.info
let type: EntityKeyboardAnimationData.ItemType
if item.file.isAnimatedSticker {
type = .lottie
} else if item.file.isVideoEmoji || item.file.isVideoSticker {
type = .video(isVP9: true)
} else if item.file.isVideo {
type = .video(isVP9: false)
} else {
type = .still
}
headerItem = EntityKeyboardAnimationData(
id: .stickerPackThumbnail(info.id),
type: type,
resource: .stickerPackThumbnail(id: featuredEmojiPack.info.id.id, accessHash: featuredEmojiPack.info.accessHash, info: featuredEmojiPack.info),
dimensions: thumbnailDimensions.cgSize,
immediateThumbnailData: info.immediateThumbnailData,
isReaction: false,
isTemplate: false
)
}
var isFeatured = true
if case .reactionList = subject {
isFeatured = false
}
itemGroups.append(ItemGroup(supergroupId: supergroupId, id: groupId, title: featuredEmojiPack.info.title, subtitle: nil, badge: nil, isPremiumLocked: isPremiumLocked, isFeatured: isFeatured, collapsedLineCount: 3, isClearable: false, headerItem: headerItem, items: [resultItem]))
}
}
}
}
}
if hasPremium {
maybeAppendUnicodeEmoji()
}
var displaySearchWithPlaceholder: String?
let searchInitiallyHidden = true
if hasSearch {
if [.reaction(onlyTop: false), .quickReaction, .messageTag].contains(subject) {
displaySearchWithPlaceholder = strings.EmojiSearch_SearchReactionsPlaceholder
} else if case .status = subject {
displaySearchWithPlaceholder = strings.EmojiSearch_SearchStatusesPlaceholder
} else if case .emoji = subject {
displaySearchWithPlaceholder = strings.EmojiSearch_SearchEmojiPlaceholder
} else if [.profilePhoto, .groupPhoto].contains(subject) {
displaySearchWithPlaceholder = strings.Common_Search
} else if case .stickerAlt = subject {
displaySearchWithPlaceholder = strings.Common_Search
} else if case .reactionList = subject {
displaySearchWithPlaceholder = strings.Common_Search
}
}
let allItemGroups = itemGroups.map { group -> EmojiPagerContentComponent.ItemGroup in
var hasClear = group.isClearable
var isEmbedded = false
if group.id == AnyHashable("featuredTop") {
hasClear = true
isEmbedded = true
}
var headerItem = group.headerItem
if let groupId = group.id.base as? ItemCollectionId {
outer: for (id, info, _) in view.collectionInfos {
if id == groupId, let info = info as? StickerPackCollectionInfo {
if let thumbnailFileId = info.thumbnailFileId {
for item in group.items {
if let itemFile = item.itemFile, itemFile.fileId.id == thumbnailFileId {
headerItem = EntityKeyboardAnimationData(file: itemFile)
break outer
}
}
}
}
}
}
return EmojiPagerContentComponent.ItemGroup(
supergroupId: group.supergroupId,
groupId: group.id,
title: group.title,
subtitle: group.subtitle,
badge: group.badge,
actionButtonTitle: nil,
isFeatured: group.isFeatured,
isPremiumLocked: group.isPremiumLocked,
isEmbedded: isEmbedded,
hasClear: hasClear,
hasEdit: false,
collapsedLineCount: group.collapsedLineCount,
displayPremiumBadges: false,
headerItem: headerItem,
fillWithLoadingPlaceholders: false,
customTintColor: backgroundIconColor,
items: group.items
)
}
let warpContentsOnEdges = [.reaction(onlyTop: true), .reaction(onlyTop: false), .quickReaction, .status, .channelStatus, .profilePhoto, .groupPhoto, .backgroundIcon, .messageTag].contains(subject)
let enableLongPress = [.reaction(onlyTop: true), .reaction(onlyTop: false), .status, .channelStatus].contains(subject)
return EmojiPagerContentComponent(
id: "emoji",
context: context,
avatarPeer: avatarPeer,
animationCache: animationCache,
animationRenderer: animationRenderer,
inputInteractionHolder: EmojiPagerContentComponent.InputInteractionHolder(),
panelItemGroups: allItemGroups,
contentItemGroups: allItemGroups,
itemLayoutType: .compact,
itemContentUniqueId: nil,
searchState: .empty(hasResults: false),
warpContentsOnEdges: warpContentsOnEdges,
hideBackground: hideBackground,
maskEdge: maskEdge,
displaySearchWithPlaceholder: displaySearchWithPlaceholder,
searchCategories: searchCategories,
searchInitiallyHidden: searchInitiallyHidden,
searchAlwaysActive: false,
searchIsPlaceholderOnly: false,
searchUnicodeEmojiOnly: subject == .stickerAlt,
emptySearchResults: nil,
enableLongPress: enableLongPress,
selectedItems: selectedItems,
customTintColor: backgroundIconColor
)
}
return emojiItems
}
enum StickersSubject {
case profilePhotoEmojiSelection
case groupPhotoEmojiSelection
case chatStickers
case greetingStickers
}
static func stickerInputData(
context: AccountContext,
animationCache: AnimationCache,
animationRenderer: MultiAnimationRenderer,
stickerNamespaces: [ItemCollectionId.Namespace],
stickerOrderedItemListCollectionIds: [Int32],
chatPeerId: EnginePeer.Id?,
hasSearch: Bool,
hasTrending: Bool,
forceHasPremium: Bool,
hasEdit: Bool = false,
hasAdd: Bool = false,
searchIsPlaceholderOnly: Bool = true,
subject: StickersSubject = .chatStickers,
hideBackground: Bool = false,
maskEdge: EmojiPagerContentComponent.MaskEdgeMode = .none
) -> Signal<EmojiPagerContentComponent, NoError> {
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
let isPremiumDisabled = premiumConfiguration.isPremiumDisabled
struct PeerSpecificPackData: Equatable {
var info: StickerPackCollectionInfo.Accessor
var items: [StickerPackItem]
var peer: EnginePeer
static func ==(lhs: PeerSpecificPackData, rhs: PeerSpecificPackData) -> Bool {
if lhs.info.id != rhs.info.id {
return false
}
if lhs.items != rhs.items {
return false
}
if lhs.peer != rhs.peer {
return false
}
return true
}
}
let peerSpecificPack: Signal<PeerSpecificPackData?, NoError>
if let chatPeerId = chatPeerId {
peerSpecificPack = combineLatest(
context.engine.peers.peerSpecificStickerPack(peerId: chatPeerId),
context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: chatPeerId))
)
|> map { packData, peer -> PeerSpecificPackData? in
guard let peer = peer else {
return nil
}
guard let (info, items) = packData.packInfo else {
return nil
}
return PeerSpecificPackData(info: info, items: items.compactMap { $0 as? StickerPackItem }, peer: peer)
}
|> distinctUntilChanged
} else {
peerSpecificPack = .single(nil)
}
let strings = context.sharedContext.currentPresentationData.with({ $0 }).strings
let searchCategories: Signal<EmojiSearchCategories?, NoError>
switch subject {
case .groupPhotoEmojiSelection, .profilePhotoEmojiSelection:
searchCategories = context.engine.stickers.emojiSearchCategories(kind: .avatar)
case .chatStickers, .greetingStickers:
searchCategories = context.engine.stickers.emojiSearchCategories(kind: .combinedChatStickers)
|> map { result -> EmojiSearchCategories? in
guard let result else {
return nil
}
var groups: [EmojiSearchCategories.Group] = []
groups = result.groups
if case .greetingStickers = subject {
if let index = groups.firstIndex(where: { group in
return group.kind == .greeting
}) {
let group = groups.remove(at: index)
groups.insert(group, at: 0)
}
} else if case .chatStickers = subject {
if let index = groups.firstIndex(where: { group in
return group.kind == .premium
}) {
let group = groups.remove(at: index)
groups.append(group)
}
}
return EmojiSearchCategories(
hash: result.hash,
groups: groups
)
}
}
return combineLatest(
context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: stickerOrderedItemListCollectionIds, namespaces: stickerNamespaces, aroundIndex: nil, count: 10000000),
hasPremium(context: context, chatPeerId: chatPeerId, premiumIfSavedMessages: false),
hasTrending ? context.account.viewTracker.featuredStickerPacks() : .single([]),
context.engine.data.get(TelegramEngine.EngineData.Item.ItemCache.Item(collectionId: Namespaces.CachedItemCollection.featuredStickersConfiguration, id: ValueBoxKey(length: 0))),
ApplicationSpecificNotice.dismissedTrendingStickerPacks(accountManager: context.sharedContext.accountManager),
peerSpecificPack,
searchCategories
)
|> map { view, hasPremium, featuredStickerPacks, featuredStickersConfiguration, dismissedTrendingStickerPacks, peerSpecificPack, searchCategories -> EmojiPagerContentComponent in
let hasPremium = forceHasPremium || hasPremium
struct ItemGroup {
var supergroupId: AnyHashable
var id: AnyHashable
var title: String
var subtitle: String?
var actionButtonTitle: String?
var isPremiumLocked: Bool
var isFeatured: Bool
var displayPremiumBadges: Bool
var hasEdit: Bool
var headerItem: EntityKeyboardAnimationData?
var items: [EmojiPagerContentComponent.Item]
}
var itemGroups: [ItemGroup] = []
var itemGroupIndexById: [AnyHashable: Int] = [:]
var savedStickers: OrderedItemListView?
var recentStickers: OrderedItemListView?
for orderedView in view.orderedItemListsViews {
if orderedView.collectionId == Namespaces.OrderedItemList.CloudRecentStickers {
recentStickers = orderedView
} else if orderedView.collectionId == Namespaces.OrderedItemList.CloudSavedStickers {
savedStickers = orderedView
}
}
var installedCollectionIds = Set<ItemCollectionId>()
for (id, _, _) in view.collectionInfos {
installedCollectionIds.insert(id)
}
let dismissedTrendingStickerPacksSet = Set(dismissedTrendingStickerPacks ?? [])
let featuredStickerPacksSet = Set(featuredStickerPacks.map(\.info.id.id))
if dismissedTrendingStickerPacksSet != featuredStickerPacksSet {
let featuredStickersConfiguration = featuredStickersConfiguration?.get(FeaturedStickersConfiguration.self)
for featuredStickerPack in featuredStickerPacks {
if installedCollectionIds.contains(featuredStickerPack.info.id) {
continue
}
guard let item = featuredStickerPack.topItems.first else {
continue
}
let animationData: EntityKeyboardAnimationData
if let thumbnailDimensions = featuredStickerPack.info.thumbnailDimensions {
let type: EntityKeyboardAnimationData.ItemType
if item.file.isAnimatedSticker {
type = .lottie
} else if item.file.isVideoEmoji || item.file.isVideoSticker {
type = .video(isVP9: true)
} else if item.file.isVideo {
type = .video(isVP9: false)
} else {
type = .still
}
animationData = EntityKeyboardAnimationData(
id: .stickerPackThumbnail(featuredStickerPack.info.id),
type: type,
resource: .stickerPackThumbnail(id: featuredStickerPack.info.id.id, accessHash: featuredStickerPack.info.accessHash, info: featuredStickerPack.info),
dimensions: thumbnailDimensions.cgSize,
immediateThumbnailData: featuredStickerPack.info.immediateThumbnailData,
isReaction: false,
isTemplate: false
)
} else {
animationData = EntityKeyboardAnimationData(file: item.file)
}
var tintMode: Item.TintMode = .none
if item.file.isCustomTemplateEmoji {
tintMode = .primary
}
let resultItem = EmojiPagerContentComponent.Item(
animationData: animationData,
content: .animation(animationData),
itemFile: item.file,
subgroupId: nil,
icon: .none,
tintMode: tintMode
)
let supergroupId = "featuredTop"
let groupId: AnyHashable = supergroupId
let isPremiumLocked: Bool = item.file.isPremiumSticker && !hasPremium
if isPremiumLocked && isPremiumDisabled {
continue
}
if let groupIndex = itemGroupIndexById[groupId] {
itemGroups[groupIndex].items.append(resultItem)
} else {
itemGroupIndexById[groupId] = itemGroups.count
let trendingIsPremium = featuredStickersConfiguration?.isPremium ?? false
let title = trendingIsPremium ? strings.Stickers_TrendingPremiumStickers : strings.StickerPacksSettings_FeaturedPacks
itemGroups.append(
ItemGroup(
supergroupId: groupId,
id: groupId,
title: title,
subtitle: nil,
actionButtonTitle: nil,
isPremiumLocked: false,
isFeatured: false,
displayPremiumBadges: false,
hasEdit: false,
headerItem: nil,
items: [resultItem]
)
)
}
}
}
if let savedStickers = savedStickers {
let groupId = "saved"
for item in savedStickers.items {
guard let item = item.contents.get(SavedStickerItem.self) else {
continue
}
if isPremiumDisabled && item.file.isPremiumSticker {
continue
}
var tintMode: Item.TintMode = .none
if item.file.isCustomTemplateEmoji {
tintMode = .primary
}
let animationData = EntityKeyboardAnimationData(file: item.file, partialReference: .savedSticker)
let resultItem = EmojiPagerContentComponent.Item(
animationData: animationData,
content: .animation(animationData),
itemFile: item.file,
subgroupId: nil,
icon: .none,
tintMode: tintMode
)
if let groupIndex = itemGroupIndexById[groupId] {
itemGroups[groupIndex].items.append(resultItem)
} else {
itemGroupIndexById[groupId] = itemGroups.count
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: strings.EmojiInput_SectionTitleFavoriteStickers, subtitle: nil, actionButtonTitle: nil, isPremiumLocked: false, isFeatured: false, displayPremiumBadges: false, hasEdit: false, headerItem: nil, items: [resultItem]))
}
}
}
var addedCreateStickerButton = false
if let recentStickers = recentStickers {
let groupId = "recent"
for item in recentStickers.items {
guard let item = item.contents.get(RecentMediaItem.self) else {
continue
}
if isPremiumDisabled && item.media.isPremiumSticker {
continue
}
var tintMode: Item.TintMode = .none
if item.media.isCustomTemplateEmoji {
tintMode = .primary
}
let animationData = EntityKeyboardAnimationData(file: item.media, partialReference: .recentSticker)
let resultItem = EmojiPagerContentComponent.Item(
animationData: animationData,
content: .animation(animationData),
itemFile: item.media,
subgroupId: nil,
icon: .none,
tintMode: tintMode
)
if let groupIndex = itemGroupIndexById[groupId] {
itemGroups[groupIndex].items.append(resultItem)
} else {
itemGroupIndexById[groupId] = itemGroups.count
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: strings.Stickers_FrequentlyUsed, subtitle: nil, actionButtonTitle: nil, isPremiumLocked: false, isFeatured: false, displayPremiumBadges: false, hasEdit: false, headerItem: nil, items: [resultItem]))
}
}
if hasAdd && !addedCreateStickerButton, let groupIndex = itemGroupIndexById[groupId] {
let resultItem = EmojiPagerContentComponent.Item(
animationData: nil,
content: .icon(.add),
itemFile: nil,
subgroupId: nil,
icon: .none,
tintMode: .none
)
itemGroups[groupIndex].items.insert(resultItem, at: 0)
addedCreateStickerButton = true
}
}
var avatarPeer: EnginePeer?
if let peerSpecificPack = peerSpecificPack {
avatarPeer = peerSpecificPack.peer
var processedIds = Set<MediaId>()
let groupId = "peerSpecific"
for item in peerSpecificPack.items {
if isPremiumDisabled && item.file.isPremiumSticker {
continue
}
if processedIds.contains(item.file.fileId) {
continue
}
processedIds.insert(item.file.fileId)
var tintMode: Item.TintMode = .none
if item.file.isCustomTemplateEmoji {
tintMode = .primary
}
let animationData = EntityKeyboardAnimationData(file: item.file)
let resultItem = EmojiPagerContentComponent.Item(
animationData: animationData,
content: .animation(animationData),
itemFile: item.file,
subgroupId: nil,
icon: .none,
tintMode: tintMode
)
if let groupIndex = itemGroupIndexById[groupId] {
itemGroups[groupIndex].items.append(resultItem)
} else {
itemGroupIndexById[groupId] = itemGroups.count
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: peerSpecificPack.peer.compactDisplayTitle, subtitle: nil, actionButtonTitle: nil, isPremiumLocked: false, isFeatured: false, displayPremiumBadges: false, hasEdit: false, headerItem: nil, items: [resultItem]))
}
}
if hasEdit && !addedCreateStickerButton, let groupIndex = itemGroupIndexById[groupId] {
let resultItem = EmojiPagerContentComponent.Item(
animationData: nil,
content: .icon(.add),
itemFile: nil,
subgroupId: nil,
icon: .none,
tintMode: .none
)
itemGroups[groupIndex].items.insert(resultItem, at: 0)
addedCreateStickerButton = true
}
}
for entry in view.entries {
guard let item = entry.item as? StickerPackItem else {
continue
}
var tintMode: Item.TintMode = .none
if item.file.isCustomTemplateEmoji {
tintMode = .primary
}
let animationData = EntityKeyboardAnimationData(file: item.file)
let resultItem = EmojiPagerContentComponent.Item(
animationData: animationData,
content: .animation(animationData),
itemFile: item.file,
subgroupId: nil,
icon: .none,
tintMode: tintMode
)
let groupId = entry.index.collectionId
if let groupIndex = itemGroupIndexById[groupId] {
itemGroups[groupIndex].items.append(resultItem)
} else {
itemGroupIndexById[groupId] = itemGroups.count
var title = ""
var headerItem: EntityKeyboardAnimationData?
var groupHasEdit = false
inner: for (id, info, _) in view.collectionInfos {
if id == groupId, let info = info as? StickerPackCollectionInfo {
title = info.title
groupHasEdit = info.flags.contains(.isCreator)
if let thumbnail = info.thumbnail {
let type: EntityKeyboardAnimationData.ItemType
if item.file.isAnimatedSticker {
type = .lottie
} else if item.file.isVideoEmoji || item.file.isVideoSticker {
type = .video(isVP9: true)
} else if item.file.isVideo {
type = .video(isVP9: false)
} else {
type = .still
}
headerItem = EntityKeyboardAnimationData(
id: .stickerPackThumbnail(info.id),
type: type,
resource: .resource(.stickerPackThumbnail(stickerPack: .id(id: info.id.id, accessHash: info.accessHash), resource: thumbnail.resource)),
dimensions: thumbnail.dimensions.cgSize,
immediateThumbnailData: info.immediateThumbnailData,
isReaction: false,
isTemplate: false
)
}
break inner
}
}
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: title, subtitle: nil, actionButtonTitle: nil, isPremiumLocked: false, isFeatured: false, displayPremiumBadges: true, hasEdit: hasEdit && groupHasEdit, headerItem: headerItem, items: [resultItem]))
if hasEdit && !addedCreateStickerButton, let groupIndex = itemGroupIndexById[groupId] {
let resultItem = EmojiPagerContentComponent.Item(
animationData: nil,
content: .icon(.add),
itemFile: nil,
subgroupId: nil,
icon: .none,
tintMode: .none
)
itemGroups[groupIndex].items.insert(resultItem, at: 0)
addedCreateStickerButton = true
}
}
}
for featuredStickerPack in featuredStickerPacks {
if installedCollectionIds.contains(featuredStickerPack.info.id) {
continue
}
for item in featuredStickerPack.topItems {
var tintMode: Item.TintMode = .none
if item.file.isCustomTemplateEmoji {
tintMode = .primary
}
let animationData = EntityKeyboardAnimationData(file: item.file)
let resultItem = EmojiPagerContentComponent.Item(
animationData: animationData,
content: .animation(animationData),
itemFile: item.file,
subgroupId: nil,
icon: .none,
tintMode: tintMode
)
let supergroupId = featuredStickerPack.info.id
let groupId: AnyHashable = supergroupId
let isPremiumLocked: Bool = item.file.isPremiumSticker && !hasPremium
if isPremiumLocked && isPremiumDisabled {
continue
}
if let groupIndex = itemGroupIndexById[groupId] {
itemGroups[groupIndex].items.append(resultItem)
} else {
itemGroupIndexById[groupId] = itemGroups.count
let subtitle: String = strings.StickerPack_StickerCount(Int32(featuredStickerPack.info.count))
var headerItem: EntityKeyboardAnimationData?
if let thumbnailFileId = featuredStickerPack.info.thumbnailFileId, let file = featuredStickerPack.topItems.first(where: { $0.file.fileId.id == thumbnailFileId }) {
headerItem = EntityKeyboardAnimationData(file: file.file)
} else if let thumbnailDimensions = featuredStickerPack.info.thumbnailDimensions {
let info = featuredStickerPack.info
let type: EntityKeyboardAnimationData.ItemType
if item.file.isAnimatedSticker {
type = .lottie
} else if item.file.isVideoEmoji || item.file.isVideoSticker {
type = .video(isVP9: true)
} else if item.file.isVideo {
type = .video(isVP9: false)
} else {
type = .still
}
headerItem = EntityKeyboardAnimationData(
id: .stickerPackThumbnail(info.id),
type: type,
resource: .stickerPackThumbnail(id: info.id.id, accessHash: info.accessHash, info: info),
dimensions: thumbnailDimensions.cgSize,
immediateThumbnailData: info.immediateThumbnailData,
isReaction: false,
isTemplate: false
)
}
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: featuredStickerPack.info.title, subtitle: subtitle, actionButtonTitle: strings.Stickers_Install, isPremiumLocked: isPremiumLocked, isFeatured: true, displayPremiumBadges: false, hasEdit: false, headerItem: headerItem, items: [resultItem]))
}
}
}
let isMasks = stickerNamespaces.contains(Namespaces.ItemCollection.CloudMaskPacks)
let allItemGroups = itemGroups.map { group -> EmojiPagerContentComponent.ItemGroup in
var hasClear = false
var isEmbedded = false
if group.id == AnyHashable("recent") {
hasClear = true
} else if group.id == AnyHashable("featuredTop") {
hasClear = true
isEmbedded = true
} else if group.id == AnyHashable("saved") {
}
return EmojiPagerContentComponent.ItemGroup(
supergroupId: group.supergroupId,
groupId: group.id,
title: group.title,
subtitle: group.subtitle,
badge: nil,
actionButtonTitle: group.actionButtonTitle,
isFeatured: group.isFeatured,
isPremiumLocked: group.isPremiumLocked,
isEmbedded: isEmbedded,
hasClear: hasClear,
hasEdit: group.hasEdit,
collapsedLineCount: nil,
displayPremiumBadges: group.displayPremiumBadges,
headerItem: group.headerItem,
fillWithLoadingPlaceholders: false,
items: group.items
)
}
let warpContentsOnEdges: Bool
switch subject {
case .profilePhotoEmojiSelection, .groupPhotoEmojiSelection:
warpContentsOnEdges = true
default:
warpContentsOnEdges = false
}
return EmojiPagerContentComponent(
id: isMasks ? "masks" : "stickers",
context: context,
avatarPeer: avatarPeer,
animationCache: animationCache,
animationRenderer: animationRenderer,
inputInteractionHolder: EmojiPagerContentComponent.InputInteractionHolder(),
panelItemGroups: allItemGroups,
contentItemGroups: allItemGroups,
itemLayoutType: .detailed,
itemContentUniqueId: nil,
searchState: .empty(hasResults: false),
warpContentsOnEdges: warpContentsOnEdges,
hideBackground: hideBackground,
maskEdge: maskEdge,
displaySearchWithPlaceholder: hasSearch ? strings.StickersSearch_SearchStickersPlaceholder : nil,
searchCategories: searchCategories,
searchInitiallyHidden: true,
searchAlwaysActive: false,
searchIsPlaceholderOnly: searchIsPlaceholderOnly,
searchUnicodeEmojiOnly: false,
emptySearchResults: nil,
enableLongPress: false,
selectedItems: Set(),
customTintColor: nil
)
}
}
static func messageEffectsInputData(
context: AccountContext,
animationCache: AnimationCache,
animationRenderer: MultiAnimationRenderer,
hasSearch: Bool,
hideBackground: Bool = false,
maskEdge: EmojiPagerContentComponent.MaskEdgeMode = .none
) -> Signal<EmojiPagerContentComponent, NoError> {
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
let isPremiumDisabled = premiumConfiguration.isPremiumDisabled
let strings = context.sharedContext.currentPresentationData.with({ $0 }).strings
let searchCategories: Signal<EmojiSearchCategories?, NoError> = context.engine.stickers.emojiSearchCategories(kind: .emoji)
return combineLatest(
hasPremium(context: context, chatPeerId: nil, premiumIfSavedMessages: false),
context.engine.stickers.availableMessageEffects(),
searchCategories
)
|> map { hasPremium, availableMessageEffects, searchCategories -> EmojiPagerContentComponent in
struct ItemGroup {
var supergroupId: AnyHashable
var id: AnyHashable
var title: String?
var subtitle: String?
var actionButtonTitle: String?
var isPremiumLocked: Bool
var isFeatured: Bool
var displayPremiumBadges: Bool
var hasEdit: Bool
var headerItem: EntityKeyboardAnimationData?
var items: [EmojiPagerContentComponent.Item]
}
var itemGroups: [ItemGroup] = []
var itemGroupIndexById: [AnyHashable: Int] = [:]
if let availableMessageEffects {
var reactionEffects: [AvailableMessageEffects.MessageEffect] = []
var stickerEffects: [AvailableMessageEffects.MessageEffect] = []
for messageEffect in availableMessageEffects.messageEffects {
if messageEffect.effectAnimation != nil {
reactionEffects.append(messageEffect)
} else {
stickerEffects.append(messageEffect)
}
}
for i in 0 ..< 2 {
let groupId = i == 0 ? "reactions" : "stickers"
for item in i == 0 ? reactionEffects : stickerEffects {
if item.isPremium && isPremiumDisabled {
continue
}
let itemFile = item.effectSticker
var tintMode: Item.TintMode = .none
if itemFile.isCustomTemplateEmoji {
tintMode = .primary
}
let icon: EmojiPagerContentComponent.Item.Icon
if i == 0 {
if !hasPremium && item.isPremium {
icon = .locked
} else {
icon = .none
}
} else {
if !hasPremium && item.isPremium {
icon = .locked
} else if let staticIcon = item.staticIcon {
icon = .customFile(staticIcon)
} else {
icon = .text(item.emoticon)
}
}
let animationData = EntityKeyboardAnimationData(file: itemFile, partialReference: .none)
let resultItem = EmojiPagerContentComponent.Item(
animationData: animationData,
content: .animation(animationData),
itemFile: itemFile,
subgroupId: nil,
icon: icon,
tintMode: tintMode
)
if let groupIndex = itemGroupIndexById[groupId] {
itemGroups[groupIndex].items.append(resultItem)
} else {
itemGroupIndexById[groupId] = itemGroups.count
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: i == 0 ? nil : strings.Chat_MessageEffectMenu_SectionMessageEffects, subtitle: nil, actionButtonTitle: nil, isPremiumLocked: false, isFeatured: false, displayPremiumBadges: false, hasEdit: false, headerItem: nil, items: [resultItem]))
}
}
}
}
let warpContentsOnEdges: Bool = true
let allItemGroups = itemGroups.map { group -> EmojiPagerContentComponent.ItemGroup in
let hasClear = false
let isEmbedded = false
return EmojiPagerContentComponent.ItemGroup(
supergroupId: group.supergroupId,
groupId: group.id,
title: group.title,
subtitle: group.subtitle,
badge: nil,
actionButtonTitle: group.actionButtonTitle,
isFeatured: group.isFeatured,
isPremiumLocked: group.isPremiumLocked,
isEmbedded: isEmbedded,
hasClear: hasClear,
hasEdit: group.hasEdit,
collapsedLineCount: nil,
displayPremiumBadges: group.displayPremiumBadges,
headerItem: group.headerItem,
fillWithLoadingPlaceholders: false,
items: group.items
)
}
return EmojiPagerContentComponent(
id: "stickers",
context: context,
avatarPeer: nil,
animationCache: animationCache,
animationRenderer: animationRenderer,
inputInteractionHolder: EmojiPagerContentComponent.InputInteractionHolder(),
panelItemGroups: [],
contentItemGroups: allItemGroups,
itemLayoutType: .detailed,
itemContentUniqueId: nil,
searchState: .empty(hasResults: false),
warpContentsOnEdges: warpContentsOnEdges,
hideBackground: hideBackground,
maskEdge: maskEdge,
displaySearchWithPlaceholder: hasSearch ? strings.StickersSearch_SearchStickersPlaceholder : nil,
searchCategories: searchCategories,
searchInitiallyHidden: true,
searchAlwaysActive: false,
searchIsPlaceholderOnly: false,
searchUnicodeEmojiOnly: false,
emptySearchResults: nil,
enableLongPress: false,
selectedItems: Set(),
customTintColor: nil
)
}
}
}