mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Add animated emojis in chat theme selection screen
This commit is contained in:
parent
e5a08a1cd8
commit
3cde24efe1
@ -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)))
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user