Add animated emojis in chat theme selection screen

This commit is contained in:
Ilya Laktyushin 2021-09-09 23:52:21 +03:00
parent e5a08a1cd8
commit 3cde24efe1
2 changed files with 144 additions and 12 deletions

View File

@ -13346,9 +13346,28 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return updated
})
let _ = (self.cachedDataPromise.get()
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self] cachedData in
let animatedEmojiStickers = context.engine.stickers.loadedStickerPack(reference: .animatedEmoji, forceActualized: false)
|> map { animatedEmoji -> [String: [StickerPackItem]] in
var animatedEmojiStickers: [String: [StickerPackItem]] = [:]
switch animatedEmoji {
case let .result(_, items, _):
for case let item as StickerPackItem in items {
if let emoji = item.getStringRepresentationsOfIndexKeys().first {
animatedEmojiStickers[emoji.basicEmoji.0] = [item]
let strippedEmoji = emoji.basicEmoji.0.strippedEmoji
if animatedEmojiStickers[strippedEmoji] == nil {
animatedEmojiStickers[strippedEmoji] = [item]
}
}
}
default:
break
}
return animatedEmojiStickers
}
let _ = (combineLatest(queue: Queue.mainQueue(), self.cachedDataPromise.get(), animatedEmojiStickers)
|> take(1)).start(next: { [weak self] cachedData, animatedEmojiStickers in
guard let strongSelf = self else {
return
}
@ -13364,7 +13383,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
selectedEmoticon = nil
}
let controller = ChatThemeScreen(context: context, updatedPresentationData: strongSelf.updatedPresentationData, initiallySelectedEmoticon: selectedEmoticon, previewTheme: { [weak self] emoticon, dark in
let controller = ChatThemeScreen(context: context, updatedPresentationData: strongSelf.updatedPresentationData, animatedEmojiStickers: animatedEmojiStickers, initiallySelectedEmoticon: selectedEmoticon, previewTheme: { [weak self] emoticon, dark in
if let strongSelf = self {
strongSelf.presentCrossfadeSnapshot(delay: 0.2)
strongSelf.themeEmoticonAndDarkAppearancePreviewPromise.set(.single((emoticon, dark)))

View File

@ -14,8 +14,12 @@ import PresentationDataUtils
import AnimationUI
import MergeLists
import MediaResources
import StickerResources
import WallpaperResources
import TooltipUI
import AnimatedStickerNode
import TelegramAnimatedStickerNode
import ShimmerEffect
private func closeButtonImage(theme: PresentationTheme) -> UIImage? {
return generateImage(CGSize(width: 30.0, height: 30.0), contextGenerator: { size, context in
@ -41,6 +45,7 @@ private func closeButtonImage(theme: PresentationTheme) -> UIImage? {
private struct ThemeSettingsThemeEntry: Comparable, Identifiable {
let index: Int
let emoticon: String?
let emojiFile: TelegramMediaFile?
let themeReference: PresentationThemeReference?
var selected: Bool
let theme: PresentationTheme
@ -58,6 +63,7 @@ private struct ThemeSettingsThemeEntry: Comparable, Identifiable {
if lhs.emoticon != rhs.emoticon {
return false
}
if lhs.themeReference?.index != rhs.themeReference?.index {
return false
}
@ -81,7 +87,7 @@ private struct ThemeSettingsThemeEntry: Comparable, Identifiable {
}
func item(context: AccountContext, action: @escaping (String?) -> Void) -> ListViewItem {
return ThemeSettingsThemeIconItem(context: context, emoticon: self.emoticon, themeReference: self.themeReference, selected: self.selected, theme: self.theme, strings: self.strings, wallpaper: self.wallpaper, action: action)
return ThemeSettingsThemeIconItem(context: context, emoticon: self.emoticon, emojiFile: self.emojiFile, themeReference: self.themeReference, selected: self.selected, theme: self.theme, strings: self.strings, wallpaper: self.wallpaper, action: action)
}
}
@ -89,6 +95,7 @@ private struct ThemeSettingsThemeEntry: Comparable, Identifiable {
private class ThemeSettingsThemeIconItem: ListViewItem {
let context: AccountContext
let emoticon: String?
let emojiFile: TelegramMediaFile?
let themeReference: PresentationThemeReference?
let selected: Bool
let theme: PresentationTheme
@ -96,9 +103,10 @@ private class ThemeSettingsThemeIconItem: ListViewItem {
let wallpaper: TelegramWallpaper?
let action: (String?) -> Void
public init(context: AccountContext, emoticon: String?, themeReference: PresentationThemeReference?, selected: Bool, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper?, action: @escaping (String?) -> Void) {
public init(context: AccountContext, emoticon: String?, emojiFile: TelegramMediaFile?, themeReference: PresentationThemeReference?, selected: Bool, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper?, action: @escaping (String?) -> Void) {
self.context = context
self.emoticon = emoticon
self.emojiFile = emojiFile
self.themeReference = themeReference
self.selected = selected
self.theme = theme
@ -235,9 +243,28 @@ private final class ThemeSettingsThemeItemIconNode : ListViewItemNode {
private let overlayNode: ASImageNode
private let textNode: TextNode
private let emojiNode: TextNode
private let emojiImageNode: TransformImageNode
private var animatedStickerNode: AnimatedStickerNode?
private var placeholderNode: StickerShimmerEffectNode
var snapshotView: UIView?
var item: ThemeSettingsThemeIconItem?
override var visibility: ListViewItemNodeVisibility {
didSet {
self.visibilityStatus = self.visibility != .none
}
}
private var visibilityStatus: Bool = false {
didSet {
if self.visibilityStatus != oldValue {
self.animatedStickerNode?.visibility = self.visibilityStatus
}
}
}
private let stickerFetchedDisposable = MetaDisposable()
init() {
self.containerNode = ASDisplayNode()
@ -257,6 +284,10 @@ private final class ThemeSettingsThemeItemIconNode : ListViewItemNode {
self.emojiNode = TextNode()
self.emojiNode.isUserInteractionEnabled = false
self.emojiImageNode = TransformImageNode()
self.placeholderNode = StickerShimmerEffectNode()
super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false)
@ -265,8 +296,43 @@ private final class ThemeSettingsThemeItemIconNode : ListViewItemNode {
self.containerNode.addSubnode(self.overlayNode)
self.containerNode.addSubnode(self.textNode)
self.containerNode.addSubnode(self.emojiNode)
self.containerNode.addSubnode(self.placeholderNode)
var firstTime = true
self.emojiImageNode.imageUpdated = { [weak self] image in
guard let strongSelf = self else {
return
}
if image != nil {
strongSelf.removePlaceholder(animated: !firstTime)
if firstTime {
strongSelf.emojiImageNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
}
firstTime = false
}
}
deinit {
self.stickerFetchedDisposable.dispose()
}
private func removePlaceholder(animated: Bool) {
if !animated {
self.placeholderNode.removeFromSupernode()
} else {
self.placeholderNode.alpha = 0.0
self.placeholderNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak self] _ in
self?.placeholderNode.removeFromSupernode()
})
}
}
override func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
let emojiFrame = CGRect(origin: CGPoint(x: 33.0, y: 79.0), size: CGSize(width: 24.0, height: 24.0))
self.placeholderNode.updateAbsoluteRect(CGRect(origin: CGPoint(x: rect.minX + emojiFrame.minX, y: rect.minY + emojiFrame.minY), size: emojiFrame.size), within: containerSize)
}
func asyncLayout() -> (ThemeSettingsThemeIconItem, ListViewItemLayoutParams) -> (ListViewItemNodeLayout, (Bool) -> Void) {
let makeTextLayout = TextNode.asyncLayout(self.textNode)
let makeEmojiLayout = TextNode.asyncLayout(self.emojiNode)
@ -296,7 +362,13 @@ private final class ThemeSettingsThemeItemIconNode : ListViewItemNode {
let text = NSAttributedString(string: item.strings.Conversation_Theme_NoTheme, font: Font.semibold(15.0), textColor: item.theme.actionSheet.controlAccentColor)
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: text, backgroundColor: nil, maximumNumberOfLines: 2, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
let title = NSAttributedString(string: item.emoticon ?? "", font: Font.regular(22.0), textColor: .black)
var emoticon = item.emoticon
if emoticon == "🦁" {
emoticon = "🌳"
} else if emoticon == "🔮" {
emoticon = "🎆"
}
let title = NSAttributedString(string: emoticon != nil ? "" : "", font: Font.regular(22.0), textColor: .black)
let (_, emojiApply) = makeEmojiLayout(TextNodeLayoutArguments(attributedString: title, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
let itemLayout = ListViewItemNodeLayout(contentSize: CGSize(width: 120.0, height: 90.0), insets: UIEdgeInsets())
@ -334,6 +406,39 @@ private final class ThemeSettingsThemeItemIconNode : ListViewItemNode {
strongSelf.overlayNode.frame = strongSelf.imageNode.frame.insetBy(dx: -1.0, dy: -1.0)
strongSelf.emojiNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 79.0), size: CGSize(width: 90.0, height: 30.0))
let emojiFrame = CGRect(origin: CGPoint(x: 33.0, y: 79.0), size: CGSize(width: 24.0, height: 24.0))
if let file = item.emojiFile {
let imageApply = strongSelf.emojiImageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: emojiFrame.size, boundingSize: emojiFrame.size, intrinsicInsets: UIEdgeInsets()))
imageApply()
strongSelf.emojiImageNode.setSignal(chatMessageStickerPackThumbnail(postbox: item.context.account.postbox, resource: file.resource, animated: true, nilIfEmpty: true))
strongSelf.emojiImageNode.frame = emojiFrame
let animatedStickerNode: AnimatedStickerNode
if let current = strongSelf.animatedStickerNode {
animatedStickerNode = current
} else {
animatedStickerNode = AnimatedStickerNode()
animatedStickerNode.started = { [weak self] in
self?.emojiImageNode.isHidden = true
}
strongSelf.animatedStickerNode = animatedStickerNode
strongSelf.containerNode.insertSubnode(animatedStickerNode, belowSubnode: strongSelf.placeholderNode)
animatedStickerNode.setup(source: AnimatedStickerResourceSource(account: item.context.account, resource: file.resource), width: 128, height: 128, mode: .cached)
}
animatedStickerNode.visibility = strongSelf.visibilityStatus
strongSelf.stickerFetchedDisposable.set(fetchedMediaResource(mediaBox: item.context.account.postbox.mediaBox, reference: MediaResourceReference.media(media: .standalone(media: file), resource: file.resource)).start())
let thumbnailDimensions = PixelDimensions(width: 512, height: 512)
strongSelf.placeholderNode.update(backgroundColor: nil, foregroundColor: UIColor(rgb: 0xffffff, alpha: 0.2), shimmeringColor: UIColor(rgb: 0xffffff, alpha: 0.3), data: file.immediateThumbnailData, size: emojiFrame.size, imageSize: thumbnailDimensions.cgSize)
strongSelf.placeholderNode.frame = emojiFrame
}
if let animatedStickerNode = strongSelf.animatedStickerNode {
animatedStickerNode.frame = emojiFrame
animatedStickerNode.updateLayout(size: emojiFrame.size)
}
}
})
}
@ -376,6 +481,7 @@ final class ChatThemeScreen: ViewController {
private var animatedIn = false
private let context: AccountContext
private let animatedEmojiStickers: [String: [StickerPackItem]]
private let initiallySelectedEmoticon: String?
private let dismissByTapOutside: Bool
private let previewTheme: (String?, Bool?) -> Void
@ -392,9 +498,10 @@ final class ChatThemeScreen: ViewController {
}
}
init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>), initiallySelectedEmoticon: String?, dismissByTapOutside: Bool = true, previewTheme: @escaping (String?, Bool?) -> Void, completion: @escaping (String?) -> Void) {
init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>), animatedEmojiStickers: [String: [StickerPackItem]], initiallySelectedEmoticon: String?, dismissByTapOutside: Bool = true, previewTheme: @escaping (String?, Bool?) -> Void, completion: @escaping (String?) -> Void) {
self.context = context
self.presentationData = updatedPresentationData.initial
self.animatedEmojiStickers = animatedEmojiStickers
self.initiallySelectedEmoticon = initiallySelectedEmoticon
self.dismissByTapOutside = dismissByTapOutside
self.previewTheme = previewTheme
@ -426,7 +533,7 @@ final class ChatThemeScreen: ViewController {
}
override public func loadDisplayNode() {
self.displayNode = ChatThemeScreenNode(context: self.context, presentationData: self.presentationData, initiallySelectedEmoticon: self.initiallySelectedEmoticon, dismissByTapOutside: self.dismissByTapOutside)
self.displayNode = ChatThemeScreenNode(context: self.context, presentationData: self.presentationData, animatedEmojiStickers: self.animatedEmojiStickers, initiallySelectedEmoticon: self.initiallySelectedEmoticon, dismissByTapOutside: self.dismissByTapOutside)
self.controllerNode.passthroughHitTestImpl = self.passthroughHitTestImpl
self.controllerNode.previewTheme = { [weak self] emoticon, dark in
guard let strongSelf = self else {
@ -558,7 +665,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega
var dismiss: (() -> Void)?
var cancel: (() -> Void)?
init(context: AccountContext, presentationData: PresentationData, initiallySelectedEmoticon: String?, dismissByTapOutside: Bool) {
init(context: AccountContext, presentationData: PresentationData, animatedEmojiStickers: [String: [StickerPackItem]], initiallySelectedEmoticon: String?, dismissByTapOutside: Bool) {
self.context = context
self.initiallySelectedEmoticon = initiallySelectedEmoticon
self.selectedEmoticon = initiallySelectedEmoticon
@ -662,10 +769,16 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega
var entries: [ThemeSettingsThemeEntry] = []
if strongSelf.initiallySelectedEmoticon != nil {
entries.append(ThemeSettingsThemeEntry(index: 0, emoticon: nil, themeReference: nil, selected: selectedEmoticon == nil, theme: presentationData.theme, strings: presentationData.strings, wallpaper: nil))
entries.append(ThemeSettingsThemeEntry(index: 0, emoticon: nil, emojiFile: nil, themeReference: nil, selected: selectedEmoticon == nil, theme: presentationData.theme, strings: presentationData.strings, wallpaper: nil))
}
for theme in themes {
entries.append(ThemeSettingsThemeEntry(index: entries.count, emoticon: theme.emoji, themeReference: .cloud(PresentationCloudTheme(theme: isDarkAppearance ? theme.darkTheme : theme.theme, resolvedWallpaper: nil, creatorAccountId: nil)), selected: selectedEmoticon == theme.emoji, theme: presentationData.theme, strings: presentationData.strings, wallpaper: nil))
var emoticon = theme.emoji
if emoticon == "🦁" {
emoticon = "🌳"
} else if emoticon == "🔮" {
emoticon = "🎆"
}
entries.append(ThemeSettingsThemeEntry(index: entries.count, emoticon: theme.emoji, emojiFile: animatedEmojiStickers[emoticon]?.first?.file, themeReference: .cloud(PresentationCloudTheme(theme: isDarkAppearance ? theme.darkTheme : theme.theme, resolvedWallpaper: nil, creatorAccountId: nil)), selected: selectedEmoticon == theme.emoji, theme: presentationData.theme, strings: presentationData.strings, wallpaper: nil))
}
let action: (String?) -> Void = { [weak self] emoticon in