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 return updated
}) })
let _ = (self.cachedDataPromise.get() let animatedEmojiStickers = context.engine.stickers.loadedStickerPack(reference: .animatedEmoji, forceActualized: false)
|> take(1) |> map { animatedEmoji -> [String: [StickerPackItem]] in
|> deliverOnMainQueue).start(next: { [weak self] cachedData 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 { guard let strongSelf = self else {
return return
} }
@ -13364,7 +13383,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
selectedEmoticon = nil 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 { if let strongSelf = self {
strongSelf.presentCrossfadeSnapshot(delay: 0.2) strongSelf.presentCrossfadeSnapshot(delay: 0.2)
strongSelf.themeEmoticonAndDarkAppearancePreviewPromise.set(.single((emoticon, dark))) strongSelf.themeEmoticonAndDarkAppearancePreviewPromise.set(.single((emoticon, dark)))

View File

@ -14,8 +14,12 @@ import PresentationDataUtils
import AnimationUI import AnimationUI
import MergeLists import MergeLists
import MediaResources import MediaResources
import StickerResources
import WallpaperResources import WallpaperResources
import TooltipUI import TooltipUI
import AnimatedStickerNode
import TelegramAnimatedStickerNode
import ShimmerEffect
private func closeButtonImage(theme: PresentationTheme) -> UIImage? { private func closeButtonImage(theme: PresentationTheme) -> UIImage? {
return generateImage(CGSize(width: 30.0, height: 30.0), contextGenerator: { size, context in 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 { private struct ThemeSettingsThemeEntry: Comparable, Identifiable {
let index: Int let index: Int
let emoticon: String? let emoticon: String?
let emojiFile: TelegramMediaFile?
let themeReference: PresentationThemeReference? let themeReference: PresentationThemeReference?
var selected: Bool var selected: Bool
let theme: PresentationTheme let theme: PresentationTheme
@ -58,6 +63,7 @@ private struct ThemeSettingsThemeEntry: Comparable, Identifiable {
if lhs.emoticon != rhs.emoticon { if lhs.emoticon != rhs.emoticon {
return false return false
} }
if lhs.themeReference?.index != rhs.themeReference?.index { if lhs.themeReference?.index != rhs.themeReference?.index {
return false return false
} }
@ -81,7 +87,7 @@ private struct ThemeSettingsThemeEntry: Comparable, Identifiable {
} }
func item(context: AccountContext, action: @escaping (String?) -> Void) -> ListViewItem { 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 { private class ThemeSettingsThemeIconItem: ListViewItem {
let context: AccountContext let context: AccountContext
let emoticon: String? let emoticon: String?
let emojiFile: TelegramMediaFile?
let themeReference: PresentationThemeReference? let themeReference: PresentationThemeReference?
let selected: Bool let selected: Bool
let theme: PresentationTheme let theme: PresentationTheme
@ -96,9 +103,10 @@ private class ThemeSettingsThemeIconItem: ListViewItem {
let wallpaper: TelegramWallpaper? let wallpaper: TelegramWallpaper?
let action: (String?) -> Void 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.context = context
self.emoticon = emoticon self.emoticon = emoticon
self.emojiFile = emojiFile
self.themeReference = themeReference self.themeReference = themeReference
self.selected = selected self.selected = selected
self.theme = theme self.theme = theme
@ -235,9 +243,28 @@ private final class ThemeSettingsThemeItemIconNode : ListViewItemNode {
private let overlayNode: ASImageNode private let overlayNode: ASImageNode
private let textNode: TextNode private let textNode: TextNode
private let emojiNode: TextNode private let emojiNode: TextNode
private let emojiImageNode: TransformImageNode
private var animatedStickerNode: AnimatedStickerNode?
private var placeholderNode: StickerShimmerEffectNode
var snapshotView: UIView? var snapshotView: UIView?
var item: ThemeSettingsThemeIconItem? 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() { init() {
self.containerNode = ASDisplayNode() self.containerNode = ASDisplayNode()
@ -257,6 +284,10 @@ private final class ThemeSettingsThemeItemIconNode : ListViewItemNode {
self.emojiNode = TextNode() self.emojiNode = TextNode()
self.emojiNode.isUserInteractionEnabled = false self.emojiNode.isUserInteractionEnabled = false
self.emojiImageNode = TransformImageNode()
self.placeholderNode = StickerShimmerEffectNode()
super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) 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.overlayNode)
self.containerNode.addSubnode(self.textNode) self.containerNode.addSubnode(self.textNode)
self.containerNode.addSubnode(self.emojiNode) 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) { func asyncLayout() -> (ThemeSettingsThemeIconItem, ListViewItemLayoutParams) -> (ListViewItemNodeLayout, (Bool) -> Void) {
let makeTextLayout = TextNode.asyncLayout(self.textNode) let makeTextLayout = TextNode.asyncLayout(self.textNode)
let makeEmojiLayout = TextNode.asyncLayout(self.emojiNode) 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 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 (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 (_, 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()) 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.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)) 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 var animatedIn = false
private let context: AccountContext private let context: AccountContext
private let animatedEmojiStickers: [String: [StickerPackItem]]
private let initiallySelectedEmoticon: String? private let initiallySelectedEmoticon: String?
private let dismissByTapOutside: Bool private let dismissByTapOutside: Bool
private let previewTheme: (String?, Bool?) -> Void 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.context = context
self.presentationData = updatedPresentationData.initial self.presentationData = updatedPresentationData.initial
self.animatedEmojiStickers = animatedEmojiStickers
self.initiallySelectedEmoticon = initiallySelectedEmoticon self.initiallySelectedEmoticon = initiallySelectedEmoticon
self.dismissByTapOutside = dismissByTapOutside self.dismissByTapOutside = dismissByTapOutside
self.previewTheme = previewTheme self.previewTheme = previewTheme
@ -426,7 +533,7 @@ final class ChatThemeScreen: ViewController {
} }
override public func loadDisplayNode() { 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.passthroughHitTestImpl = self.passthroughHitTestImpl
self.controllerNode.previewTheme = { [weak self] emoticon, dark in self.controllerNode.previewTheme = { [weak self] emoticon, dark in
guard let strongSelf = self else { guard let strongSelf = self else {
@ -558,7 +665,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega
var dismiss: (() -> Void)? var dismiss: (() -> Void)?
var cancel: (() -> 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.context = context
self.initiallySelectedEmoticon = initiallySelectedEmoticon self.initiallySelectedEmoticon = initiallySelectedEmoticon
self.selectedEmoticon = initiallySelectedEmoticon self.selectedEmoticon = initiallySelectedEmoticon
@ -662,10 +769,16 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega
var entries: [ThemeSettingsThemeEntry] = [] var entries: [ThemeSettingsThemeEntry] = []
if strongSelf.initiallySelectedEmoticon != nil { 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 { 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 let action: (String?) -> Void = { [weak self] emoticon in