From d556a80d0bd50e6b8039e2e86257429d7b3552a0 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Tue, 24 Jan 2023 09:06:58 +0100 Subject: [PATCH 1/7] Emoji search improvements --- .../Sources/State/EmojiSearchCategories.swift | 20 +++++++++++++++- .../Sources/EmojiPagerContentComponent.swift | 24 ++++++++++--------- .../EmojiSearchSearchBarComponent.swift | 7 ++++++ 3 files changed, 39 insertions(+), 12 deletions(-) diff --git a/submodules/TelegramCore/Sources/State/EmojiSearchCategories.swift b/submodules/TelegramCore/Sources/State/EmojiSearchCategories.swift index 06a108d61b..880dcbfaf4 100644 --- a/submodules/TelegramCore/Sources/State/EmojiSearchCategories.swift +++ b/submodules/TelegramCore/Sources/State/EmojiSearchCategories.swift @@ -146,7 +146,25 @@ func managedSynchronizeEmojiSearchCategories(postbox: Postbox, network: Network, break } - return .complete() + var fileIds: [Int64] = [] + if let cached = _internal_cachedEmojiSearchCategories(transaction: transaction, kind: kind) { + for group in cached.groups { + fileIds.append(group.id) + } + } + return _internal_resolveInlineStickers(postbox: postbox, network: network, fileIds: fileIds) + |> mapToSignal { files -> Signal in + var fetchSignals: Signal = .complete() + for (_, file) in files { + let signal = fetchedMediaResource(mediaBox: postbox.mediaBox, userLocation: .other, userContentType: .other, reference: .standalone(resource: file.resource)) + |> ignoreValues + |> `catch` { _ -> Signal in + return .complete() + } + fetchSignals = fetchSignals |> then(signal) + } + return fetchSignals + } } |> switchToLatest } diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift index 635bcc49f3..2f0c1b6d94 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift @@ -6485,17 +6485,19 @@ public final class EmojiPagerContentComponent: Component { let searchHeaderFrame = CGRect(origin: CGPoint(x: itemLayout.searchInsets.left, y: itemLayout.searchInsets.top), size: CGSize(width: itemLayout.width - itemLayout.searchInsets.left - itemLayout.searchInsets.right, height: itemLayout.searchHeight)) visibleSearchHeader.update(context: component.context, theme: keyboardChildEnvironment.theme, strings: keyboardChildEnvironment.strings, text: displaySearchWithPlaceholder, useOpaqueTheme: useOpaqueTheme, isActive: self.isSearchActivated, size: searchHeaderFrame.size, canFocus: !component.searchIsPlaceholderOnly, searchCategories: component.searchCategories, transition: transition) - /*transition.attachAnimation(view: visibleSearchHeader, id: "search_transition", completion: { [weak self] completed in - guard let strongSelf = self, completed, let visibleSearchHeader = strongSelf.visibleSearchHeader else { - return - } - - if !strongSelf.isSearchActivated && visibleSearchHeader.superview != strongSelf.scrollView { - strongSelf.scrollView.addSubview(visibleSearchHeader) - strongSelf.mirrorContentScrollView.addSubview(visibleSearchHeader.tintContainerView) - } - })*/ - if visibleSearchHeader.frame != searchHeaderFrame { + if !useOpaqueTheme { + transition.setFrame(view: visibleSearchHeader, frame: searchHeaderFrame) + transition.attachAnimation(view: visibleSearchHeader, id: "search_transition", completion: { [weak self] completed in + guard let strongSelf = self, completed, let visibleSearchHeader = strongSelf.visibleSearchHeader else { + return + } + + if !strongSelf.isSearchActivated && visibleSearchHeader.superview != strongSelf.scrollView { + strongSelf.scrollView.addSubview(visibleSearchHeader) + strongSelf.mirrorContentScrollView.addSubview(visibleSearchHeader.tintContainerView) + } + }) + } else { transition.setFrame(view: visibleSearchHeader, frame: searchHeaderFrame, completion: { [weak self] completed in if !useOpaqueTheme { guard let strongSelf = self, completed, let visibleSearchHeader = strongSelf.visibleSearchHeader else { diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchSearchBarComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchSearchBarComponent.swift index 3d3ff96944..e91ff3fee0 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchSearchBarComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchSearchBarComponent.swift @@ -15,6 +15,7 @@ import LottieAnimationComponent import EmojiStatusComponent import LottieComponent import LottieComponentEmojiContent +import AudioToolbox private final class RoundMaskView: UIImageView { private var currentDiameter: CGFloat? @@ -214,6 +215,10 @@ final class EmojiSearchSearchBarComponent: Component { private var selectedItem: AnyHashable? + private lazy var hapticFeedback: HapticFeedback = { + return HapticFeedback() + }() + override init(frame: CGRect) { self.tintContainerView = UIView() @@ -281,6 +286,8 @@ final class EmojiSearchSearchBarComponent: Component { self.selectedItem = nil } else { self.selectedItem = AnyHashable(id) + AudioServicesPlaySystemSound(0x450) + self.hapticFeedback.tap() } self.state?.updated(transition: .immediate) From b92d21f9d7edd204270324b2eab90813f3804c85 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Tue, 24 Jan 2023 14:25:26 +0100 Subject: [PATCH 2/7] Emoji UI improvements --- .../Sources/AnimatedStickerNode.swift | 19 +++- .../Sources/SoftwareAnimationRenderer.swift | 40 ++++++-- .../Source/Base/Transition.swift | 49 ++++++++-- .../Display/Source/NavigationButtonNode.swift | 6 ++ .../Sources/FeaturedStickersScreen.swift | 4 +- .../ImportStickerPackControllerNode.swift | 2 +- .../Sources/StickerPreviewPeekContent.swift | 9 +- .../Items/ItemListExpandableSwitchItem.swift | 1 + .../Sources/ReactionContextNode.swift | 2 +- .../AutodownloadMediaCategoryController.swift | 12 ++- .../StickerPackPreviewControllerNode.swift | 2 +- .../Sources/StickerPackScreen.swift | 2 +- .../Sources/StickerPreviewPeekContent.swift | 26 ++--- .../SyncCore/SyncCore_TelegramMediaFile.swift | 3 + .../Sources/AvatarEditorScreen.swift | 4 +- .../Sources/ChatEntityKeyboardInputNode.swift | 32 +++++-- .../EmojiStatusSelectionComponent.swift | 2 +- .../Sources/EmojiPagerContentComponent.swift | 95 +++++++++++++++---- .../Sources/EmojiSearchContent.swift | 3 +- .../EmojiSearchSearchBarComponent.swift | 17 +++- .../Sources/GifPagerContentComponent.swift | 17 +++- .../Sources/LottieComponent.swift | 2 +- .../Sources/DataCategoriesComponent.swift | 8 +- .../Sources/DataCategoryItemCompoment.swift | 16 ++-- .../Sources/DataUsageScreen.swift | 9 +- .../Sources/PieChartComponent.swift | 2 +- .../Sources/ChatMediaInputNode.swift | 4 +- .../EmojisChatInputContextPanelNode.swift | 2 +- ...textResultsChatInputContextPanelNode.swift | 17 ++-- ...ListContextResultsChatInputPanelItem.swift | 28 +++--- ...rizontalStickersChatContextPanelNode.swift | 2 +- .../Sources/InlineReactionSearchPanel.swift | 2 +- .../StickersChatInputContextPanelNode.swift | 2 +- 33 files changed, 318 insertions(+), 123 deletions(-) diff --git a/submodules/AnimatedStickerNode/Sources/AnimatedStickerNode.swift b/submodules/AnimatedStickerNode/Sources/AnimatedStickerNode.swift index 2fa0012fc9..94976a0026 100644 --- a/submodules/AnimatedStickerNode/Sources/AnimatedStickerNode.swift +++ b/submodules/AnimatedStickerNode/Sources/AnimatedStickerNode.swift @@ -252,6 +252,15 @@ public final class DefaultAnimatedStickerNodeImpl: ASDisplayNode, AnimatedSticke private var overlayColor: (UIColor?, Bool)? = nil private var size: CGSize? + public var dynamicColor: UIColor? { + didSet { + if let renderer = self.renderer?.renderer as? SoftwareAnimationRenderer { + renderer.renderAsTemplateImage = self.dynamicColor != nil + } + self.renderer?.renderer.view.tintColor = self.dynamicColor + } + } + public init(useMetalCache: Bool = false) { self.queue = sharedQueue self.eventsNode = AnimatedStickerNodeDisplayEvents() @@ -279,12 +288,12 @@ public final class DefaultAnimatedStickerNodeImpl: ASDisplayNode, AnimatedSticke if #available(iOS 10.0, *) { return CompressedAnimationRenderer() } else { - return SoftwareAnimationRenderer() + return SoftwareAnimationRenderer(templateImageSupport: true) } }) private static let softwareRendererPool = AnimationRendererPool(generate: { - return SoftwareAnimationRenderer() + return SoftwareAnimationRenderer(templateImageSupport: true) }) private weak var nodeToCopyFrameFrom: DefaultAnimatedStickerNodeImpl? @@ -295,6 +304,12 @@ public final class DefaultAnimatedStickerNodeImpl: ASDisplayNode, AnimatedSticke self.renderer = DefaultAnimatedStickerNodeImpl.hardwareRendererPool.take() } else { self.renderer = DefaultAnimatedStickerNodeImpl.softwareRendererPool.take() + + if let renderer = self.renderer?.renderer as? SoftwareAnimationRenderer { + renderer.renderAsTemplateImage = self.dynamicColor != nil + } + self.renderer?.renderer.view.tintColor = self.dynamicColor + if let contents = self.nodeToCopyFrameFrom?.renderer?.renderer.contents { self.renderer?.renderer.contents = contents } diff --git a/submodules/AnimatedStickerNode/Sources/SoftwareAnimationRenderer.swift b/submodules/AnimatedStickerNode/Sources/SoftwareAnimationRenderer.swift index 555c14dc46..57a1e49b06 100644 --- a/submodules/AnimatedStickerNode/Sources/SoftwareAnimationRenderer.swift +++ b/submodules/AnimatedStickerNode/Sources/SoftwareAnimationRenderer.swift @@ -7,20 +7,30 @@ import YuvConversion import Accelerate final class SoftwareAnimationRenderer: ASDisplayNode, AnimationRenderer { + private let templateImageSupport: Bool + private var highlightedContentNode: ASDisplayNode? private var highlightedColor: UIColor? private var highlightReplacesContent = false + public var renderAsTemplateImage: Bool = false - public var currentFrameImage: UIImage? { - if let contents = self.contents { - return UIImage(cgImage: contents as! CGImage) - } else { - return nil + public private(set) var currentFrameImage: UIImage? + + init(templateImageSupport: Bool) { + self.templateImageSupport = templateImageSupport + + super.init() + + if templateImageSupport { + self.setViewBlock({ + return UIImageView() + }) } } func render(queue: Queue, width: Int, height: Int, bytesPerRow: Int, data: Data, type: AnimationRendererFrameType, mulAlpha: Bool, completion: @escaping () -> Void) { assert(bytesPerRow > 0) + let renderAsTemplateImage = self.renderAsTemplateImage queue.async { [weak self] in switch type { case .argb: @@ -70,13 +80,21 @@ final class SoftwareAnimationRenderer: ASDisplayNode, AnimationRenderer { break } }) + if renderAsTemplateImage { + image = image?.withRenderingMode(.alwaysTemplate) + } } Queue.mainQueue().async { guard let strongSelf = self else { return } - strongSelf.contents = image?.cgImage + strongSelf.currentFrameImage = image + if strongSelf.templateImageSupport { + (strongSelf.view as? UIImageView)?.image = image + } else { + strongSelf.contents = image?.cgImage + } strongSelf.updateHighlightedContentNode() if strongSelf.highlightedContentNode?.frame != strongSelf.bounds { strongSelf.highlightedContentNode?.frame = strongSelf.bounds @@ -90,12 +108,14 @@ final class SoftwareAnimationRenderer: ASDisplayNode, AnimationRenderer { guard let highlightedContentNode = self.highlightedContentNode, let highlightedColor = self.highlightedColor else { return } - if let contents = self.contents, CFGetTypeID(contents as CFTypeRef) == CGImage.typeID { - (highlightedContentNode.view as! UIImageView).image = UIImage(cgImage: contents as! CGImage).withRenderingMode(.alwaysTemplate) - } + (highlightedContentNode.view as! UIImageView).image = self.currentFrameImage?.withRenderingMode(.alwaysTemplate) highlightedContentNode.tintColor = highlightedColor if self.highlightReplacesContent { - self.contents = nil + if self.templateImageSupport { + (self.view as? UIImageView)?.image = nil + } else { + self.contents = nil + } } } diff --git a/submodules/ComponentFlow/Source/Base/Transition.swift b/submodules/ComponentFlow/Source/Base/Transition.swift index f8f6e6344b..2e81ec8f4d 100644 --- a/submodules/ComponentFlow/Source/Base/Transition.swift +++ b/submodules/ComponentFlow/Source/Base/Transition.swift @@ -136,21 +136,30 @@ public struct Transition { //view.layer.position = CGPoint(x: frame.midX, y: frame.midY) view.layer.removeAnimation(forKey: "position") view.layer.removeAnimation(forKey: "bounds") + view.layer.removeAnimation(forKey: "bounds.size") completion?(true) case .curve: - let previousFrame: CGRect - if (view.layer.animation(forKey: "position") != nil || view.layer.animation(forKey: "bounds") != nil), let presentation = view.layer.presentation() { - previousFrame = presentation.frame + let previousPosition: CGPoint + let previousBounds: CGRect + if (view.layer.animation(forKey: "position") != nil || view.layer.animation(forKey: "bounds") != nil || view.layer.animation(forKey: "bounds.size") != nil), let presentation = view.layer.presentation() { + previousPosition = presentation.position + previousBounds = presentation.bounds } else { - previousFrame = view.frame + previousPosition = view.layer.position + previousBounds = view.layer.bounds } view.frame = frame //view.bounds = CGRect(origin: previousBounds.origin, size: frame.size) //view.center = CGPoint(x: frame.midX, y: frame.midY) + + let anchorPoint = view.layer.anchorPoint + let updatedPosition = CGPoint(x: frame.minX + frame.width * anchorPoint.x, y: frame.minY + frame.height * anchorPoint.y) - self.animatePosition(view: view, from: CGPoint(x: previousFrame.midX, y: previousFrame.midY), to: CGPoint(x: frame.midX, y: frame.midY), completion: completion) - self.animateBoundsSize(view: view, from: previousFrame.size, to: frame.size) + self.animatePosition(view: view, from: previousPosition, to: updatedPosition, completion: completion) + if previousBounds.size != frame.size { + self.animateBoundsSize(view: view, from: previousBounds.size, to: frame.size) + } } } @@ -193,10 +202,12 @@ public struct Transition { case .none: view.bounds = bounds view.layer.removeAnimation(forKey: "bounds") + view.layer.removeAnimation(forKey: "bounds.origin") + view.layer.removeAnimation(forKey: "bounds.size") completion?(true) case .curve: let previousBounds: CGRect - if view.layer.animation(forKey: "bounds") != nil, let presentation = view.layer.presentation() { + if (view.layer.animation(forKey: "bounds") != nil || view.layer.animation(forKey: "bounds.origin") != nil || view.layer.animation(forKey: "bounds.size") != nil), let presentation = view.layer.presentation() { previousBounds = presentation.bounds } else { previousBounds = view.layer.bounds @@ -207,6 +218,30 @@ public struct Transition { } } + public func setBoundsOrigin(view: UIView, origin: CGPoint, completion: ((Bool) -> Void)? = nil) { + if view.bounds.origin == origin { + completion?(true) + return + } + switch self.animation { + case .none: + view.bounds = CGRect(origin: origin, size: view.bounds.size) + view.layer.removeAnimation(forKey: "bounds") + view.layer.removeAnimation(forKey: "bounds.origin") + completion?(true) + case .curve: + let previousOrigin: CGPoint + if (view.layer.animation(forKey: "bounds") != nil || view.layer.animation(forKey: "bounds.origin") != nil), let presentation = view.layer.presentation() { + previousOrigin = presentation.bounds.origin + } else { + previousOrigin = view.layer.bounds.origin + } + view.bounds = CGRect(origin: origin, size: view.bounds.size) + + self.animateBoundsOrigin(view: view, from: previousOrigin, to: origin, completion: completion) + } + } + public func setBoundsSize(view: UIView, size: CGSize, completion: ((Bool) -> Void)? = nil) { if view.bounds.size == size { completion?(true) diff --git a/submodules/Display/Source/NavigationButtonNode.swift b/submodules/Display/Source/NavigationButtonNode.swift index dea5779c0b..b8528c798d 100644 --- a/submodules/Display/Source/NavigationButtonNode.swift +++ b/submodules/Display/Source/NavigationButtonNode.swift @@ -395,6 +395,12 @@ public final class NavigationButtonNode: ContextControllerSourceNode { } } + public func updateManualAlpha(alpha: CGFloat, transition: ContainedViewLayoutTransition) { + for node in self.nodes { + transition.updateAlpha(node: node, alpha: alpha) + } + } + func updateManualText(_ text: String, isBack: Bool = true) { let node: NavigationButtonItemNode if self.nodes.count > 0 { diff --git a/submodules/FeaturedStickersScreen/Sources/FeaturedStickersScreen.swift b/submodules/FeaturedStickersScreen/Sources/FeaturedStickersScreen.swift index 128cb9700a..d93e4d9e29 100644 --- a/submodules/FeaturedStickersScreen/Sources/FeaturedStickersScreen.swift +++ b/submodules/FeaturedStickersScreen/Sources/FeaturedStickersScreen.swift @@ -552,7 +552,7 @@ private final class FeaturedStickersScreenNode: ViewControllerTracingNode { } })) ] - return (itemNode.view, itemNode.bounds, StickerPreviewPeekContent(account: strongSelf.context.account, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, item: item, menu: menuItems, openPremiumIntro: { [weak self] in + return (itemNode.view, itemNode.bounds, StickerPreviewPeekContent(context: strongSelf.context, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, item: item, menu: menuItems, openPremiumIntro: { [weak self] in guard let strongSelf = self else { return } @@ -640,7 +640,7 @@ private final class FeaturedStickersScreenNode: ViewControllerTracingNode { } })) ] - return (itemNode.view, itemNode.bounds, StickerPreviewPeekContent(account: strongSelf.context.account, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, item: .pack(item.file), menu: menuItems, openPremiumIntro: { [weak self] in + return (itemNode.view, itemNode.bounds, StickerPreviewPeekContent(context: strongSelf.context, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, item: .pack(item.file), menu: menuItems, openPremiumIntro: { [weak self] in guard let strongSelf = self else { return } diff --git a/submodules/ImportStickerPackUI/Sources/ImportStickerPackControllerNode.swift b/submodules/ImportStickerPackUI/Sources/ImportStickerPackControllerNode.swift index 163dadd050..95d4c99ab8 100644 --- a/submodules/ImportStickerPackUI/Sources/ImportStickerPackControllerNode.swift +++ b/submodules/ImportStickerPackUI/Sources/ImportStickerPackControllerNode.swift @@ -259,7 +259,7 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll } }))) } - return .single((itemNode.view, itemNode.bounds, StickerPreviewPeekContent(account: strongSelf.context.account, item: item, menu: menuItems))) + return .single((itemNode.view, itemNode.bounds, StickerPreviewPeekContent(context: strongSelf.context, item: item, menu: menuItems))) } } return nil diff --git a/submodules/ImportStickerPackUI/Sources/StickerPreviewPeekContent.swift b/submodules/ImportStickerPackUI/Sources/StickerPreviewPeekContent.swift index ed757880c8..4e815ee4f8 100644 --- a/submodules/ImportStickerPackUI/Sources/StickerPreviewPeekContent.swift +++ b/submodules/ImportStickerPackUI/Sources/StickerPreviewPeekContent.swift @@ -9,14 +9,15 @@ import StickerResources import AnimatedStickerNode import TelegramAnimatedStickerNode import ContextUI +import AccountContext final class StickerPreviewPeekContent: PeekControllerContent { - let account: Account + let context: AccountContext let item: ImportStickerPack.Sticker let menu: [ContextMenuItem] - init(account: Account, item: ImportStickerPack.Sticker, menu: [ContextMenuItem]) { - self.account = account + init(context: AccountContext, item: ImportStickerPack.Sticker, menu: [ContextMenuItem]) { + self.context = context self.item = item self.menu = menu } @@ -34,7 +35,7 @@ final class StickerPreviewPeekContent: PeekControllerContent { } func node() -> PeekControllerContentNode & ASDisplayNode { - return StickerPreviewPeekContentNode(account: self.account, item: self.item) + return StickerPreviewPeekContentNode(account: self.context.account, item: self.item) } func topAccessoryNode() -> ASDisplayNode? { diff --git a/submodules/ItemListUI/Sources/Items/ItemListExpandableSwitchItem.swift b/submodules/ItemListUI/Sources/Items/ItemListExpandableSwitchItem.swift index e933571d3c..bdf9190160 100644 --- a/submodules/ItemListUI/Sources/Items/ItemListExpandableSwitchItem.swift +++ b/submodules/ItemListUI/Sources/Items/ItemListExpandableSwitchItem.swift @@ -169,6 +169,7 @@ private final class SubItemNode: HighlightTrackingButtonNode { } } else { checkNode = CheckNode(theme: CheckNodeTheme(theme: presentationData.theme, style: .plain)) + checkNode.isUserInteractionEnabled = false self.checkNode = checkNode self.addSubnode(checkNode) } diff --git a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift index c19c67ecc0..23ffc175cf 100644 --- a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift +++ b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift @@ -460,7 +460,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { } else { strongSelf.stableEmptyResultEmoji = nil } - emojiContent = emojiContent.withUpdatedItemGroups(panelItemGroups: emojiContent.panelItemGroups, contentItemGroups: emojiSearchResult.groups, itemContentUniqueId: emojiSearchResult.id, emptySearchResults: emptySearchResults) + emojiContent = emojiContent.withUpdatedItemGroups(panelItemGroups: emojiContent.panelItemGroups, contentItemGroups: emojiSearchResult.groups, itemContentUniqueId: emojiSearchResult.id, emptySearchResults: emptySearchResults, searchState: .active) } else { strongSelf.stableEmptyResultEmoji = nil } diff --git a/submodules/SettingsUI/Sources/Data and Storage/AutodownloadMediaCategoryController.swift b/submodules/SettingsUI/Sources/Data and Storage/AutodownloadMediaCategoryController.swift index 5605d90557..7ed34ef52d 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/AutodownloadMediaCategoryController.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/AutodownloadMediaCategoryController.swift @@ -12,14 +12,22 @@ import AccountContext public func autodownloadDataSizeString(_ size: Int64, decimalSeparator: String = ".") -> String { if size >= 1024 * 1024 * 1024 { - let remainder = (size % (1024 * 1024 * 1024)) / (1024 * 1024 * 102) + var remainder = (size % (1024 * 1024 * 1024)) / (1024 * 1024 * 102) + while remainder != 0 && remainder % 10 == 0 { + remainder /= 10 + } + if remainder != 0 { return "\(size / (1024 * 1024 * 1024))\(decimalSeparator)\(remainder) GB" } else { return "\(size / (1024 * 1024 * 1024)) GB" } } else if size >= 1024 * 1024 { - let remainder = (size % (1024 * 1024)) / (1024 * 102) + var remainder = (size % (1024 * 1024)) / (1024 * 102) + while remainder != 0 && remainder % 10 == 0 { + remainder /= 10 + } + if size < 10 * 1024 * 1024 { return "\(size / (1024 * 1024))\(decimalSeparator)\(remainder) MB" } else { diff --git a/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewControllerNode.swift b/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewControllerNode.swift index 9a0925950d..8ba9bfd247 100644 --- a/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewControllerNode.swift +++ b/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewControllerNode.swift @@ -239,7 +239,7 @@ final class StickerPackPreviewControllerNode: ViewControllerTracingNode, UIScrol } }))) } - return (itemNode.view, itemNode.bounds, StickerPreviewPeekContent(account: strongSelf.context.account, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, item: .pack(item.file), isLocked: item.file.isPremiumSticker && !hasPremium, menu: menuItems, openPremiumIntro: { + return (itemNode.view, itemNode.bounds, StickerPreviewPeekContent(context: strongSelf.context, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, item: .pack(item.file), isLocked: item.file.isPremiumSticker && !hasPremium, menu: menuItems, openPremiumIntro: { })) } else { diff --git a/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift b/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift index 934129b7f5..3ca11e0a57 100644 --- a/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift +++ b/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift @@ -481,7 +481,7 @@ private final class StickerPackContainer: ASDisplayNode { } }))) } - return (itemNode.view, itemNode.bounds, StickerPreviewPeekContent(account: strongSelf.context.account, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, item: .pack(item.file), isLocked: item.file.isPremiumSticker && !hasPremium, menu: menuItems, openPremiumIntro: { [weak self] in + return (itemNode.view, itemNode.bounds, StickerPreviewPeekContent(context: strongSelf.context, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, item: .pack(item.file), isLocked: item.file.isPremiumSticker && !hasPremium, menu: menuItems, openPremiumIntro: { [weak self] in guard let strongSelf = self else { return } diff --git a/submodules/StickerPeekUI/Sources/StickerPreviewPeekContent.swift b/submodules/StickerPeekUI/Sources/StickerPreviewPeekContent.swift index 26a569fe15..f291623ed0 100644 --- a/submodules/StickerPeekUI/Sources/StickerPreviewPeekContent.swift +++ b/submodules/StickerPeekUI/Sources/StickerPreviewPeekContent.swift @@ -29,7 +29,7 @@ public enum StickerPreviewPeekItem: Equatable { } public final class StickerPreviewPeekContent: PeekControllerContent { - let account: Account + let context: AccountContext let theme: PresentationTheme let strings: PresentationStrings public let item: StickerPreviewPeekItem @@ -37,8 +37,8 @@ public final class StickerPreviewPeekContent: PeekControllerContent { let menu: [ContextMenuItem] let openPremiumIntro: () -> Void - public init(account: Account, theme: PresentationTheme, strings: PresentationStrings, item: StickerPreviewPeekItem, isLocked: Bool = false, menu: [ContextMenuItem], openPremiumIntro: @escaping () -> Void) { - self.account = account + public init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, item: StickerPreviewPeekItem, isLocked: Bool = false, menu: [ContextMenuItem], openPremiumIntro: @escaping () -> Void) { + self.context = context self.theme = theme self.strings = strings self.item = item @@ -64,7 +64,7 @@ public final class StickerPreviewPeekContent: PeekControllerContent { } public func node() -> PeekControllerContentNode & ASDisplayNode { - return StickerPreviewPeekContentNode(account: self.account, item: self.item) + return StickerPreviewPeekContentNode(context: self.context, item: self.item, theme: self.theme) } public func topAccessoryNode() -> ASDisplayNode? { @@ -91,7 +91,7 @@ public final class StickerPreviewPeekContent: PeekControllerContent { } public final class StickerPreviewPeekContentNode: ASDisplayNode, PeekControllerContentNode { - private let account: Account + private let context: AccountContext private let item: StickerPreviewPeekItem private var textNode: ASTextNode @@ -105,8 +105,8 @@ public final class StickerPreviewPeekContentNode: ASDisplayNode, PeekControllerC private let _ready = Promise() - init(account: Account, item: StickerPreviewPeekItem) { - self.account = account + init(context: AccountContext, item: StickerPreviewPeekItem, theme: PresentationTheme) { + self.context = context self.item = item self.textNode = ASTextNode() @@ -133,14 +133,18 @@ public final class StickerPreviewPeekContentNode: ASDisplayNode, PeekControllerC } let fittedDimensions = dimensions.cgSize.aspectFitted(fitSize) - animationNode.setup(source: AnimatedStickerResourceSource(account: account, resource: item.file.resource, isVideo: item.file.isVideoSticker), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), playbackMode: isPremiumSticker ? .once : .loop, mode: .direct(cachePathPrefix: nil)) + if item.file.isCustomTemplateEmoji { + animationNode.dynamicColor = theme.list.itemPrimaryTextColor + } + + animationNode.setup(source: AnimatedStickerResourceSource(account: context.account, resource: item.file.resource, isVideo: item.file.isVideoSticker), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), playbackMode: isPremiumSticker ? .once : .loop, mode: .direct(cachePathPrefix: nil)) animationNode.visibility = true animationNode.addSubnode(self.textNode) if isPremiumSticker, let effect = item.file.videoThumbnails.first { - self.effectDisposable.set(freeMediaFileResourceInteractiveFetched(account: account, userLocation: .other, fileReference: .standalone(media: item.file), resource: effect.resource).start()) + self.effectDisposable.set(freeMediaFileResourceInteractiveFetched(account: context.account, userLocation: .other, fileReference: .standalone(media: item.file), resource: effect.resource).start()) - let source = AnimatedStickerResourceSource(account: account, resource: effect.resource, fitzModifier: nil) + let source = AnimatedStickerResourceSource(account: context.account, resource: effect.resource, fitzModifier: nil) let additionalAnimationNode = DefaultAnimatedStickerNodeImpl() additionalAnimationNode.setup(source: source, width: Int(fittedDimensions.width * 2.0), height: Int(fittedDimensions.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: nil)) additionalAnimationNode.visibility = true @@ -151,7 +155,7 @@ public final class StickerPreviewPeekContentNode: ASDisplayNode, PeekControllerC self.animationNode = nil } - self.imageNode.setSignal(chatMessageSticker(account: account, userLocation: .other, file: item.file, small: false, fetched: true)) + self.imageNode.setSignal(chatMessageSticker(account: context.account, userLocation: .other, file: item.file, small: false, fetched: true)) super.init() diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaFile.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaFile.swift index 62615e5fe1..76b96e9e1c 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaFile.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaFile.swift @@ -633,6 +633,9 @@ public final class TelegramMediaFile: Media, Equatable, Codable { if case .Sticker = attribute { hasSticker = true break + } else if case .CustomEmoji = attribute { + hasSticker = true + break } } return hasSticker diff --git a/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift b/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift index 4cf8539699..0eefecb582 100644 --- a/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift +++ b/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift @@ -850,9 +850,9 @@ final class AvatarEditorScreenComponent: Component { } if state?.keyboardContentId == AnyHashable("emoji") { - data.emoji = data.emoji.withUpdatedItemGroups(panelItemGroups: data.emoji.panelItemGroups, contentItemGroups: searchResult.groups, itemContentUniqueId: searchResult.id, emptySearchResults: emptySearchResults) + data.emoji = data.emoji.withUpdatedItemGroups(panelItemGroups: data.emoji.panelItemGroups, contentItemGroups: searchResult.groups, itemContentUniqueId: searchResult.id, emptySearchResults: emptySearchResults, searchState: .active) } else { - data.stickers = data.stickers?.withUpdatedItemGroups(panelItemGroups: data.stickers?.panelItemGroups ?? searchResult.groups, contentItemGroups: searchResult.groups, itemContentUniqueId: searchResult.id, emptySearchResults: emptySearchResults) + data.stickers = data.stickers?.withUpdatedItemGroups(panelItemGroups: data.stickers?.panelItemGroups ?? searchResult.groups, contentItemGroups: searchResult.groups, itemContentUniqueId: searchResult.id, emptySearchResults: emptySearchResults, searchState: .active) } } diff --git a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift index 9bc6941a75..6fa19c847c 100644 --- a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift +++ b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift @@ -608,7 +608,11 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { strongSelf.currentUndoOverlayController = controller controllerInteraction.presentController(controller, nil) }, - copyEmoji: { file in + copyEmoji: { [weak self] file in + guard let strongSelf = self else { + return + } + var text = "." var emojiAttribute: ChatTextInputTextCustomEmojiAttribute? loop: for attribute in file.attributes { @@ -625,6 +629,20 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { if let _ = emojiAttribute { storeMessageTextInPasteboard(text, entities: [MessageTextEntity(range: 0 ..< (text as NSString).length, type: .CustomEmoji(stickerPack: nil, fileId: file.fileId.id))]) + + var animateInAsReplacement = false + if let currentUndoOverlayController = strongSelf.currentUndoOverlayController { + currentUndoOverlayController.dismissWithCommitActionAndReplacementAnimation() + strongSelf.currentUndoOverlayController = nil + animateInAsReplacement = true + } + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + + //TODO:localize + let controller = UndoOverlayController(presentationData: presentationData, content: .sticker(context: context, file: file, title: nil, text: "Emoji copied to clipboard.", undoText: nil, customAction: nil), elevatedLayout: false, animateInAsReplacement: animateInAsReplacement, action: { _ in return false }) + strongSelf.currentUndoOverlayController = controller + controllerInteraction.presentController(controller, nil) } }, presentController: controllerInteraction.presentController, @@ -1425,7 +1443,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { ) } if let emoji = inputData.emoji { - inputData.emoji = emoji.withUpdatedItemGroups(panelItemGroups: emoji.panelItemGroups, contentItemGroups: emojiSearchResult.groups, itemContentUniqueId: emojiSearchResult.id, emptySearchResults: emptySearchResults) + inputData.emoji = emoji.withUpdatedItemGroups(panelItemGroups: emoji.panelItemGroups, contentItemGroups: emojiSearchResult.groups, itemContentUniqueId: emojiSearchResult.id, emptySearchResults: emptySearchResults, searchState: .active) } } @@ -1439,7 +1457,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { ) } if let stickers = inputData.stickers { - inputData.stickers = stickers.withUpdatedItemGroups(panelItemGroups: stickers.panelItemGroups, contentItemGroups: stickerSearchResult.groups, itemContentUniqueId: stickerSearchResult.id, emptySearchResults: stickerSearchResults) + inputData.stickers = stickers.withUpdatedItemGroups(panelItemGroups: stickers.panelItemGroups, contentItemGroups: stickerSearchResult.groups, itemContentUniqueId: stickerSearchResult.id, emptySearchResults: stickerSearchResults, searchState: .active) } } @@ -1861,10 +1879,10 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { private func processInputData(inputData: InputData) -> InputData { return InputData( emoji: inputData.emoji.flatMap { emoji in - return emoji.withUpdatedItemGroups(panelItemGroups: self.processStableItemGroupList(category: .emoji, itemGroups: emoji.panelItemGroups), contentItemGroups: self.processStableItemGroupList(category: .emoji, itemGroups: emoji.contentItemGroups), itemContentUniqueId: emoji.itemContentUniqueId, emptySearchResults: emoji.emptySearchResults) + return emoji.withUpdatedItemGroups(panelItemGroups: self.processStableItemGroupList(category: .emoji, itemGroups: emoji.panelItemGroups), contentItemGroups: self.processStableItemGroupList(category: .emoji, itemGroups: emoji.contentItemGroups), itemContentUniqueId: emoji.itemContentUniqueId, emptySearchResults: emoji.emptySearchResults, searchState: emoji.searchState) }, stickers: inputData.stickers.flatMap { stickers in - return stickers.withUpdatedItemGroups(panelItemGroups: self.processStableItemGroupList(category: .stickers, itemGroups: stickers.panelItemGroups), contentItemGroups: self.processStableItemGroupList(category: .stickers, itemGroups: stickers.contentItemGroups), itemContentUniqueId: stickers.itemContentUniqueId, emptySearchResults: nil) + return stickers.withUpdatedItemGroups(panelItemGroups: self.processStableItemGroupList(category: .stickers, itemGroups: stickers.panelItemGroups), contentItemGroups: self.processStableItemGroupList(category: .stickers, itemGroups: stickers.contentItemGroups), itemContentUniqueId: stickers.itemContentUniqueId, emptySearchResults: nil, searchState: stickers.searchState) }, gifs: inputData.gifs, availableGifSearchEmojies: inputData.availableGifSearchEmojies @@ -2503,7 +2521,7 @@ public final class EmojiContentPeekBehaviorImpl: EmojiContentPeekBehavior { return nil } - return (view, itemLayer.convert(itemLayer.bounds, to: view.layer), StickerPreviewPeekContent(account: context.account, theme: presentationData.theme, strings: presentationData.strings, item: .pack(file), isLocked: isLocked, menu: menuItems, openPremiumIntro: { + return (view, itemLayer.convert(itemLayer.bounds, to: view.layer), StickerPreviewPeekContent(context: context, theme: presentationData.theme, strings: presentationData.strings, item: .pack(file), isLocked: isLocked, menu: menuItems, openPremiumIntro: { guard let strongSelf = self, let interaction = strongSelf.interaction else { return } @@ -2632,7 +2650,7 @@ public final class EmojiContentPeekBehaviorImpl: EmojiContentPeekBehavior { return nil } - return (view, itemLayer.convert(itemLayer.bounds, to: view.layer), StickerPreviewPeekContent(account: context.account, theme: presentationData.theme, strings: presentationData.strings, item: .pack(file), isLocked: isLocked && !isStarred, menu: menuItems, openPremiumIntro: { + return (view, itemLayer.convert(itemLayer.bounds, to: view.layer), StickerPreviewPeekContent(context: context, theme: presentationData.theme, strings: presentationData.strings, item: .pack(file), isLocked: isLocked && !isStarred, menu: menuItems, openPremiumIntro: { guard let strongSelf = self, let interaction = strongSelf.interaction else { return } diff --git a/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift b/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift index e70b7011c3..8655e96284 100644 --- a/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift +++ b/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift @@ -371,7 +371,7 @@ public final class EmojiStatusSelectionController: ViewController { } else { strongSelf.stableEmptyResultEmoji = nil } - emojiContent = emojiContent.withUpdatedItemGroups(panelItemGroups: emojiContent.panelItemGroups, contentItemGroups: emojiSearchResult.groups, itemContentUniqueId: emojiSearchResult.id, emptySearchResults: emptySearchResults) + emojiContent = emojiContent.withUpdatedItemGroups(panelItemGroups: emojiContent.panelItemGroups, contentItemGroups: emojiSearchResult.groups, itemContentUniqueId: emojiSearchResult.id, emptySearchResults: emptySearchResults, searchState: .active) } else { strongSelf.stableEmptyResultEmoji = nil } diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift index 8724a958b4..e9e5926646 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift @@ -1330,6 +1330,15 @@ private final class GroupEmbeddedView: UIScrollView, UIScrollViewDelegate, Pager self.layer.addSublayer(itemLayer) } + switch item.tintMode { + case .accent: + itemLayer.layerTintColor = theme.list.itemAccentColor.cgColor + case .primary: + itemLayer.layerTintColor = theme.list.itemPrimaryTextColor.cgColor + case .none: + itemLayer.layerTintColor = nil + } + let itemFrame = itemLayout.frame(at: index) itemLayer.frame = itemFrame @@ -1708,17 +1717,24 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate { if case .ended = recognizer.state { let location = recognizer.location(in: self) if self.backIconView.frame.contains(location) { - if let placeholderContentView = self.placeholderContent.view as? EmojiSearchSearchBarComponent.View { - placeholderContentView.clearSelection(dispatchEvent : true) - } + self.clearCategorySearch() } else { self.activateTextInput() } } } + func clearCategorySearch() { + if let placeholderContentView = self.placeholderContent.view as? EmojiSearchSearchBarComponent.View { + placeholderContentView.clearSelection(dispatchEvent : true) + } + } + private func activateTextInput() { - if self.textField == nil, let textFrame = self.textFrame, self.params?.canFocus == true { + guard let params = self.params else { + return + } + if self.textField == nil, let textFrame = self.textFrame, params.canFocus == true { let backgroundFrame = self.backgroundLayer.frame let textFieldFrame = CGRect(origin: CGPoint(x: textFrame.minX, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.maxX - textFrame.minX, height: backgroundFrame.height)) @@ -1730,9 +1746,11 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate { textField.addTarget(self, action: #selector(self.textFieldChanged(_:)), for: .editingChanged) } - self.currentPresetSearchTerm = nil - if let placeholderContentView = self.placeholderContent.view as? EmojiSearchSearchBarComponent.View { - placeholderContentView.clearSelection(dispatchEvent: false) + if params.canFocus { + self.currentPresetSearchTerm = nil + if let placeholderContentView = self.placeholderContent.view as? EmojiSearchSearchBarComponent.View { + placeholderContentView.clearSelection(dispatchEvent: false) + } } self.activated(true) @@ -1968,9 +1986,9 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate { self.currentPresetSearchTerm = term if shouldChangeActivation { - self.update(transition: Transition(animation: .curve(duration: 0.4, curve: .spring))) - if let term { + self.update(transition: Transition(animation: .curve(duration: 0.4, curve: .spring))) + self.updateQuery(.category(value: term)) self.activated(false) } else { @@ -2049,6 +2067,7 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate { cancelButtonTitleComponentView.isUserInteractionEnabled = false } transition.setFrame(view: cancelButtonTitleComponentView, frame: CGRect(origin: CGPoint(x: backgroundFrame.maxX + cancelButtonSpacing, y: floor((size.height - cancelTextSize.height) / 2.0)), size: cancelTextSize)) + transition.setAlpha(view: cancelButtonTitleComponentView, alpha: isActiveWithText ? 1.0 : 0.0) } if let cancelButtonTintTitleComponentView = self.cancelButtonTintTitle.view { if cancelButtonTintTitleComponentView.superview == nil { @@ -2056,6 +2075,7 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate { cancelButtonTintTitleComponentView.isUserInteractionEnabled = false } transition.setFrame(view: cancelButtonTintTitleComponentView, frame: CGRect(origin: CGPoint(x: backgroundFrame.maxX + cancelButtonSpacing, y: floor((size.height - cancelTextSize.height) / 2.0)), size: cancelTextSize)) + transition.setAlpha(view: cancelButtonTintTitleComponentView, alpha: isActiveWithText ? 1.0 : 0.0) } var hasText = false @@ -2103,9 +2123,9 @@ private final class EmptySearchResultsView: UIView { func update(context: AccountContext, theme: PresentationTheme, useOpaqueTheme: Bool, text: String, file: TelegramMediaFile?, size: CGSize, searchInitiallyHidden: Bool, transition: Transition) { let titleColor: UIColor if useOpaqueTheme { - titleColor = theme.chat.inputMediaPanel.panelContentControlOpaqueOverlayColor + titleColor = theme.chat.inputMediaPanel.panelContentOpaqueSearchOverlayColor } else { - titleColor = theme.chat.inputMediaPanel.panelContentControlVibrantOverlayColor + titleColor = theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor } let iconSize: CGSize @@ -2513,6 +2533,12 @@ public final class EmojiPagerContentComponent: Component { case detailed } + public enum SearchState { + case empty + case searching + case active + } + public final class EmptySearchResults: Equatable { public let text: String public let iconFile: TelegramMediaFile? @@ -2543,6 +2569,7 @@ public final class EmojiPagerContentComponent: Component { public let contentItemGroups: [ItemGroup] public let itemLayoutType: ItemLayoutType public let itemContentUniqueId: AnyHashable? + public let searchState: SearchState public let warpContentsOnEdges: Bool public let displaySearchWithPlaceholder: String? public let searchCategories: EmojiSearchCategories? @@ -2564,6 +2591,7 @@ public final class EmojiPagerContentComponent: Component { contentItemGroups: [ItemGroup], itemLayoutType: ItemLayoutType, itemContentUniqueId: AnyHashable?, + searchState: SearchState, warpContentsOnEdges: Bool, displaySearchWithPlaceholder: String?, searchCategories: EmojiSearchCategories?, @@ -2584,6 +2612,7 @@ public final class EmojiPagerContentComponent: Component { self.contentItemGroups = contentItemGroups self.itemLayoutType = itemLayoutType self.itemContentUniqueId = itemContentUniqueId + self.searchState = searchState self.warpContentsOnEdges = warpContentsOnEdges self.displaySearchWithPlaceholder = displaySearchWithPlaceholder self.searchCategories = searchCategories @@ -2595,7 +2624,7 @@ public final class EmojiPagerContentComponent: Component { self.selectedItems = selectedItems } - public func withUpdatedItemGroups(panelItemGroups: [ItemGroup], contentItemGroups: [ItemGroup], itemContentUniqueId: AnyHashable?, emptySearchResults: EmptySearchResults?) -> EmojiPagerContentComponent { + public func withUpdatedItemGroups(panelItemGroups: [ItemGroup], contentItemGroups: [ItemGroup], itemContentUniqueId: AnyHashable?, emptySearchResults: EmptySearchResults?, searchState: SearchState) -> EmojiPagerContentComponent { return EmojiPagerContentComponent( id: self.id, context: self.context, @@ -2607,6 +2636,7 @@ public final class EmojiPagerContentComponent: Component { contentItemGroups: contentItemGroups, itemLayoutType: self.itemLayoutType, itemContentUniqueId: itemContentUniqueId, + searchState: searchState, warpContentsOnEdges: self.warpContentsOnEdges, displaySearchWithPlaceholder: self.displaySearchWithPlaceholder, searchCategories: self.searchCategories, @@ -2650,6 +2680,12 @@ public final class EmojiPagerContentComponent: Component { if lhs.itemLayoutType != rhs.itemLayoutType { return false } + if lhs.itemContentUniqueId != rhs.itemContentUniqueId { + return false + } + if lhs.searchState != rhs.searchState { + return false + } if lhs.warpContentsOnEdges != rhs.warpContentsOnEdges { return false } @@ -2662,6 +2698,9 @@ public final class EmojiPagerContentComponent: Component { if lhs.searchInitiallyHidden != rhs.searchInitiallyHidden { return false } + if lhs.searchAlwaysActive != rhs.searchAlwaysActive { + return false + } if lhs.searchIsPlaceholderOnly != rhs.searchIsPlaceholderOnly { return false } @@ -4313,6 +4352,12 @@ public final class EmojiPagerContentComponent: Component { guard let component = self.component, let pagerEnvironment = self.pagerEnvironment, let itemLayout = self.itemLayout else { return } + + if self.isSearchActivated { + self.visibleSearchHeader?.clearCategorySearch() + return + } + for groupIndex in 0 ..< itemLayout.itemGroupLayouts.count { let group = itemLayout.itemGroupLayouts[groupIndex] @@ -5044,7 +5089,7 @@ public final class EmojiPagerContentComponent: Component { scrollView.layer.removeAllAnimations() } - if self.isSearchActivated, let component = self.component, !component.searchAlwaysActive, let visibleSearchHeader = self.visibleSearchHeader, visibleSearchHeader.currentPresetSearchTerm == nil { + if self.isSearchActivated, let component = self.component, component.searchState == .empty, !component.searchAlwaysActive, let visibleSearchHeader = self.visibleSearchHeader, visibleSearchHeader.currentPresetSearchTerm == nil { scrollView.isScrollEnabled = false DispatchQueue.main.async { scrollView.isScrollEnabled = true @@ -6024,6 +6069,9 @@ public final class EmojiPagerContentComponent: Component { guard let strongSelf = self else { return nil } + if !strongSelf.scrollViewClippingView.bounds.contains(strongSelf.convert(point, to: strongSelf.scrollViewClippingView)) { + return nil + } guard let item = strongSelf.item(atPoint: point), let itemLayer = strongSelf.visibleItemLayers[item.1], let file = item.0.itemFile else { return nil } @@ -6459,12 +6507,15 @@ public final class EmojiPagerContentComponent: Component { strongSelf.isSearchActivated = false strongSelf.pagerEnvironment?.onWantsExclusiveModeUpdated(false) - if strongSelf.component?.searchInitiallyHidden == false { - if !isFirstResponder { - strongSelf.component?.inputInteractionHolder.inputInteraction?.requestUpdate(.easeInOut(duration: 0.2)) - } + + if !isFirstResponder { + strongSelf.component?.inputInteractionHolder.inputInteraction?.requestUpdate( + Transition(animation: .curve(duration: 0.4, curve: .spring))) } else { - strongSelf.component?.inputInteractionHolder.inputInteraction?.requestUpdate(.immediate) + DispatchQueue.main.async { + self?.component?.inputInteractionHolder.inputInteraction?.requestUpdate( + Transition(animation: .curve(duration: 0.4, curve: .spring))) + } } } }, updateQuery: { [weak self] query in @@ -6550,7 +6601,11 @@ public final class EmojiPagerContentComponent: Component { visibleEmptySearchResultsView = EmptySearchResultsView(frame: CGRect()) self.visibleEmptySearchResultsView = visibleEmptySearchResultsView self.addSubview(visibleEmptySearchResultsView) - self.mirrorContentClippingView?.addSubview(visibleEmptySearchResultsView.tintContainerView) + if let mirrorContentClippingView = self.mirrorContentClippingView { + mirrorContentClippingView.addSubview(visibleEmptySearchResultsView.tintContainerView) + } else if let vibrancyEffectView = self.vibrancyEffectView { + vibrancyEffectView.contentView.addSubview(visibleEmptySearchResultsView.tintContainerView) + } } let emptySearchResultsSize = CGSize(width: availableSize.width, height: availableSize.height - itemLayout.searchInsets.top - itemLayout.searchHeight) visibleEmptySearchResultsView.update( @@ -7550,6 +7605,7 @@ public final class EmojiPagerContentComponent: Component { contentItemGroups: allItemGroups, itemLayoutType: .compact, itemContentUniqueId: nil, + searchState: .empty, warpContentsOnEdges: isReactionSelection || isStatusSelection || isProfilePhotoEmojiSelection || isGroupPhotoEmojiSelection, displaySearchWithPlaceholder: displaySearchWithPlaceholder, searchCategories: searchCategories, @@ -8071,6 +8127,7 @@ public final class EmojiPagerContentComponent: Component { contentItemGroups: allItemGroups, itemLayoutType: .detailed, itemContentUniqueId: nil, + searchState: .empty, warpContentsOnEdges: isProfilePhotoEmojiSelection || isGroupPhotoEmojiSelection, displaySearchWithPlaceholder: hasSearch ? strings.StickersSearch_SearchStickersPlaceholder : nil, searchCategories: searchCategories, diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchContent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchContent.swift index 2c3483b3ca..d4769f77e0 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchContent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchContent.swift @@ -391,6 +391,7 @@ public final class EmojiSearchContent: ASDisplayNode, EntitySearchContainerNode contentItemGroups: self.itemGroups, itemLayoutType: .compact, itemContentUniqueId: "main", + searchState: .empty, warpContentsOnEdges: false, displaySearchWithPlaceholder: "Search Emoji", searchCategories: nil, @@ -410,7 +411,7 @@ public final class EmojiSearchContent: ASDisplayNode, EntitySearchContainerNode iconFile: nil ) } - emojiContent = emojiContent.withUpdatedItemGroups(panelItemGroups: emojiContent.panelItemGroups, contentItemGroups: emojiSearchResult.groups, itemContentUniqueId: emojiSearchResult.id, emptySearchResults: emptySearchResults) + emojiContent = emojiContent.withUpdatedItemGroups(panelItemGroups: emojiContent.panelItemGroups, contentItemGroups: emojiSearchResult.groups, itemContentUniqueId: emojiSearchResult.id, emptySearchResults: emptySearchResults, searchState: .active) } let _ = self.keyboardView.update( diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchSearchBarComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchSearchBarComponent.swift index e91ff3fee0..4cf126676c 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchSearchBarComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchSearchBarComponent.swift @@ -131,9 +131,16 @@ final class EmojiSearchSearchBarComponent: Component { self.textFrame = CGRect(origin: CGPoint(x: self.leftInset, y: floor((containerSize.height - textSize.height) * 0.5)), size: textSize) - self.itemStartX = self.textFrame.maxX + self.textSpacing + let itemsWidth: CGFloat = self.itemSize.width * CGFloat(self.itemCount) + self.itemSpacing * CGFloat(max(0, self.itemCount - 1)) - self.contentSize = CGSize(width: self.itemStartX + self.itemSize.width * CGFloat(self.itemCount) + self.itemSpacing * CGFloat(max(0, self.itemCount - 1)) + self.rightInset, height: containerSize.height) + var itemStartX = self.textFrame.maxX + self.textSpacing + if itemStartX + itemsWidth + self.rightInset < containerSize.width { + itemStartX = containerSize.width - self.rightInset - itemsWidth + } + + self.itemStartX = itemStartX + + self.contentSize = CGSize(width: self.itemStartX + itemsWidth + self.rightInset, height: containerSize.height) } func visibleItems(for rect: CGRect) -> Range? { @@ -310,7 +317,7 @@ final class EmojiSearchSearchBarComponent: Component { } } else { let transition = Transition(animation: .curve(duration: 0.4, curve: .spring)) - transition.setBounds(view: self.scrollView, bounds: CGRect(origin: CGPoint(), size: self.scrollView.bounds.size)) + transition.setBoundsOrigin(view: self.scrollView, origin: CGPoint()) self.updateScrolling(transition: transition, fromScrolling: false) //self.scrollView.setContentOffset(CGPoint(), animated: true) @@ -329,7 +336,7 @@ final class EmojiSearchSearchBarComponent: Component { self.selectedItem = nil let transition = Transition(animation: .curve(duration: 0.4, curve: .spring)) - transition.setBounds(view: self.scrollView, bounds: CGRect(origin: CGPoint(), size: self.scrollView.bounds.size)) + transition.setBoundsOrigin(view: self.scrollView, origin: CGPoint()) self.updateScrolling(transition: transition, fromScrolling: false) self.state?.updated(transition: transition) @@ -544,7 +551,7 @@ final class EmojiSearchSearchBarComponent: Component { transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(), size: availableSize)) } if case .active = component.textInputState { - transition.setBounds(view: self.scrollView, bounds: CGRect(origin: CGPoint(), size: availableSize)) + transition.setBoundsOrigin(view: self.scrollView, origin: CGPoint()) } if self.scrollView.contentSize != itemLayout.contentSize { self.scrollView.contentSize = itemLayout.contentSize diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/GifPagerContentComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/GifPagerContentComponent.swift index e1c3326610..a0fa067b71 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/GifPagerContentComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/GifPagerContentComponent.swift @@ -98,7 +98,15 @@ private class GifVideoLayer: AVSampleBufferDisplayLayer { } override init(layer: Any) { - preconditionFailure() + guard let layer = layer as? GifVideoLayer else { + preconditionFailure() + } + + self.context = layer.context + self.userLocation = layer.userLocation + self.file = layer.file + + super.init(layer: layer) } required init?(coder: NSCoder) { @@ -375,6 +383,13 @@ public final class GifPagerContentComponent: Component { } } + override init(layer: Any) { + self.item = nil + self.onUpdateDisplayPlaceholder = { _, _ in } + + super.init(layer: layer) + } + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } diff --git a/submodules/TelegramUI/Components/LottieComponent/Sources/LottieComponent.swift b/submodules/TelegramUI/Components/LottieComponent/Sources/LottieComponent.swift index 584f59703c..5709f2a710 100644 --- a/submodules/TelegramUI/Components/LottieComponent/Sources/LottieComponent.swift +++ b/submodules/TelegramUI/Components/LottieComponent/Sources/LottieComponent.swift @@ -106,7 +106,7 @@ public final class LottieComponent: Component { if delay != 0.0 { self.isHidden = true - DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.08, execute: { [weak self] in + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + delay, execute: { [weak self] in guard let self else { return } diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataCategoriesComponent.swift b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataCategoriesComponent.swift index f3166b2463..eb6a9587ff 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataCategoriesComponent.swift +++ b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataCategoriesComponent.swift @@ -43,13 +43,13 @@ final class DataCategoriesComponent: Component { let theme: PresentationTheme let strings: PresentationStrings let categories: [CategoryData] - let toggleCategoryExpanded: (DataUsageScreenComponent.Category) -> Void + let toggleCategoryExpanded: ((DataUsageScreenComponent.Category) -> Void)? init( theme: PresentationTheme, strings: PresentationStrings, categories: [CategoryData], - toggleCategoryExpanded: @escaping (DataUsageScreenComponent.Category) -> Void + toggleCategoryExpanded: ((DataUsageScreenComponent.Category) -> Void)? ) { self.theme = theme self.strings = strings @@ -138,11 +138,11 @@ final class DataCategoriesComponent: Component { category: category, isExpanded: category.isExpanded, hasNext: i != component.categories.count - 1, - action: { [weak self] key in + action: component.toggleCategoryExpanded == nil ? nil : { [weak self] key in guard let self, let component = self.component else { return } - component.toggleCategoryExpanded(key) + component.toggleCategoryExpanded?(key) } )), environment: {}, diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataCategoryItemCompoment.swift b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataCategoryItemCompoment.swift index 52cdcf9c91..9ad5dfb0c2 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataCategoryItemCompoment.swift +++ b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataCategoryItemCompoment.swift @@ -52,7 +52,7 @@ private final class SubItemComponent: Component { return true } - class View: HighlightTrackingButton { + class View: UIView { private let iconView: UIImageView private let title = ComponentView() private let titleValue = ComponentView() @@ -74,7 +74,7 @@ private final class SubItemComponent: Component { self.addSubview(self.iconView) - self.highligthedChanged = { [weak self] isHighlighted in + /*self.highligthedChanged = { [weak self] isHighlighted in guard let self, let component = self.component, let highlightBackgroundFrame = self.highlightBackgroundFrame else { return } @@ -103,7 +103,7 @@ private final class SubItemComponent: Component { } } self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside) - self.isUserInteractionEnabled = false + self.isEnabled = false*/ } required init?(coder: NSCoder) { @@ -243,7 +243,7 @@ final class DataCategoryItemComponent: Component { let category: DataCategoriesComponent.CategoryData let isExpanded: Bool let hasNext: Bool - let action: (DataUsageScreenComponent.Category) -> Void + let action: ((DataUsageScreenComponent.Category) -> Void)? init( theme: PresentationTheme, @@ -251,7 +251,7 @@ final class DataCategoryItemComponent: Component { category: DataCategoriesComponent.CategoryData, isExpanded: Bool, hasNext: Bool, - action: @escaping (DataUsageScreenComponent.Category) -> Void + action: ((DataUsageScreenComponent.Category) -> Void)? ) { self.theme = theme self.strings = strings @@ -350,14 +350,14 @@ final class DataCategoryItemComponent: Component { guard let component = self.component else { return } - component.action(component.category.key) + component.action?(component.category.key) } @objc private func checkPressed() { guard let component = self.component else { return } - component.action(component.category.key) + component.action?(component.category.key) } override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { @@ -597,6 +597,8 @@ final class DataCategoryItemComponent: Component { transition.setFrame(view: self.subcategoryClippingContainer, frame: CGRect(origin: CGPoint(), size: CGSize(width: availableSize.width, height: height))) + self.isEnabled = component.action != nil + return CGSize(width: availableSize.width, height: height) } } diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataUsageScreen.swift b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataUsageScreen.swift index 0bfaf88083..0dfe11eb8b 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataUsageScreen.swift +++ b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataUsageScreen.swift @@ -468,7 +468,9 @@ final class DataUsageScreenComponent: Component { transition.setBounds(view: self.headerOffsetContainer, bounds: CGRect(origin: CGPoint(x: 0.0, y: headerOffset), size: self.headerOffsetContainer.bounds.size)) if let controller = self.controller?(), let backButtonNode = controller.navigationBar?.backButtonNode { - if backButtonNode.alpha != navigationButtonAlpha { + backButtonNode.updateManualAlpha(alpha: navigationButtonAlpha, transition: animatedTransition.containedViewLayoutTransition) + + /*if backButtonNode.alpha != navigationButtonAlpha { if backButtonNode.isHidden { backButtonNode.alpha = 0.0 backButtonNode.isHidden = false @@ -480,7 +482,7 @@ final class DataUsageScreenComponent: Component { } } }) - } + }*/ } } } @@ -1035,8 +1037,7 @@ final class DataUsageScreenComponent: Component { theme: environment.theme, strings: environment.strings, categories: totalCategories, - toggleCategoryExpanded: { _ in - } + toggleCategoryExpanded: nil )), environment: {}, containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: .greatestFiniteMagnitude) diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/PieChartComponent.swift b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/PieChartComponent.swift index a3a88e5411..cd901ca5b8 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/PieChartComponent.swift +++ b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/PieChartComponent.swift @@ -522,7 +522,7 @@ final class PieChartComponent: Component { let fractionValue: Double = floor(displayValue * 100.0 * 10.0) / 10.0 let fractionString: String - if fractionValue == 0.0 { + if displayValue == 0.0 { fractionString = "" } else if fractionValue < 0.1 { fractionString = "<0.1%" diff --git a/submodules/TelegramUI/Sources/ChatMediaInputNode.swift b/submodules/TelegramUI/Sources/ChatMediaInputNode.swift index b85b0db557..df7e5843f8 100644 --- a/submodules/TelegramUI/Sources/ChatMediaInputNode.swift +++ b/submodules/TelegramUI/Sources/ChatMediaInputNode.swift @@ -1662,7 +1662,7 @@ final class ChatMediaInputNode: ChatInputNode { } } }))) - return (itemNode.view, itemNode.bounds, StickerPreviewPeekContent(account: strongSelf.context.account, theme: strongSelf.theme, strings: strongSelf.strings, item: item, menu: menuItems, openPremiumIntro: { [weak self] in + return (itemNode.view, itemNode.bounds, StickerPreviewPeekContent(context: strongSelf.context, theme: strongSelf.theme, strings: strongSelf.strings, item: item, menu: menuItems, openPremiumIntro: { [weak self] in guard let strongSelf = self else { return } @@ -1819,7 +1819,7 @@ final class ChatMediaInputNode: ChatInputNode { } })) ) - return (itemNode.view, itemNode.bounds, StickerPreviewPeekContent(account: strongSelf.context.account, theme: strongSelf.theme, strings: strongSelf.strings, item: .pack(item.file), isLocked: item.file.isPremiumSticker && !hasPremium, menu: menuItems, openPremiumIntro: { [weak self] in + return (itemNode.view, itemNode.bounds, StickerPreviewPeekContent(context: strongSelf.context, theme: strongSelf.theme, strings: strongSelf.strings, item: .pack(item.file), isLocked: item.file.isPremiumSticker && !hasPremium, menu: menuItems, openPremiumIntro: { [weak self] in guard let strongSelf = self else { return } diff --git a/submodules/TelegramUI/Sources/EmojisChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/EmojisChatInputContextPanelNode.swift index f5b97b01b2..66f0a8880e 100644 --- a/submodules/TelegramUI/Sources/EmojisChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/EmojisChatInputContextPanelNode.swift @@ -423,7 +423,7 @@ final class EmojisChatInputContextPanelNode: ChatInputContextPanelNode { return nil } - let content = StickerPreviewPeekContent(account: context.account, theme: presentationData.theme, strings: presentationData.strings, item: .pack(file), isLocked: isLocked, menu: menuItems, openPremiumIntro: { [weak self] in + let content = StickerPreviewPeekContent(context: context, theme: presentationData.theme, strings: presentationData.strings, item: .pack(file), isLocked: isLocked, menu: menuItems, openPremiumIntro: { [weak self] in guard let self else { return } diff --git a/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputContextPanelNode.swift index 0348c7b5fa..7e0391b776 100644 --- a/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputContextPanelNode.swift @@ -46,8 +46,8 @@ private struct HorizontalListContextResultsChatInputContextPanelEntry: Comparabl return lhs.index < rhs.index } - func item(account: Account, resultSelected: @escaping (ChatContextResult, ASDisplayNode, CGRect) -> Bool) -> ListViewItem { - return HorizontalListContextResultsChatInputPanelItem(account: account, theme: self.theme, result: self.result, resultSelected: resultSelected) + func item(context: AccountContext, resultSelected: @escaping (ChatContextResult, ASDisplayNode, CGRect) -> Bool) -> ListViewItem { + return HorizontalListContextResultsChatInputPanelItem(context: context, theme: self.theme, result: self.result, resultSelected: resultSelected) } } @@ -69,12 +69,12 @@ private final class HorizontalListContextResultsOpaqueState { } } -private func preparedTransition(from fromEntries: [HorizontalListContextResultsChatInputContextPanelEntry], to toEntries: [HorizontalListContextResultsChatInputContextPanelEntry], hasMore: Bool, account: Account, resultSelected: @escaping (ChatContextResult, ASDisplayNode, CGRect) -> Bool) -> HorizontalListContextResultsChatInputContextPanelTransition { +private func preparedTransition(from fromEntries: [HorizontalListContextResultsChatInputContextPanelEntry], to toEntries: [HorizontalListContextResultsChatInputContextPanelEntry], hasMore: Bool, context: AccountContext, resultSelected: @escaping (ChatContextResult, ASDisplayNode, CGRect) -> Bool) -> HorizontalListContextResultsChatInputContextPanelTransition { let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries) let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } - let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, resultSelected: resultSelected), directionHint: nil) } - let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, resultSelected: resultSelected), directionHint: nil) } + let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, resultSelected: resultSelected), directionHint: nil) } + let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, resultSelected: resultSelected), directionHint: nil) } return HorizontalListContextResultsChatInputContextPanelTransition(deletions: deletions, insertions: insertions, updates: updates, entryCount: toEntries.count, hasMore: hasMore) } @@ -147,6 +147,7 @@ final class HorizontalListContextResultsChatInputContextPanelNode: ChatInputCont } var selectedItemNodeAndContent: (UIView, CGRect, PeekControllerContent)? + selectedItemNodeAndContent = nil strongSelf.listView.forEachItemNode { itemNode in if itemNode.frame.contains(convertedPoint), let itemNode = itemNode as? HorizontalListContextResultsChatInputPanelItemNode, let item = itemNode.item { if case let .internalReference(internalReference) = item.result, let file = internalReference.file, file.isSticker { @@ -177,7 +178,7 @@ final class HorizontalListContextResultsChatInputContextPanelNode: ChatInputCont } }))) } - selectedItemNodeAndContent = (itemNode.view, itemNode.bounds, StickerPreviewPeekContent(account: item.account, theme: strongSelf.theme, strings: strongSelf.strings, item: .found(FoundStickerItem(file: file, stringRepresentations: [])), menu: menuItems, openPremiumIntro: { [weak self] in + selectedItemNodeAndContent = (itemNode.view, itemNode.bounds, StickerPreviewPeekContent(context: item.context, theme: strongSelf.theme, strings: strongSelf.strings, item: .found(FoundStickerItem(file: file, stringRepresentations: [])), menu: menuItems, openPremiumIntro: { [weak self] in guard let strongSelf = self else { return } @@ -230,7 +231,7 @@ final class HorizontalListContextResultsChatInputContextPanelNode: ChatInputCont f(.default) let _ = item.resultSelected(item.result, itemNode, itemNode.bounds) }))) - selectedItemNodeAndContent = (itemNode.view, itemNode.bounds, ChatContextResultPeekContent(account: item.account, contextResult: item.result, menu: menuItems)) + selectedItemNodeAndContent = (itemNode.view, itemNode.bounds, ChatContextResultPeekContent(account: item.context.account, contextResult: item.result, menu: menuItems)) } } } @@ -313,7 +314,7 @@ final class HorizontalListContextResultsChatInputContextPanelNode: ChatInputCont } let firstTime = self.currentEntries == nil - let transition = preparedTransition(from: self.currentEntries ?? [], to: entries, hasMore: results.nextOffset != nil, account: self.context.account, resultSelected: { [weak self] result, node, rect in + let transition = preparedTransition(from: self.currentEntries ?? [], to: entries, hasMore: results.nextOffset != nil, context: self.context, resultSelected: { [weak self] result, node, rect in if let strongSelf = self, let interfaceInteraction = strongSelf.interfaceInteraction { return interfaceInteraction.sendContextResult(results, result, node, rect) } else { diff --git a/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputPanelItem.swift b/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputPanelItem.swift index f86022e58a..5e442b9b78 100644 --- a/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputPanelItem.swift +++ b/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputPanelItem.swift @@ -18,15 +18,15 @@ import SoftwareVideo import MultiplexedVideoNode final class HorizontalListContextResultsChatInputPanelItem: ListViewItem { - let account: Account + let context: AccountContext let theme: PresentationTheme let result: ChatContextResult let resultSelected: (ChatContextResult, ASDisplayNode, CGRect) -> Bool let selectable: Bool = true - public init(account: Account, theme: PresentationTheme, result: ChatContextResult, resultSelected: @escaping (ChatContextResult, ASDisplayNode, CGRect) -> Bool) { - self.account = account + public init(context: AccountContext, theme: PresentationTheme, result: ChatContextResult, resultSelected: @escaping (ChatContextResult, ASDisplayNode, CGRect) -> Bool) { + self.context = context self.theme = theme self.result = result self.resultSelected = resultSelected @@ -265,9 +265,9 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode } if let file = videoFile { - updatedStatusSignal = item.account.postbox.mediaBox.resourceStatus(file.resource) + updatedStatusSignal = item.context.account.postbox.mediaBox.resourceStatus(file.resource) } else if let imageResource = imageResource { - updatedStatusSignal = item.account.postbox.mediaBox.resourceStatus(imageResource) + updatedStatusSignal = item.context.account.postbox.mediaBox.resourceStatus(imageResource) } case let .internalReference(internalReference): if let image = internalReference.image { @@ -296,12 +296,12 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode if file.isVideo && file.isAnimated { videoFile = file imageResource = nil - updatedStatusSignal = item.account.postbox.mediaBox.resourceStatus(file.resource) + updatedStatusSignal = item.context.account.postbox.mediaBox.resourceStatus(file.resource) } else if let imageResource = imageResource { - updatedStatusSignal = item.account.postbox.mediaBox.resourceStatus(imageResource) + updatedStatusSignal = item.context.account.postbox.mediaBox.resourceStatus(imageResource) } } else if let imageResource = imageResource { - updatedStatusSignal = item.account.postbox.mediaBox.resourceStatus(imageResource) + updatedStatusSignal = item.context.account.postbox.mediaBox.resourceStatus(imageResource) } } @@ -351,11 +351,11 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode if updatedImageResource { if let imageResource = imageResource { if let stickerFile = stickerFile { - updateImageSignal = chatMessageSticker(account: item.account, userLocation: .other, file: stickerFile, small: false, fetched: true) + updateImageSignal = chatMessageSticker(account: item.context.account, userLocation: .other, file: stickerFile, small: false, fetched: true) } else { let tmpRepresentation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(CGSize(width: fittedImageDimensions.width * 2.0, height: fittedImageDimensions.height * 2.0)), resource: imageResource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false) let tmpImage = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [tmpRepresentation], immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: []) - updateImageSignal = chatMessagePhoto(postbox: item.account.postbox, userLocation: .other, photoReference: .standalone(media: tmpImage), synchronousLoad: true) + updateImageSignal = chatMessagePhoto(postbox: item.context.account.postbox, userLocation: .other, photoReference: .standalone(media: tmpImage), synchronousLoad: true) } } else { updateImageSignal = .complete() @@ -391,7 +391,7 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode } if let videoFile = videoFile { - let thumbnailLayer = SoftwareVideoThumbnailNode(account: item.account, fileReference: .standalone(media: videoFile), synchronousLoad: synchronousLoads) + let thumbnailLayer = SoftwareVideoThumbnailNode(account: item.context.account, fileReference: .standalone(media: videoFile), synchronousLoad: synchronousLoads) thumbnailLayer.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0) strongSelf.addSubnode(thumbnailLayer) let layerHolder = takeSampleBufferLayer() @@ -399,7 +399,7 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode layerHolder.layer.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0) strongSelf.layer.addSublayer(layerHolder.layer) - let manager = SoftwareVideoLayerFrameManager(account: item.account, userLocation: .other, userContentType: .other, fileReference: .standalone(media: videoFile), layerHolder: layerHolder) + let manager = SoftwareVideoLayerFrameManager(account: item.context.account, userLocation: .other, userContentType: .other, fileReference: .standalone(media: videoFile), layerHolder: layerHolder) strongSelf.videoLayer = (thumbnailLayer, manager, layerHolder) thumbnailLayer.ready = { [weak thumbnailLayer, weak manager] in if let strongSelf = self, let thumbnailLayer = thumbnailLayer, let manager = manager { @@ -437,8 +437,8 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode } let dimensions = animatedStickerFile.dimensions ?? PixelDimensions(width: 512, height: 512) let fittedDimensions = dimensions.cgSize.aspectFitted(CGSize(width: 160.0, height: 160.0)) - strongSelf.fetchDisposable.set(freeMediaFileResourceInteractiveFetched(account: item.account, userLocation: .other, fileReference: stickerPackFileReference(animatedStickerFile), resource: animatedStickerFile.resource).start()) - animationNode.setup(source: AnimatedStickerResourceSource(account: item.account, resource: animatedStickerFile.resource, isVideo: animatedStickerFile.isVideoSticker), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), playbackMode: .loop, mode: .cached) + strongSelf.fetchDisposable.set(freeMediaFileResourceInteractiveFetched(account: item.context.account, userLocation: .other, fileReference: stickerPackFileReference(animatedStickerFile), resource: animatedStickerFile.resource).start()) + animationNode.setup(source: AnimatedStickerResourceSource(account: item.context.account, resource: animatedStickerFile.resource, isVideo: animatedStickerFile.isVideoSticker), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), playbackMode: .loop, mode: .cached) } } diff --git a/submodules/TelegramUI/Sources/HorizontalStickersChatContextPanelNode.swift b/submodules/TelegramUI/Sources/HorizontalStickersChatContextPanelNode.swift index 952a9b0ea2..5a2baa934c 100755 --- a/submodules/TelegramUI/Sources/HorizontalStickersChatContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/HorizontalStickersChatContextPanelNode.swift @@ -243,7 +243,7 @@ final class HorizontalStickersChatContextPanelNode: ChatInputContextPanelNode { } })) ] - return (itemNode.view, itemNode.bounds, StickerPreviewPeekContent(account: strongSelf.context.account, theme: strongSelf.theme, strings: strongSelf.strings, item: .pack(item.file), menu: menuItems, openPremiumIntro: { [weak self] in + return (itemNode.view, itemNode.bounds, StickerPreviewPeekContent(context: strongSelf.context, theme: strongSelf.theme, strings: strongSelf.strings, item: .pack(item.file), menu: menuItems, openPremiumIntro: { [weak self] in guard let strongSelf = self else { return } diff --git a/submodules/TelegramUI/Sources/InlineReactionSearchPanel.swift b/submodules/TelegramUI/Sources/InlineReactionSearchPanel.swift index e999aaea28..7363d6ac1d 100644 --- a/submodules/TelegramUI/Sources/InlineReactionSearchPanel.swift +++ b/submodules/TelegramUI/Sources/InlineReactionSearchPanel.swift @@ -198,7 +198,7 @@ private final class InlineReactionSearchStickersNode: ASDisplayNode, UIScrollVie } })) ) - return (itemNode.view, itemNode.bounds, StickerPreviewPeekContent(account: strongSelf.context.account, theme: strongSelf.theme, strings: strongSelf.strings, item: .pack(item.file), menu: menuItems, openPremiumIntro: { [weak self] in + return (itemNode.view, itemNode.bounds, StickerPreviewPeekContent(context: strongSelf.context, theme: strongSelf.theme, strings: strongSelf.strings, item: .pack(item.file), menu: menuItems, openPremiumIntro: { [weak self] in guard let strongSelf = self, let controllerInteraction = strongSelf.getControllerInteraction?() else { return } diff --git a/submodules/TelegramUI/Sources/StickersChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/StickersChatInputContextPanelNode.swift index 6c2ea057b0..f54acae3f8 100644 --- a/submodules/TelegramUI/Sources/StickersChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/StickersChatInputContextPanelNode.swift @@ -199,7 +199,7 @@ final class StickersChatInputContextPanelNode: ChatInputContextPanelNode { } })) ] - return (itemNode.view, itemNode.bounds, StickerPreviewPeekContent(account: strongSelf.context.account, theme: strongSelf.theme, strings: strongSelf.strings, item: .pack(item.file), menu: menuItems, openPremiumIntro: { [weak self] in + return (itemNode.view, itemNode.bounds, StickerPreviewPeekContent(context: strongSelf.context, theme: strongSelf.theme, strings: strongSelf.strings, item: .pack(item.file), menu: menuItems, openPremiumIntro: { [weak self] in guard let strongSelf = self else { return } From 1ce798c465e068ad8eb1e72da6dc4286d73d7ace Mon Sep 17 00:00:00 2001 From: Ali <> Date: Tue, 24 Jan 2023 14:25:52 +0100 Subject: [PATCH 3/7] Optimize cache cleanup for default settings --- .../Postbox/Sources/TimeBasedCleanup.swift | 65 ++++++++++--------- 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/submodules/Postbox/Sources/TimeBasedCleanup.swift b/submodules/Postbox/Sources/TimeBasedCleanup.swift index 2d2e86fd18..0e14f9a194 100644 --- a/submodules/Postbox/Sources/TimeBasedCleanup.swift +++ b/submodules/Postbox/Sources/TimeBasedCleanup.swift @@ -325,11 +325,6 @@ private final class TimeBasedCleanupImpl { } private func resetScan(general: Int32, shortLived: Int32, gigabytesLimit: Int32) { - if "".isEmpty { - //TODO:remove debugging - return - } - let generalPaths = self.generalPaths let totalSizeBasedPath = self.totalSizeBasedPath let shortLivedPaths = self.shortLivedPaths @@ -362,27 +357,30 @@ private final class TimeBasedCleanupImpl { //#endif var totalApproximateSize: Int64 = 0 - for path in shortLivedPaths { - totalApproximateSize += statForDirectory(path: path) - } - for path in generalPaths { - totalApproximateSize += statForDirectory(path: path) - } - - if let enumerator = FileManager.default.enumerator(at: URL(fileURLWithPath: totalSizeBasedPath), includingPropertiesForKeys: [.fileSizeKey, .fileResourceIdentifierKey], options: [.skipsHiddenFiles, .skipsSubdirectoryDescendants], errorHandler: nil) { - var fileIds = Set() - loop: for url in enumerator { - guard let url = url as? URL else { - continue - } - if let fileId = (try? url.resourceValues(forKeys: Set([.fileResourceIdentifierKey])))?.fileResourceIdentifier as? Data { - if fileIds.contains(fileId) { - continue loop + if gigabytesLimit < Int32.max { + for path in shortLivedPaths { + totalApproximateSize += statForDirectory(path: path) + } + for path in generalPaths { + totalApproximateSize += statForDirectory(path: path) + } + + + if let enumerator = FileManager.default.enumerator(at: URL(fileURLWithPath: totalSizeBasedPath), includingPropertiesForKeys: [.fileSizeKey, .fileResourceIdentifierKey], options: [.skipsHiddenFiles, .skipsSubdirectoryDescendants], errorHandler: nil) { + var fileIds = Set() + loop: for url in enumerator { + guard let url = url as? URL else { + continue } - - if let value = (try? url.resourceValues(forKeys: Set([.fileSizeKey])))?.fileSize, value != 0 { - fileIds.insert(fileId) - totalApproximateSize += Int64(value) + if let fileId = (try? url.resourceValues(forKeys: Set([.fileResourceIdentifierKey])))?.fileResourceIdentifier as? Data { + if fileIds.contains(fileId) { + continue loop + } + + if let value = (try? url.resourceValues(forKeys: Set([.fileSizeKey])))?.fileSize, value != 0 { + fileIds.insert(fileId) + totalApproximateSize += Int64(value) + } } } } @@ -405,15 +403,18 @@ private final class TimeBasedCleanupImpl { var totalLimitSize: UInt64 = 0 - for path in generalPaths { - let scanResult = scanFiles(at: path, olderThan: oldestGeneralTimestamp, includeSubdirectories: true, performSizeMapping: performSizeMapping, tempDatabase: tempDatabase) - if !paths.contains(path) { - paths.append(path) + if general < Int32.max { + for path in generalPaths { + let scanResult = scanFiles(at: path, olderThan: oldestGeneralTimestamp, includeSubdirectories: true, performSizeMapping: performSizeMapping, tempDatabase: tempDatabase) + if !paths.contains(path) { + paths.append(path) + } + removedGeneralCount += scanResult.unlinkedCount + totalLimitSize += scanResult.totalSize } - removedGeneralCount += scanResult.unlinkedCount - totalLimitSize += scanResult.totalSize } - do { + + if gigabytesLimit < Int32.max { let scanResult = scanFiles(at: totalSizeBasedPath, olderThan: 0, includeSubdirectories: false, performSizeMapping: performSizeMapping, tempDatabase: tempDatabase) if !paths.contains(totalSizeBasedPath) { paths.append(totalSizeBasedPath) From e943444b48ae21834e9d3a9c75e433c006866b5d Mon Sep 17 00:00:00 2001 From: Ali <> Date: Tue, 24 Jan 2023 15:45:28 +0100 Subject: [PATCH 4/7] Fix notification service media download --- .../Sources/NotificationService.swift | 42 +++++++++- .../Sources/Account/AccountManager.swift | 1 + .../SynchronizeAutosaveItemOperations.swift | 78 +++++++++++++++++++ .../SyncCore/SyncCore_Namespaces.swift | 1 + .../Messages/TelegramEngineMessages.swift | 12 +++ .../TelegramUI/Sources/AppDelegate.swift | 18 +++++ .../Sources/StoreDownloadedMedia.swift | 25 ++++++ 7 files changed, 173 insertions(+), 4 deletions(-) create mode 100644 submodules/TelegramCore/Sources/State/SynchronizeAutosaveItemOperations.swift diff --git a/Telegram/NotificationService/Sources/NotificationService.swift b/Telegram/NotificationService/Sources/NotificationService.swift index d1529a3c8f..b56adb778f 100644 --- a/Telegram/NotificationService/Sources/NotificationService.swift +++ b/Telegram/NotificationService/Sources/NotificationService.swift @@ -460,6 +460,7 @@ private struct NotificationContent: CustomStringConvertible { string += " userInfo: \(String(describing: self.userInfo)),\n" string += " senderImage: \(self.senderImage != nil ? "non-empty" : "empty"),\n" string += " isLockedMessage: \(String(describing: self.isLockedMessage)),\n" + string += " attachments: \(self.attachments),\n" string += "}" return string } @@ -1164,7 +1165,13 @@ private final class NotificationServiceHandler { } else { let intervals: Signal<[(Range, MediaBoxFetchPriority)], NoError> = .single([(0 ..< Int64.max, MediaBoxFetchPriority.maximum)]) fetchMediaSignal = Signal { subscriber in - let collectedData = Atomic(value: Data()) + final class DataValue { + var data = Data() + var totalSize: Int64? + } + + let collectedData = Atomic(value: DataValue()) + return standaloneMultipartFetch( postbox: stateManager.postbox, network: stateManager.network, @@ -1191,11 +1198,33 @@ private final class NotificationServiceHandler { ).start(next: { result in switch result { case let .dataPart(_, data, _, _): + var isCompleted = false let _ = collectedData.modify { current in - var current = current - current.append(data) + let current = current + current.data.append(data) + if let totalSize = current.totalSize, Int64(current.data.count) >= totalSize { + isCompleted = true + } return current } + if isCompleted { + subscriber.putNext(collectedData.with({ $0.data })) + subscriber.putCompletion() + } + case let .resourceSizeUpdated(size): + var isCompleted = false + let _ = collectedData.modify { current in + let current = current + current.totalSize = size + if Int64(current.data.count) >= size { + isCompleted = true + } + return current + } + if isCompleted { + subscriber.putNext(collectedData.with({ $0.data })) + subscriber.putCompletion() + } default: break } @@ -1203,7 +1232,7 @@ private final class NotificationServiceHandler { subscriber.putNext(nil) subscriber.putCompletion() }, completed: { - subscriber.putNext(collectedData.with({ $0 })) + subscriber.putNext(collectedData.with({ $0.data })) subscriber.putCompletion() }) } @@ -1303,10 +1332,15 @@ private final class NotificationServiceHandler { } Logger.shared.log("NotificationService \(episode)", "Unread count: \(value.0), isCurrentAccount: \(isCurrentAccount)") + + Logger.shared.log("NotificationService \(episode)", "mediaAttachment: \(String(describing: mediaAttachment)), mediaData: \(String(describing: mediaData?.count))") if let image = mediaAttachment as? TelegramMediaImage, let resource = largestImageRepresentation(image.representations)?.resource { if let mediaData = mediaData { stateManager.postbox.mediaBox.storeResourceData(resource.id, data: mediaData, synchronous: true) + if let messageId { + let _ = addSynchronizeAutosaveItemOperation(postbox: stateManager.postbox, messageId: messageId, mediaId: image.imageId).start() + } } if let storedPath = stateManager.postbox.mediaBox.completedResourcePath(resource, pathExtension: "jpg") { if let attachment = try? UNNotificationAttachment(identifier: "image", url: URL(fileURLWithPath: storedPath), options: nil) { diff --git a/submodules/TelegramCore/Sources/Account/AccountManager.swift b/submodules/TelegramCore/Sources/Account/AccountManager.swift index 664df0b395..1b21568714 100644 --- a/submodules/TelegramCore/Sources/Account/AccountManager.swift +++ b/submodules/TelegramCore/Sources/Account/AccountManager.swift @@ -193,6 +193,7 @@ private var declaredEncodables: Void = { declareEncodable(TelegramPeerUsername.self, f: { TelegramPeerUsername(decoder: $0) }) declareEncodable(MediaSpoilerMessageAttribute.self, f: { MediaSpoilerMessageAttribute(decoder: $0) }) declareEncodable(TranslationMessageAttribute.self, f: { TranslationMessageAttribute(decoder: $0) }) + declareEncodable(SynchronizeAutosaveItemOperation.self, f: { SynchronizeAutosaveItemOperation(decoder: $0) }) return }() diff --git a/submodules/TelegramCore/Sources/State/SynchronizeAutosaveItemOperations.swift b/submodules/TelegramCore/Sources/State/SynchronizeAutosaveItemOperations.swift new file mode 100644 index 0000000000..3fa8ba5acf --- /dev/null +++ b/submodules/TelegramCore/Sources/State/SynchronizeAutosaveItemOperations.swift @@ -0,0 +1,78 @@ +import Foundation +import Postbox +import SwiftSignalKit + +public final class SynchronizeAutosaveItemOperation: PostboxCoding { + public struct Content: Codable { + public var messageId: MessageId + public var mediaId: MediaId + + public init(messageId: MessageId, mediaId: MediaId) { + self.messageId = messageId + self.mediaId = mediaId + } + } + + public let messageId: MessageId + public let mediaId: MediaId + + public init(messageId: MessageId, mediaId: MediaId) { + self.messageId = messageId + self.mediaId = mediaId + } + + public init(decoder: PostboxDecoder) { + if let content = decoder.decode(Content.self, forKey: "c") { + self.messageId = content.messageId + self.mediaId = content.mediaId + } else { + self.messageId = MessageId(peerId: PeerId(0), namespace: 0, id: 0) + self.mediaId = MediaId(namespace: 0, id: 0) + } + } + + public func encode(_ encoder: PostboxEncoder) { + encoder.encode(Content(messageId: self.messageId, mediaId: self.mediaId), forKey: "c") + } +} + +public func addSynchronizeAutosaveItemOperation(transaction: Transaction, messageId: MessageId, mediaId: MediaId) { + let tag: PeerOperationLogTag = OperationLogTags.SynchronizeAutosaveItems + let peerId = PeerId(0) + + transaction.operationLogAddEntry(peerId: peerId, tag: tag, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: SynchronizeAutosaveItemOperation(messageId: messageId, mediaId: mediaId)) +} + +public func addSynchronizeAutosaveItemOperation(postbox: Postbox, messageId: MessageId, mediaId: MediaId) -> Signal { + return postbox.transaction { transaction -> Void in + addSynchronizeAutosaveItemOperation(transaction: transaction, messageId: messageId, mediaId: mediaId) + } + |> ignoreValues +} + +public func _internal_getSynchronizeAutosaveItemOperations(transaction: Transaction) -> [(index: Int32, message: Message, mediaId: MediaId)] { + let peerId = PeerId(0) + var result: [(index: Int32, message: Message, mediaId: MediaId)] = [] + var removeIndices: [Int32] = [] + transaction.operationLogEnumerateEntries(peerId: peerId, tag: OperationLogTags.SynchronizeAutosaveItems, { entry in + if let operation = entry.contents as? SynchronizeAutosaveItemOperation { + if let message = transaction.getMessage(operation.messageId) { + result.append((index: entry.tagLocalIndex, message: message, mediaId: operation.mediaId)) + } else { + removeIndices.append(entry.tagLocalIndex) + } + } + return true + }) + for index in removeIndices { + let _ = transaction.operationLogRemoveEntry(peerId: PeerId(0), tag: OperationLogTags.SynchronizeAutosaveItems, tagLocalIndex: index) + } + + return result +} + +public func _internal_removeSyncrhonizeAutosaveItemOperations(transaction: Transaction, indices: [Int32]) { + for index in indices { + let _ = transaction.operationLogRemoveEntry(peerId: PeerId(0), tag: OperationLogTags.SynchronizeAutosaveItems, tagLocalIndex: index) + } +} diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift index 3e9ac4dad7..41d88def06 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift @@ -180,6 +180,7 @@ public struct OperationLogTags { public static let SynchronizeChatListFilters = PeerOperationLogTag(value: 20) public static let SynchronizeMarkAllUnseenReactions = PeerOperationLogTag(value: 21) public static let SynchronizeInstalledEmoji = PeerOperationLogTag(value: 22) + public static let SynchronizeAutosaveItems = PeerOperationLogTag(value: 23) } public struct LegacyPeerSummaryCounterTags: OptionSet, Sequence, Hashable { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift index 191b3bdce0..d77767be48 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift @@ -507,5 +507,17 @@ public extension TelegramEngine { return managedSynchronizeMessageHistoryTagSummaries(postbox: self.account.postbox, network: self.account.network, stateManager: self.account.stateManager, peerId: peerId, threadId: threadId) |> ignoreValues } + + public func getSynchronizeAutosaveItemOperations() -> Signal<[(index: Int32, message: Message, mediaId: MediaId)], NoError> { + return self.account.postbox.transaction { transaction -> [(index: Int32, message: Message, mediaId: MediaId)] in + return _internal_getSynchronizeAutosaveItemOperations(transaction: transaction) + } + } + + func removeSyncrhonizeAutosaveItemOperations(indices: [Int32]) { + let _ = (self.account.postbox.transaction { transaction -> Void in + _internal_removeSyncrhonizeAutosaveItemOperations(transaction: transaction, indices: indices) + }).start() + } } } diff --git a/submodules/TelegramUI/Sources/AppDelegate.swift b/submodules/TelegramUI/Sources/AppDelegate.swift index 700bac95a4..864706caa9 100644 --- a/submodules/TelegramUI/Sources/AppDelegate.swift +++ b/submodules/TelegramUI/Sources/AppDelegate.swift @@ -1310,6 +1310,8 @@ private func extractAccountManagerState(records: AccountRecordsView take(1) + |> deliverOnMainQueue).start(next: { sharedApplicationContext in + let _ = (sharedApplicationContext.sharedContext.activeAccountContexts + |> take(1) + |> deliverOnMainQueue).start(next: { activeAccounts in + for (_, context, _) in activeAccounts.accounts { + (context.downloadedMediaStoreManager as? DownloadedMediaStoreManagerImpl)?.runTasks() + } + }) + }) } func applicationDidBecomeActive(_ application: UIApplication) { diff --git a/submodules/TelegramUI/Sources/StoreDownloadedMedia.swift b/submodules/TelegramUI/Sources/StoreDownloadedMedia.swift index c1d674a60a..f13a3b32e6 100644 --- a/submodules/TelegramUI/Sources/StoreDownloadedMedia.swift +++ b/submodules/TelegramUI/Sources/StoreDownloadedMedia.swift @@ -266,10 +266,12 @@ private final class DownloadedMediaStoreManagerPrivateImpl { final class DownloadedMediaStoreManagerImpl: DownloadedMediaStoreManager { private let queue = Queue() + private let postbox: Postbox private let impl: QueueLocalObject init(postbox: Postbox, accountManager: AccountManager) { let queue = self.queue + self.postbox = postbox self.impl = QueueLocalObject(queue: queue, generate: { return DownloadedMediaStoreManagerPrivateImpl(queue: queue, postbox: postbox, accountManager: accountManager) }) @@ -280,4 +282,27 @@ final class DownloadedMediaStoreManagerImpl: DownloadedMediaStoreManager { impl.store(media, timestamp: timestamp, peerId: peerId) } } + + func runTasks() { + let _ = (self.postbox.transaction({ transaction -> [(index: Int32, message: Message, mediaId: MediaId)] in + return _internal_getSynchronizeAutosaveItemOperations(transaction: transaction) + }) + |> deliverOnMainQueue).start(next: { [weak self] items in + guard let self else { + return + } + for item in items { + for media in item.message.media { + if let id = media.id, id == item.mediaId { + self.store(.standalone(media: media), timestamp: item.message.timestamp, peerId: item.message.id.peerId) + break + } + } + } + + let _ = self.postbox.transaction({ transaction -> Void in + return _internal_removeSyncrhonizeAutosaveItemOperations(transaction: transaction, indices: items.map(\.index)) + }).start() + }) + } } From a7c9ee2de7f34ffbbfb71705766f46f5f2562deb Mon Sep 17 00:00:00 2001 From: Ali <> Date: Tue, 24 Jan 2023 16:39:27 +0100 Subject: [PATCH 5/7] UI improvements --- .../Sources/EmojiSuggestionsComponent.swift | 25 ++ .../ChatInterfaceStateInputPanels.swift | 2 +- .../ChatRestrictedInputPanelNode.swift | 16 +- .../Sources/ChatTextInputPanelNode.swift | 276 ++++++++++++++++++ 4 files changed, 310 insertions(+), 9 deletions(-) diff --git a/submodules/TelegramUI/Components/EmojiSuggestionsComponent/Sources/EmojiSuggestionsComponent.swift b/submodules/TelegramUI/Components/EmojiSuggestionsComponent/Sources/EmojiSuggestionsComponent.swift index a93adb17bc..4d00e0b9d7 100644 --- a/submodules/TelegramUI/Components/EmojiSuggestionsComponent/Sources/EmojiSuggestionsComponent.swift +++ b/submodules/TelegramUI/Components/EmojiSuggestionsComponent/Sources/EmojiSuggestionsComponent.swift @@ -233,6 +233,31 @@ public final class EmojiSuggestionsComponent: Component { fatalError("init(coder:) has not been implemented") } + public func item(at point: CGPoint) -> (CALayer, TelegramMediaFile)? { + let location = self.convert(point, to: self.scrollView) + if self.scrollView.bounds.contains(location) { + var closestFile: (file: TelegramMediaFile, layer: CALayer, distance: CGFloat)? + for (_, itemLayer) in self.visibleLayers { + guard let file = itemLayer.file else { + continue + } + let distance = abs(location.x - itemLayer.position.x) + if let (_, _, currentDistance) = closestFile { + if distance < currentDistance { + closestFile = (file, itemLayer, distance) + } + } else { + closestFile = (file, itemLayer, distance) + } + } + if let (file, itemLayer, _) = closestFile { + return (itemLayer, file) + } + } + + return nil + } + @objc private func tapGesture(_ recognizer: UITapGestureRecognizer) { if case .ended = recognizer.state { let location = recognizer.location(in: self.scrollView) diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift index 3902434dfb..5c3ace2355 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift @@ -196,7 +196,7 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState } } - if isMember && !channel.hasPermission(.sendSomething) && !channel.flags.contains(.isGigagroup) { + if case .group = channel.info, isMember && !channel.hasPermission(.sendSomething) && !channel.flags.contains(.isGigagroup) { if let currentPanel = (currentPanel as? ChatRestrictedInputPanelNode) ?? (currentSecondaryPanel as? ChatRestrictedInputPanelNode) { return (currentPanel, nil) } else { diff --git a/submodules/TelegramUI/Sources/ChatRestrictedInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatRestrictedInputPanelNode.swift index 2b88e19118..25cad550c1 100644 --- a/submodules/TelegramUI/Sources/ChatRestrictedInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatRestrictedInputPanelNode.swift @@ -30,17 +30,17 @@ final class ChatRestrictedInputPanelNode: ChatInputPanelNode { self.presentationInterfaceState = interfaceState } - let bannedPermission: (Int32, Bool)? + var bannedPermission: (Int32, Bool)? if let channel = interfaceState.renderedPeer?.peer as? TelegramChannel { - bannedPermission = channel.hasBannedPermission(.banSendText) - } else if let group = interfaceState.renderedPeer?.peer as? TelegramGroup { - if group.hasBannedPermission(.banSendText) { + if let value = channel.hasBannedPermission(.banSendText) { + bannedPermission = value + } else if !channel.hasPermission(.sendSomething) { + bannedPermission = (Int32.max, false) + } + } else if let group = interfaceState.renderedPeer?.peer as? TelegramGroup { + if !group.hasPermission(.sendSomething) { bannedPermission = (Int32.max, false) - } else { - bannedPermission = nil } - } else { - bannedPermission = nil } var iconImage: UIImage? diff --git a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift index 6aa729b850..ec011420a2 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift @@ -30,6 +30,9 @@ import ComponentFlow import EmojiSuggestionsComponent import AudioToolbox import ChatControllerInteraction +import UndoUI +import PremiumUI +import StickerPeekUI private let accessoryButtonFont = Font.medium(14.0) private let counterFont = Font.with(size: 14.0, design: .regular, traits: [.monospacedNumbers]) @@ -2571,6 +2574,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { viewForOverlayContent.addSubview(currentEmojiSuggestionView) currentEmojiSuggestionView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) + + self.installEmojiSuggestionPreviewGesture(hostView: currentEmojiSuggestionView) } let globalPosition = textInputNode.textView.convert(currentEmojiSuggestion.localPosition, to: self.view) @@ -2693,6 +2698,277 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { } } + private func installEmojiSuggestionPreviewGesture(hostView: UIView) { + let peekRecognizer = PeekControllerGestureRecognizer(contentAtPoint: { [weak self] point in + guard let self else { + return nil + } + return self.emojiSuggestionPeekContentAtPoint(point: point) + }, present: { [weak self] content, sourceView, sourceRect in + guard let strongSelf = self, let context = strongSelf.context else { + return nil + } + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let controller = PeekController(presentationData: presentationData, content: content, sourceView: { + return (sourceView, sourceRect) + }) + //strongSelf.peekController = controller + strongSelf.interfaceInteraction?.presentController(controller, nil) + return controller + }, updateContent: { [weak self] content in + guard let strongSelf = self else { + return + } + + let _ = strongSelf + }) + hostView.addGestureRecognizer(peekRecognizer) + } + + private func emojiSuggestionPeekContentAtPoint(point: CGPoint) -> Signal<(UIView, CGRect, PeekControllerContent)?, NoError>? { + guard let presentationInterfaceState = self.presentationInterfaceState else { + return nil + } + guard let chatPeerId = presentationInterfaceState.renderedPeer?.peer?.id else { + return nil + } + guard let context = self.context else { + return nil + } + + var maybeFile: TelegramMediaFile? + var maybeItemLayer: CALayer? + + if let currentEmojiSuggestionView = self.currentEmojiSuggestionView?.componentView as? EmojiSuggestionsComponent.View { + if let (itemLayer, file) = currentEmojiSuggestionView.item(at: point) { + maybeFile = file + maybeItemLayer = itemLayer + } + } + + guard let file = maybeFile else { + return nil + } + guard let itemLayer = maybeItemLayer else { + return nil + } + + let _ = chatPeerId + let _ = file + let _ = itemLayer + + var collectionId: ItemCollectionId? + for attribute in file.attributes { + if case let .CustomEmoji(_, _, _, packReference) = attribute { + switch packReference { + case let .id(id, _): + collectionId = ItemCollectionId(namespace: Namespaces.ItemCollection.CloudEmojiPacks, id: id) + default: + break + } + } + } + + var bubbleUpEmojiOrStickersets: [ItemCollectionId] = [] + if let collectionId { + bubbleUpEmojiOrStickersets.append(collectionId) + } + + let accountPeerId = context.account.peerId + + let _ = bubbleUpEmojiOrStickersets + let _ = context + let _ = accountPeerId + + return context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: accountPeerId)) + |> map { peer -> Bool in + var hasPremium = false + if case let .user(user) = peer, user.isPremium { + hasPremium = true + } + return hasPremium + } + |> deliverOnMainQueue + |> map { [weak self, weak itemLayer] hasPremium -> (UIView, CGRect, PeekControllerContent)? in + guard let strongSelf = self, let itemLayer = itemLayer else { + return nil + } + + let _ = strongSelf + let _ = itemLayer + + var menuItems: [ContextMenuItem] = [] + menuItems.removeAll() + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let _ = presentationData + + var isLocked = false + if !hasPremium { + isLocked = file.isPremiumEmoji + if isLocked && chatPeerId == context.account.peerId { + isLocked = false + } + } + + if let interaction = strongSelf.interfaceInteraction { + let _ = interaction + + let sendEmoji: (TelegramMediaFile) -> Void = { file in + guard let self else { + return + } + guard let controller = (self.interfaceInteraction?.chatController() as? ChatControllerImpl) else { + return + } + + var text = "." + var emojiAttribute: ChatTextInputTextCustomEmojiAttribute? + loop: for attribute in file.attributes { + switch attribute { + case let .CustomEmoji(_, _, displayText, stickerPackReference): + text = displayText + + var packId: ItemCollectionId? + if case let .id(id, _) = stickerPackReference { + packId = ItemCollectionId(namespace: Namespaces.ItemCollection.CloudEmojiPacks, id: id) + } + emojiAttribute = ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: packId, fileId: file.fileId.id, file: file) + break loop + default: + break + } + } + + if let emojiAttribute { + controller.controllerInteraction?.sendEmoji(text, emojiAttribute, true) + } + } + let setStatus: (TelegramMediaFile) -> Void = { file in + guard let self, let context = self.context else { + return + } + guard let controller = (self.interfaceInteraction?.chatController() as? ChatControllerImpl) else { + return + } + + let _ = context.engine.accountData.setEmojiStatus(file: file, expirationDate: nil).start() + + var animateInAsReplacement = false + animateInAsReplacement = false + /*if let currentUndoOverlayController = strongSelf.currentUndoOverlayController { + currentUndoOverlayController.dismissWithCommitActionAndReplacementAnimation() + strongSelf.currentUndoOverlayController = nil + animateInAsReplacement = true + }*/ + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + + //TODO:localize + let undoController = UndoOverlayController(presentationData: presentationData, content: .sticker(context: context, file: file, title: nil, text: "Your emoji status has been updated.", undoText: nil, customAction: nil), elevatedLayout: false, animateInAsReplacement: animateInAsReplacement, action: { _ in return false }) + //strongSelf.currentUndoOverlayController = controller + controller.controllerInteraction?.presentController(undoController, nil) + } + let copyEmoji: (TelegramMediaFile) -> Void = { file in + var text = "." + var emojiAttribute: ChatTextInputTextCustomEmojiAttribute? + loop: for attribute in file.attributes { + switch attribute { + case let .CustomEmoji(_, _, displayText, _): + text = displayText + + emojiAttribute = ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: file.fileId.id, file: file) + break loop + default: + break + } + } + + if let _ = emojiAttribute { + storeMessageTextInPasteboard(text, entities: [MessageTextEntity(range: 0 ..< (text as NSString).length, type: .CustomEmoji(stickerPack: nil, fileId: file.fileId.id))]) + } + } + + //TODO:localize + menuItems.append(.action(ContextMenuActionItem(text: "Send Emoji", icon: { theme in + if let image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Download"), color: theme.actionSheet.primaryTextColor) { + return generateImage(image.size, rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + + if let cgImage = image.cgImage { + context.draw(cgImage, in: CGRect(origin: CGPoint(), size: size)) + } + }) + } else { + return nil + } + }, action: { _, f in + sendEmoji(file) + f(.default) + }))) + + //TODO:localize + menuItems.append(.action(ContextMenuActionItem(text: "Set as Status", icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Smile"), color: theme.actionSheet.primaryTextColor) + }, action: { _, f in + f(.default) + + guard let strongSelf = self else { + return + } + + if hasPremium { + setStatus(file) + } else { + var replaceImpl: ((ViewController) -> Void)? + let controller = PremiumDemoScreen(context: context, subject: .animatedEmoji, action: { + let controller = PremiumIntroScreen(context: context, source: .animatedEmoji) + replaceImpl?(controller) + }) + replaceImpl = { [weak controller] c in + controller?.replace(with: c) + } + strongSelf.interfaceInteraction?.getNavigationController()?.pushViewController(controller) + } + }))) + + //TODO:localize + menuItems.append(.action(ContextMenuActionItem(text: "Copy Emoji", icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.actionSheet.primaryTextColor) + }, action: { _, f in + copyEmoji(file) + f(.default) + }))) + } + + if menuItems.isEmpty { + return nil + } + + let content = StickerPreviewPeekContent(context: context, theme: presentationData.theme, strings: presentationData.strings, item: .pack(file), isLocked: isLocked, menu: menuItems, openPremiumIntro: { [weak self] in + guard let self else { + return + } + guard let interfaceInteraction = self.interfaceInteraction else { + return + } + + let _ = self + let _ = interfaceInteraction + + let controller = PremiumIntroScreen(context: context, source: .stickers) + //let _ = controller + + interfaceInteraction.getNavigationController()?.pushViewController(controller) + }) + let _ = content + //return nil + + return (strongSelf.view, itemLayer.convert(itemLayer.bounds, to: strongSelf.view.layer), content) + } + } + private func updateTextNodeText(animated: Bool) { var inputHasText = false var hideMicButton = false From 922cd9401fc5b75275c92ed6436923883b9d9418 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Tue, 24 Jan 2023 17:04:55 +0100 Subject: [PATCH 6/7] Add emoji category animations --- .../Sources/EmojiPagerContentComponent.swift | 80 ++++++++++++++++++- 1 file changed, 76 insertions(+), 4 deletions(-) diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift index e9e5926646..bcaf33e780 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift @@ -6633,22 +6633,94 @@ public final class EmojiPagerContentComponent: Component { } if animateContentCrossfade { - /*for (_, itemLayer) in self.visibleItemLayers { + for (_, itemLayer) in self.visibleItemLayers { if let snapshotLayer = itemLayer.snapshotContentTree() { itemLayer.superlayer?.insertSublayer(snapshotLayer, above: itemLayer) snapshotLayer.animateAlpha(from: CGFloat(snapshotLayer.opacity), to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotLayer] _ in snapshotLayer?.removeFromSuperlayer() }) + snapshotLayer.animateScale(from: 1.0, to: 0.0001, duration: 0.2, removeOnCompletion: false) } - }*/ + } + for (_, placeholderView) in self.visibleItemPlaceholderViews { + if let snapshotLayer = placeholderView.layer.snapshotContentTree() { + placeholderView.layer.superlayer?.insertSublayer(snapshotLayer, above: placeholderView.layer) + snapshotLayer.animateAlpha(from: CGFloat(snapshotLayer.opacity), to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotLayer] _ in + snapshotLayer?.removeFromSuperlayer() + }) + snapshotLayer.animateScale(from: 1.0, to: 0.0001, duration: 0.2, removeOnCompletion: false) + } + } + for (_, selectionLayer) in self.visibleItemSelectionLayers { + if let snapshotLayer = selectionLayer.snapshotContentTree() { + selectionLayer.superlayer?.insertSublayer(snapshotLayer, above: selectionLayer) + snapshotLayer.animateAlpha(from: CGFloat(snapshotLayer.opacity), to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotLayer] _ in + snapshotLayer?.removeFromSuperlayer() + }) + } + } + for (_, groupHeader) in self.visibleGroupHeaders { + if let snapshotLayer = groupHeader.layer.snapshotContentTree() { + groupHeader.layer.superlayer?.insertSublayer(snapshotLayer, above: groupHeader.layer) + snapshotLayer.animateAlpha(from: CGFloat(snapshotLayer.opacity), to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotLayer] _ in + snapshotLayer?.removeFromSuperlayer() + }) + } + } + for (_, borderLayer) in self.visibleGroupBorders { + if let snapshotLayer = borderLayer.snapshotContentTree() { + borderLayer.superlayer?.insertSublayer(snapshotLayer, above: borderLayer) + snapshotLayer.animateAlpha(from: CGFloat(snapshotLayer.opacity), to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotLayer] _ in + snapshotLayer?.removeFromSuperlayer() + }) + } + } + for (_, button) in self.visibleGroupPremiumButtons { + if let buttonView = button.view, let snapshotLayer = buttonView.layer.snapshotContentTree() { + buttonView.layer.superlayer?.insertSublayer(snapshotLayer, above: buttonView.layer) + snapshotLayer.animateAlpha(from: CGFloat(snapshotLayer.opacity), to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotLayer] _ in + snapshotLayer?.removeFromSuperlayer() + }) + } + } + for (_, button) in self.visibleGroupExpandActionButtons { + if let snapshotLayer = button.layer.snapshotContentTree() { + button.layer.superlayer?.insertSublayer(snapshotLayer, above: button.layer) + snapshotLayer.animateAlpha(from: CGFloat(snapshotLayer.opacity), to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotLayer] _ in + snapshotLayer?.removeFromSuperlayer() + }) + } + } } self.updateVisibleItems(transition: itemTransition, attemptSynchronousLoads: attemptSynchronousLoads, previousItemPositions: previousItemPositions, previousAbsoluteItemPositions: previousAbsoluteItemPositions, updatedItemPositions: updatedItemPositions, hintDisappearingGroupFrame: hintDisappearingGroupFrame) if animateContentCrossfade { - /*for (_, itemLayer) in self.visibleItemLayers { + for (_, itemLayer) in self.visibleItemLayers { itemLayer.animateAlpha(from: 0.0, to: CGFloat(itemLayer.opacity), duration: 0.2) - }*/ + itemLayer.animateScale(from: 0.0001, to: 1.0, duration: 0.2) + } + for (_, placeholderView) in self.visibleItemPlaceholderViews { + placeholderView.layer.animateAlpha(from: 0.0, to: CGFloat(placeholderView.layer.opacity), duration: 0.2) + placeholderView.layer.animateScale(from: 0.0001, to: 1.0, duration: 0.2) + } + for (_, selectionLayer) in self.visibleItemSelectionLayers { + selectionLayer.animateAlpha(from: 0.0, to: CGFloat(selectionLayer.opacity), duration: 0.2) + } + for (_, groupHeader) in self.visibleGroupHeaders { + groupHeader.layer.animateAlpha(from: 0.0, to: CGFloat(groupHeader.layer.opacity), duration: 0.2) + } + for (_, borderLayer) in self.visibleGroupBorders { + borderLayer.animateAlpha(from: 0.0, to: CGFloat(borderLayer.opacity), duration: 0.2) + } + for (_, button) in self.visibleGroupPremiumButtons { + if let buttonView = button.view { + buttonView.layer.animateAlpha(from: 0.0, to: CGFloat(buttonView.layer.opacity), duration: 0.2) + } + } + for (_, button) in self.visibleGroupExpandActionButtons { + button.layer.animateAlpha(from: 0.0, to: CGFloat(button.layer.opacity), duration: 0.2) + } } return availableSize From af2748f83957c153632bef58f6e719e4d958b58f Mon Sep 17 00:00:00 2001 From: Ali <> Date: Tue, 24 Jan 2023 17:27:24 +0100 Subject: [PATCH 7/7] Adjust animation --- .../Sources/EmojiPagerContentComponent.swift | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift index bcaf33e780..c36ce620ba 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift @@ -6632,6 +6632,8 @@ public final class EmojiPagerContentComponent: Component { animateContentCrossfade = true } + let crossfadeMinScale: CGFloat = 0.4 + if animateContentCrossfade { for (_, itemLayer) in self.visibleItemLayers { if let snapshotLayer = itemLayer.snapshotContentTree() { @@ -6639,7 +6641,7 @@ public final class EmojiPagerContentComponent: Component { snapshotLayer.animateAlpha(from: CGFloat(snapshotLayer.opacity), to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotLayer] _ in snapshotLayer?.removeFromSuperlayer() }) - snapshotLayer.animateScale(from: 1.0, to: 0.0001, duration: 0.2, removeOnCompletion: false) + snapshotLayer.animateScale(from: 1.0, to: crossfadeMinScale, duration: 0.2, removeOnCompletion: false) } } for (_, placeholderView) in self.visibleItemPlaceholderViews { @@ -6648,7 +6650,7 @@ public final class EmojiPagerContentComponent: Component { snapshotLayer.animateAlpha(from: CGFloat(snapshotLayer.opacity), to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotLayer] _ in snapshotLayer?.removeFromSuperlayer() }) - snapshotLayer.animateScale(from: 1.0, to: 0.0001, duration: 0.2, removeOnCompletion: false) + snapshotLayer.animateScale(from: 1.0, to: crossfadeMinScale, duration: 0.2, removeOnCompletion: false) } } for (_, selectionLayer) in self.visibleItemSelectionLayers { @@ -6698,11 +6700,11 @@ public final class EmojiPagerContentComponent: Component { if animateContentCrossfade { for (_, itemLayer) in self.visibleItemLayers { itemLayer.animateAlpha(from: 0.0, to: CGFloat(itemLayer.opacity), duration: 0.2) - itemLayer.animateScale(from: 0.0001, to: 1.0, duration: 0.2) + itemLayer.animateScale(from: crossfadeMinScale, to: 1.0, duration: 0.2) } for (_, placeholderView) in self.visibleItemPlaceholderViews { placeholderView.layer.animateAlpha(from: 0.0, to: CGFloat(placeholderView.layer.opacity), duration: 0.2) - placeholderView.layer.animateScale(from: 0.0001, to: 1.0, duration: 0.2) + placeholderView.layer.animateScale(from: crossfadeMinScale, to: 1.0, duration: 0.2) } for (_, selectionLayer) in self.visibleItemSelectionLayers { selectionLayer.animateAlpha(from: 0.0, to: CGFloat(selectionLayer.opacity), duration: 0.2)