Add trending emoji [WIP]

This commit is contained in:
Ali 2023-01-20 21:15:34 +04:00
parent 40f948d9b9
commit a74c80a7c0
11 changed files with 207 additions and 42 deletions

View File

@ -792,6 +792,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
isStatusSelection: true,
isReactionSelection: false,
isEmojiSelection: false,
hasTrending: false,
topReactionItems: [],
areUnicodeEmojiEnabled: false,
areCustomEmojiEnabled: true,

View File

@ -662,6 +662,7 @@ private final class DrawingScreenComponent: CombinedComponent {
isStatusSelection: false,
isReactionSelection: false,
isEmojiSelection: true,
hasTrending: false,
topReactionItems: [],
areUnicodeEmojiEnabled: true,
areCustomEmojiEnabled: true,

View File

@ -348,6 +348,7 @@ public func quickReactionSetupController(
isStatusSelection: false,
isReactionSelection: true,
isEmojiSelection: false,
hasTrending: false,
isQuickReactionSelection: true,
topReactionItems: [],
areUnicodeEmojiEnabled: false,

View File

@ -166,6 +166,7 @@ private enum ApplicationSpecificGlobalNotice: Int32 {
case emojiTooltip = 32
case audioTranscriptionSuggestion = 33
case clearStorageDismissedTipSize = 34
case dismissedTrendingEmojiPacks = 35
var key: ValueBoxKey {
let v = ValueBoxKey(length: 4)
@ -354,6 +355,10 @@ private struct ApplicationSpecificNoticeKeys {
static func clearStorageDismissedTipSize() -> NoticeEntryKey {
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.clearStorageDismissedTipSize.key)
}
static func dismissedTrendingEmojiPacks() -> NoticeEntryKey {
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.dismissedTrendingEmojiPacks.key)
}
}
public struct ApplicationSpecificNotice {
@ -1011,6 +1016,25 @@ public struct ApplicationSpecificNotice {
}
}
public static func dismissedTrendingEmojiPacks(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Signal<[Int64]?, NoError> {
return accountManager.noticeEntry(key: ApplicationSpecificNoticeKeys.dismissedTrendingEmojiPacks())
|> map { view -> [Int64]? in
if let value = view.value?.get(ApplicationSpecificInt64ArrayNotice.self) {
return value.values
} else {
return nil
}
}
}
public static func setDismissedTrendingEmojiPacks(accountManager: AccountManager<TelegramAccountManagerTypes>, values: [Int64]) -> Signal<Void, NoError> {
return accountManager.transaction { transaction -> Void in
if let entry = CodableEntry(ApplicationSpecificInt64ArrayNotice(values: values)) {
transaction.setNotice(ApplicationSpecificNoticeKeys.dismissedTrendingEmojiPacks(), entry)
}
}
}
public static func getChatSpecificThemeLightPreviewTip(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Signal<(Int32, Int32), NoError> {
return accountManager.transaction { transaction -> (Int32, Int32) in
if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.chatSpecificThemeLightPreviewTip())?.get(ApplicationSpecificTimestampAndCounterNotice.self) {

View File

@ -800,6 +800,7 @@ final class AvatarEditorScreenComponent: Component {
isStatusSelection: false,
isReactionSelection: false,
isEmojiSelection: false,
hasTrending: false,
isProfilePhotoEmojiSelection: true,
topReactionItems: [],
areUnicodeEmojiEnabled: false,

View File

@ -38,6 +38,7 @@ swift_library(
"//submodules/TelegramUI/Components/MultiplexedVideoNode:MultiplexedVideoNode",
"//submodules/TelegramUI/Components/ChatControllerInteraction:ChatControllerInteraction",
"//submodules/FeaturedStickersScreen:FeaturedStickersScreen",
"//submodules/StickerPackPreviewUI",
],
visibility = [
"//visibility:public",

View File

@ -30,6 +30,7 @@ import MultiplexedVideoNode
import ChatControllerInteraction
import FeaturedStickersScreen
import Pasteboard
import StickerPackPreviewUI
public struct ChatMediaInputPaneScrollState {
let absoluteOffset: CGFloat?
@ -107,7 +108,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
let animationCache = context.animationCache
let animationRenderer = context.animationRenderer
let emojiItems = EmojiPagerContentComponent.emojiInputData(context: context, animationCache: animationCache, animationRenderer: animationRenderer, isStandalone: false, isStatusSelection: false, isReactionSelection: false, isEmojiSelection: true, topReactionItems: [], areUnicodeEmojiEnabled: true, areCustomEmojiEnabled: areCustomEmojiEnabled, chatPeerId: chatPeerId)
let emojiItems = EmojiPagerContentComponent.emojiInputData(context: context, animationCache: animationCache, animationRenderer: animationRenderer, isStandalone: false, isStatusSelection: false, isReactionSelection: false, isEmojiSelection: true, hasTrending: true, topReactionItems: [], areUnicodeEmojiEnabled: true, areCustomEmojiEnabled: areCustomEmojiEnabled, chatPeerId: chatPeerId)
let stickerNamespaces: [ItemCollectionId.Namespace] = [Namespaces.ItemCollection.CloudStickerPacks]
let stickerOrderedItemListCollectionIds: [Int32] = [Namespaces.OrderedItemList.CloudSavedStickers, Namespaces.OrderedItemList.CloudRecentStickers, Namespaces.OrderedItemList.CloudAllPremiumStickers]
@ -645,7 +646,42 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
return
}
if let file = item.itemFile {
if groupId == AnyHashable("featuredTop"), let file = item.itemFile {
let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedEmojiPacks)
let _ = (context.account.postbox.combinedView(keys: [viewKey])
|> take(1)
|> deliverOnMainQueue).start(next: { [weak interfaceInteraction, weak controllerInteraction] views in
guard let controllerInteraction = controllerInteraction else {
return
}
guard let view = views.views[viewKey] as? OrderedItemListView else {
return
}
for featuredStickerPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) {
if featuredStickerPack.topItems.contains(where: { $0.file.fileId == file.fileId }) {
let controller = StickerPackScreen(
context: context,
updatedPresentationData: controllerInteraction.updatedPresentationData,
mode: .default,
mainStickerPack: .id(id: featuredStickerPack.info.id.id, accessHash: featuredStickerPack.info.accessHash),
stickerPacks: [.id(id: featuredStickerPack.info.id.id, accessHash: featuredStickerPack.info.accessHash)],
loadedStickerPacks: [.result(info: featuredStickerPack.info, items: featuredStickerPack.topItems, installed: false)],
parentNavigationController: controllerInteraction.navigationController(),
sendSticker: nil,
sendEmoji: { [weak interfaceInteraction] text, emojiAttribute in
guard let interfaceInteraction else {
return
}
interfaceInteraction.insertText(NSAttributedString(string: text, attributes: [ChatTextInputAttributes.customEmoji: emojiAttribute]))
}
)
controllerInteraction.presentController(controller, nil)
break
}
}
})
} else if let file = item.itemFile {
var text = "."
var emojiAttribute: ChatTextInputTextCustomEmojiAttribute?
loop: for attribute in file.attributes {
@ -807,6 +843,20 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
})
])])
controllerInteraction.presentController(actionSheet, nil)
} else if groupId == AnyHashable("featuredTop") {
let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedEmojiPacks)
let _ = (context.account.postbox.combinedView(keys: [viewKey])
|> take(1)
|> deliverOnMainQueue).start(next: { views in
guard let view = views.views[viewKey] as? OrderedItemListView else {
return
}
var emojiPackIds: [Int64] = []
for featuredEmojiPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) {
emojiPackIds.append(featuredEmojiPack.info.id.id)
}
let _ = ApplicationSpecificNotice.setDismissedTrendingEmojiPacks(accountManager: context.sharedContext.accountManager, values: emojiPackIds).start()
})
}
},
pushController: { [weak controllerInteraction] controller in
@ -2017,9 +2067,11 @@ public final class EntityInputView: UIInputView, AttachmentTextInputPanelInputVi
return
}
if let file = item.itemFile {
var text = "."
var emojiAttribute: ChatTextInputTextCustomEmojiAttribute?
if groupId == AnyHashable("featuredTop") {
} else {
if let file = item.itemFile {
var text = "."
var emojiAttribute: ChatTextInputTextCustomEmojiAttribute?
loop: for attribute in file.attributes {
switch attribute {
case let .CustomEmoji(_, _, displayText, _):
@ -2034,34 +2086,35 @@ public final class EntityInputView: UIInputView, AttachmentTextInputPanelInputVi
break
}
}
if file.isPremiumEmoji && !hasPremium {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
strongSelf.presentController?(UndoOverlayController(presentationData: presentationData, content: .sticker(context: context, file: file, title: nil, text: presentationData.strings.EmojiInput_PremiumEmojiToast_Text, undoText: presentationData.strings.EmojiInput_PremiumEmojiToast_Action, customAction: {
guard let strongSelf = self else {
return
}
var replaceImpl: ((ViewController) -> Void)?
let controller = PremiumDemoScreen(context: strongSelf.context, subject: .animatedEmoji, action: {
let controller = PremiumIntroScreen(context: strongSelf.context, source: .animatedEmoji)
replaceImpl?(controller)
})
replaceImpl = { [weak controller] c in
controller?.replace(with: c)
}
strongSelf.presentController?(controller)
}), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }))
return
}
if let emojiAttribute = emojiAttribute {
if file.isPremiumEmoji && !hasPremium {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
strongSelf.presentController?(UndoOverlayController(presentationData: presentationData, content: .sticker(context: context, file: file, title: nil, text: presentationData.strings.EmojiInput_PremiumEmojiToast_Text, undoText: presentationData.strings.EmojiInput_PremiumEmojiToast_Action, customAction: {
guard let strongSelf = self else {
return
}
var replaceImpl: ((ViewController) -> Void)?
let controller = PremiumDemoScreen(context: strongSelf.context, subject: .animatedEmoji, action: {
let controller = PremiumIntroScreen(context: strongSelf.context, source: .animatedEmoji)
replaceImpl?(controller)
})
replaceImpl = { [weak controller] c in
controller?.replace(with: c)
}
strongSelf.presentController?(controller)
}), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }))
return
}
if let emojiAttribute = emojiAttribute {
AudioServicesPlaySystemSound(0x450)
strongSelf.insertText?(NSAttributedString(string: text, attributes: [ChatTextInputAttributes.customEmoji: emojiAttribute]))
}
} else if case let .staticEmoji(staticEmoji) = item.content {
AudioServicesPlaySystemSound(0x450)
strongSelf.insertText?(NSAttributedString(string: text, attributes: [ChatTextInputAttributes.customEmoji: emojiAttribute]))
strongSelf.insertText?(NSAttributedString(string: staticEmoji, attributes: [:]))
}
} else if case let .staticEmoji(staticEmoji) = item.content {
AudioServicesPlaySystemSound(0x450)
strongSelf.insertText?(NSAttributedString(string: staticEmoji, attributes: [:]))
}
})
},
@ -2126,7 +2179,7 @@ public final class EntityInputView: UIInputView, AttachmentTextInputPanelInputVi
let semaphore = DispatchSemaphore(value: 0)
var emojiComponent: EmojiPagerContentComponent?
let _ = EmojiPagerContentComponent.emojiInputData(context: context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, isStandalone: true, isStatusSelection: false, isReactionSelection: false, isEmojiSelection: false, topReactionItems: [], areUnicodeEmojiEnabled: true, areCustomEmojiEnabled: areCustomEmojiEnabled, chatPeerId: nil, forceHasPremium: forceHasPremium).start(next: { value in
let _ = EmojiPagerContentComponent.emojiInputData(context: context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, isStandalone: true, isStatusSelection: false, isReactionSelection: false, isEmojiSelection: false, hasTrending: false, topReactionItems: [], areUnicodeEmojiEnabled: true, areCustomEmojiEnabled: areCustomEmojiEnabled, chatPeerId: nil, forceHasPremium: forceHasPremium).start(next: { value in
emojiComponent = value
semaphore.signal()
})
@ -2141,7 +2194,7 @@ public final class EntityInputView: UIInputView, AttachmentTextInputPanelInputVi
gifs: nil,
availableGifSearchEmojies: []
),
updatedInputData: EmojiPagerContentComponent.emojiInputData(context: context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, isStandalone: true, isStatusSelection: false, isReactionSelection: false, isEmojiSelection: false, topReactionItems: [], areUnicodeEmojiEnabled: true, areCustomEmojiEnabled: areCustomEmojiEnabled, chatPeerId: nil, forceHasPremium: forceHasPremium) |> map { emojiComponent -> ChatEntityKeyboardInputNode.InputData in
updatedInputData: EmojiPagerContentComponent.emojiInputData(context: context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, isStandalone: true, isStatusSelection: false, isReactionSelection: false, isEmojiSelection: false, hasTrending: false, topReactionItems: [], areUnicodeEmojiEnabled: true, areCustomEmojiEnabled: areCustomEmojiEnabled, chatPeerId: nil, forceHasPremium: forceHasPremium) |> map { emojiComponent -> ChatEntityKeyboardInputNode.InputData in
return ChatEntityKeyboardInputNode.InputData(
emoji: emojiComponent,
stickers: nil,

View File

@ -6532,6 +6532,7 @@ public final class EmojiPagerContentComponent: Component {
isStatusSelection: Bool,
isReactionSelection: Bool,
isEmojiSelection: Bool,
hasTrending: Bool,
isTopicIconSelection: Bool = false,
isQuickReactionSelection: Bool = false,
isProfilePhotoEmojiSelection: Bool = false,
@ -6612,9 +6613,10 @@ public final class EmojiPagerContentComponent: Component {
context.account.viewTracker.featuredEmojiPacks(),
availableReactions,
searchCategories,
iconStatusEmoji
iconStatusEmoji,
ApplicationSpecificNotice.dismissedTrendingEmojiPacks(accountManager: context.sharedContext.accountManager)
)
|> map { view, hasPremium, featuredEmojiPacks, availableReactions, searchCategories, iconStatusEmoji -> EmojiPagerContentComponent in
|> map { view, hasPremium, featuredEmojiPacks, availableReactions, searchCategories, iconStatusEmoji, dismissedTrendingEmojiPacks -> EmojiPagerContentComponent in
struct ItemGroup {
var supergroupId: AnyHashable
var id: AnyHashable
@ -6630,6 +6632,81 @@ public final class EmojiPagerContentComponent: Component {
var itemGroups: [ItemGroup] = []
var itemGroupIndexById: [AnyHashable: Int] = [:]
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 {
for featuredEmojiPack in featuredEmojiPacks {
if installedCollectionIds.contains(featuredEmojiPack.info.id) {
continue
}
guard let item = featuredEmojiPack.topItems.first else {
continue
}
let animationData: EntityKeyboardAnimationData
if let thumbnail = featuredEmojiPack.info.thumbnail {
let type: EntityKeyboardAnimationData.ItemType
if item.file.isAnimatedSticker {
type = .lottie
} else if item.file.isVideoEmoji || item.file.isVideoSticker {
type = .video
} else {
type = .still
}
animationData = EntityKeyboardAnimationData(
id: .stickerPackThumbnail(featuredEmojiPack.info.id),
type: type,
resource: .stickerPackThumbnail(stickerPack: .id(id: featuredEmojiPack.info.id.id, accessHash: featuredEmojiPack.info.accessHash), resource: thumbnail.resource),
dimensions: thumbnail.dimensions.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
//TODO:localize
let title = "TRENDING EMOJI"
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: title, subtitle: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: 0, isClearable: false, headerItem: nil, items: [resultItem]))
}
}
}
var recentEmoji: OrderedItemListView?
var featuredStatusEmoji: OrderedItemListView?
var recentStatusEmoji: OrderedItemListView?
@ -7137,11 +7214,6 @@ public final class EmojiPagerContentComponent: Component {
}
}
var installedCollectionIds = Set<ItemCollectionId>()
for (id, _, _) in view.collectionInfos {
installedCollectionIds.insert(id)
}
if areCustomEmojiEnabled {
for entry in view.entries {
guard let item = entry.item as? StickerPackItem else {
@ -7325,6 +7397,13 @@ public final class EmojiPagerContentComponent: Component {
}
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 {
@ -7350,8 +7429,8 @@ public final class EmojiPagerContentComponent: Component {
actionButtonTitle: nil,
isFeatured: group.isFeatured,
isPremiumLocked: group.isPremiumLocked,
isEmbedded: false,
hasClear: group.isClearable,
isEmbedded: isEmbedded,
hasClear: hasClear,
collapsedLineCount: group.collapsedLineCount,
displayPremiumBadges: false,
headerItem: headerItem,

View File

@ -553,6 +553,7 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
isStatusSelection: false,
isReactionSelection: false,
isEmojiSelection: false,
hasTrending: false,
isTopicIconSelection: true,
topReactionItems: [],
areUnicodeEmojiEnabled: false,
@ -621,6 +622,7 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
isStatusSelection: false,
isReactionSelection: false,
isEmojiSelection: false,
hasTrending: false,
isTopicIconSelection: true,
topReactionItems: [],
areUnicodeEmojiEnabled: false,

View File

@ -1169,6 +1169,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
isStatusSelection: false,
isReactionSelection: true,
isEmojiSelection: false,
hasTrending: false,
topReactionItems: reactionItems,
areUnicodeEmojiEnabled: false,
areCustomEmojiEnabled: true,

View File

@ -3476,6 +3476,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
isStatusSelection: true,
isReactionSelection: false,
isEmojiSelection: false,
hasTrending: false,
topReactionItems: [],
areUnicodeEmojiEnabled: false,
areCustomEmojiEnabled: true,