From 22e8f53e0a362a9f135e4dd8babc94ef7663af03 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Mon, 25 Jul 2022 15:32:04 +0200 Subject: [PATCH 1/8] Fix overlay touch events --- .../Sources/ChatControllerNode.swift | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index 09fd9c55ea..c4e5a59b99 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -793,8 +793,21 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { self.contentContainerNode.frame = CGRect(origin: CGPoint(), size: layout.size) - let visibleRootModalDismissProgress: CGFloat = 1.0 - self.inputPanelContainerNode.expansionFraction - if self.inputPanelContainerNode.expansionFraction != 0.0 { + let isOverlay: Bool + switch self.chatPresentationInterfaceState.mode { + case .overlay: + isOverlay = true + default: + isOverlay = false + } + + let visibleRootModalDismissProgress: CGFloat + if isOverlay { + visibleRootModalDismissProgress = 1.0 + } else { + visibleRootModalDismissProgress = 1.0 - self.inputPanelContainerNode.expansionFraction + } + if !isOverlay && self.inputPanelContainerNode.expansionFraction != 0.0 { let navigationModalFrame: NavigationModalFrame var animateFromFraction: CGFloat? if let current = self.navigationModalFrame { @@ -886,6 +899,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { containerNode.cornerRadius = 15.0 containerNode.addSubnode(self.backgroundNode) containerNode.addSubnode(self.historyNodeContainer) + self.contentContainerNode.isHidden = true if let restrictedNode = self.restrictedNode { containerNode.addSubnode(restrictedNode) } From c7de651aad9f9bd76a3da1c803803de25701ecb6 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Mon, 25 Jul 2022 15:41:10 +0200 Subject: [PATCH 2/8] Restrict forwards --- .../Sources/ChatListSearchContainerNode.swift | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift index 000eeaa071..1c68ea26ba 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift @@ -951,13 +951,15 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo }))) } - items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Conversation_ContextMenuForward, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in - c.dismiss(completion: { [weak self] in - if let strongSelf = self { - strongSelf.forwardMessages(messageIds: Set([message.id])) - } - }) - }))) + if !message._asMessage().isCopyProtected() { + items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Conversation_ContextMenuForward, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in + c.dismiss(completion: { [weak self] in + if let strongSelf = self { + strongSelf.forwardMessages(messageIds: Set([message.id])) + } + }) + }))) + } items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.SharedMedia_ViewInChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in c.dismiss(completion: { [weak self] in self?.openMessage(EnginePeer(message.peers[message.id.peerId]!), message.id, false) From 51c0888313a1631ea5989c7ff0bf9018138eebb9 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Mon, 25 Jul 2022 15:58:33 +0200 Subject: [PATCH 3/8] Fix emoji data updates in embedded mode --- .../TelegramUI/Sources/ChatController.swift | 4 ++- .../Sources/ChatEntityKeyboardInputNode.swift | 29 +++++++++++++++++-- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 50afc6da07..89e1a20048 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -10963,7 +10963,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } }) - let inputPanelNode = AttachmentTextInputPanelNode(context: self.context, presentationInterfaceState: presentationInterfaceState, isCaption: true, presentController: { _ in }, makeEntityInputView: { [weak self] in + let inputPanelNode = AttachmentTextInputPanelNode(context: self.context, presentationInterfaceState: presentationInterfaceState, isCaption: true, presentController: { [weak self] c in + self?.presentInGlobalOverlay(c) + }, makeEntityInputView: { [weak self] in guard let strongSelf = self else { return nil } diff --git a/submodules/TelegramUI/Sources/ChatEntityKeyboardInputNode.swift b/submodules/TelegramUI/Sources/ChatEntityKeyboardInputNode.swift index 9f068eee88..5f23cd5db9 100644 --- a/submodules/TelegramUI/Sources/ChatEntityKeyboardInputNode.swift +++ b/submodules/TelegramUI/Sources/ChatEntityKeyboardInputNode.swift @@ -2030,7 +2030,25 @@ final class EntityInputView: UIView, AttachmentTextInputPanelInputView, UIInputV }, addGroupAction: { _, _ in }, - clearGroup: { _ in + clearGroup: { [weak self] groupId in + guard let strongSelf = self else { + return + } + if groupId == AnyHashable("recent") { + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let actionSheet = ActionSheetController(theme: ActionSheetControllerTheme(presentationTheme: presentationData.theme, fontSize: presentationData.listsFontSize)) + var items: [ActionSheetItem] = [] + items.append(ActionSheetButtonItem(title: presentationData.strings.Emoji_ClearRecent, color: .destructive, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + let _ = context.engine.stickers.clearRecentlyUsedEmoji().start() + })) + actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [ + ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + }) + ])]) + strongSelf.presentController?(actionSheet) + } }, pushController: { _ in }, @@ -2062,7 +2080,14 @@ final class EntityInputView: UIView, AttachmentTextInputPanelInputView, UIInputV gifs: nil, availableGifSearchEmojies: [] ), - updatedInputData: .never(), + updatedInputData: ChatEntityKeyboardInputNode.emojiInputData(context: context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, isStandalone: true, isSecret: isSecret) |> map { emojiComponent -> ChatEntityKeyboardInputNode.InputData in + return ChatEntityKeyboardInputNode.InputData( + emoji: emojiComponent, + stickers: nil, + gifs: nil, + availableGifSearchEmojies: [] + ) + }, defaultToEmojiTab: true, controllerInteraction: nil, interfaceInteraction: nil, From 83e3b3bd6c49dbf4e19d8e92d56186da38d6cc70 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Mon, 25 Jul 2022 16:08:22 +0200 Subject: [PATCH 4/8] Use timer for animation --- .../Sources/MultiAnimationRenderer.swift | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/submodules/TelegramUI/Components/MultiAnimationRenderer/Sources/MultiAnimationRenderer.swift b/submodules/TelegramUI/Components/MultiAnimationRenderer/Sources/MultiAnimationRenderer.swift index 85d5dc1f55..831741dce2 100644 --- a/submodules/TelegramUI/Components/MultiAnimationRenderer/Sources/MultiAnimationRenderer.swift +++ b/submodules/TelegramUI/Components/MultiAnimationRenderer/Sources/MultiAnimationRenderer.swift @@ -553,11 +553,12 @@ public final class MultiAnimationRendererImpl: MultiAnimationRenderer { self.f() } } - let displayTimer = Foundation.Timer(timeInterval: CGFloat(self.frameSkip) / 60.0, target: TimerTarget { [weak self] in + let frameInterval = Double(self.frameSkip) / 60.0 + let displayTimer = Foundation.Timer(timeInterval: frameInterval, target: TimerTarget { [weak self] in guard let strongSelf = self else { return } - strongSelf.animationTick() + strongSelf.animationTick(frameInterval: frameInterval) }, selector: #selector(TimerTarget.timerEvent), userInfo: nil, repeats: true) self.displayTimer = displayTimer RunLoop.main.add(displayTimer, forMode: .common) @@ -646,8 +647,8 @@ public final class MultiAnimationRendererImpl: MultiAnimationRenderer { self.isPlaying = isPlaying } - private func animationTick() { - let secondsPerFrame = Double(self.frameSkip) / 60.0 + private func animationTick(frameInterval: Double) { + let secondsPerFrame = frameInterval var tasks: [LoadFrameGroupTask] = [] if let groupContext = self.groupContext { From 7e5c726f31e22e7193c29400f955036be07f92ba Mon Sep 17 00:00:00 2001 From: Ali <> Date: Mon, 25 Jul 2022 20:37:22 +0200 Subject: [PATCH 5/8] Emoji improvements --- .../Telegram-iOS/en.lproj/Localizable.strings | 3 + .../InstalledStickerPacksController.swift | 11 +- .../Sources/EmojiPagerContentComponent.swift | 325 ++++++++++++++---- 3 files changed, 272 insertions(+), 67 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index f8a4ca49a6..d72f2fa112 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -7936,3 +7936,6 @@ Sorry for the inconvenience."; "EmojiInput.PremiumEmojiToast.Text" = "Subscribe to Telegram Premium to unlock premium emoji."; "EmojiInput.PremiumEmojiToast.Action" = "More"; + +"StickerPacks.DeleteEmojiPacksConfirmation_1" = "Delete 1 Emoji Pack"; +"StickerPacks.DeleteEmojiPacksConfirmation_any" = "Delete %@ Emoji Packs"; diff --git a/submodules/SettingsUI/Sources/Stickers/InstalledStickerPacksController.swift b/submodules/SettingsUI/Sources/Stickers/InstalledStickerPacksController.swift index 73d10a8f52..f8655498f0 100644 --- a/submodules/SettingsUI/Sources/Stickers/InstalledStickerPacksController.swift +++ b/submodules/SettingsUI/Sources/Stickers/InstalledStickerPacksController.swift @@ -1005,7 +1005,16 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta toolbarItem = StickersToolbarItem(selectedCount: selectedCount, actions: [.init(title: presentationData.strings.StickerPacks_ActionDelete, isEnabled: selectedCount > 0, action: { let actionSheet = ActionSheetController(presentationData: presentationData) var items: [ActionSheetItem] = [] - items.append(ActionSheetButtonItem(title: presentationData.strings.StickerPacks_DeleteStickerPacksConfirmation(selectedCount), color: .destructive, action: { [weak actionSheet] in + + let title: String + switch mode { + case .emoji: + title = presentationData.strings.StickerPacks_DeleteEmojiPacksConfirmation(selectedCount) + default: + title = presentationData.strings.StickerPacks_DeleteStickerPacksConfirmation(selectedCount) + } + + items.append(ActionSheetButtonItem(title: title, color: .destructive, action: { [weak actionSheet] in actionSheet?.dismissAnimated() if case .modal = mode { diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift index 4cc3c0d4fa..a6a4aede8f 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift @@ -2123,6 +2123,13 @@ public final class EmojiPagerContentComponent: Component { } } + private enum VisualItemKey: Hashable { + case item(id: ItemLayer.Key) + case header(groupId: AnyHashable) + case groupExpandButton(groupId: AnyHashable) + case groupActionButton(groupId: AnyHashable) + } + private let shimmerHostView: PortalSourceView? private let standaloneShimmerEffect: StandaloneShimmerEffect? @@ -3012,7 +3019,7 @@ public final class EmojiPagerContentComponent: Component { self.updateScrollingOffset(isReset: false, transition: transition) } - private func updateVisibleItems(transition: Transition, attemptSynchronousLoads: Bool, previousItemPositions: [ItemLayer.Key: CGPoint]?, updatedItemPositions: [ItemLayer.Key: CGPoint]?) { + private func updateVisibleItems(transition: Transition, attemptSynchronousLoads: Bool, previousItemPositions: [VisualItemKey: CGPoint]?, previousAbsoluteItemPositions: [VisualItemKey: CGPoint]? = nil, updatedItemPositions: [VisualItemKey: CGPoint]?, hintDisappearingGroupFrame: (groupId: AnyHashable, frame: CGRect)? = nil) { guard let component = self.component, let pagerEnvironment = self.pagerEnvironment, let keyboardChildEnvironment = self.keyboardChildEnvironment, let itemLayout = self.itemLayout else { return } @@ -3190,10 +3197,12 @@ public final class EmojiPagerContentComponent: Component { let groupPremiumButton: ComponentView var groupPremiumButtonTransition = transition + var animateButtonIn = false if let current = self.visibleGroupPremiumButtons[itemGroup.groupId] { groupPremiumButton = current } else { groupPremiumButtonTransition = .immediate + animateButtonIn = !transition.animation.isImmediate groupPremiumButton = ComponentView() self.visibleGroupPremiumButtons[itemGroup.groupId] = groupPremiumButton } @@ -3257,13 +3266,19 @@ public final class EmojiPagerContentComponent: Component { ) let groupPremiumButtonFrame = CGRect(origin: CGPoint(x: itemLayout.itemInsets.left, y: itemGroupLayout.frame.maxY - groupPremiumButtonSize.height + 1.0), size: groupPremiumButtonSize) if let view = groupPremiumButton.view { - var animateIn = false if view.superview == nil { - animateIn = true self.scrollView.addSubview(view) } + + if animateButtonIn, !transition.animation.isImmediate { + if let previousItemPosition = previousItemPositions?[.groupActionButton(groupId: itemGroup.groupId)], transitionHintInstalledGroupId != itemGroup.groupId, transitionHintExpandedGroupId != itemGroup.groupId { + groupPremiumButtonTransition = transition + view.center = previousItemPosition + } + } + groupPremiumButtonTransition.setFrame(view: view, frame: groupPremiumButtonFrame) - if animateIn, !transition.animation.isImmediate { + if animateButtonIn, !transition.animation.isImmediate { view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) transition.animateScale(view: view, from: 0.01, to: 1.0) } @@ -3275,12 +3290,14 @@ public final class EmojiPagerContentComponent: Component { validGroupExpandActionButtons.insert(itemGroup.groupId) let groupId = itemGroup.groupId + var animateButtonIn = false var groupExpandActionButtonTransition = transition let groupExpandActionButton: GroupExpandActionButton if let current = self.visibleGroupExpandActionButtons[itemGroup.groupId] { groupExpandActionButton = current } else { groupExpandActionButtonTransition = .immediate + animateButtonIn = !transition.animation.isImmediate groupExpandActionButton = GroupExpandActionButton(pressed: { [weak self] in guard let strongSelf = self else { return @@ -3292,6 +3309,13 @@ public final class EmojiPagerContentComponent: Component { self.mirrorContentScrollView.layer.addSublayer(groupExpandActionButton.tintContainerLayer) } + if animateButtonIn, !transition.animation.isImmediate { + if let previousItemPosition = previousItemPositions?[.groupExpandButton(groupId: itemGroup.groupId)], transitionHintInstalledGroupId != itemGroup.groupId, transitionHintExpandedGroupId != itemGroup.groupId { + groupExpandActionButtonTransition = transition + groupExpandActionButton.center = previousItemPosition + } + } + let baseItemFrame = itemLayout.frame(groupIndex: groupItems.groupIndex, itemIndex: collapsedItemIndex) let buttonSize = groupExpandActionButton.update(theme: keyboardChildEnvironment.theme, title: collapsedItemText) let buttonFrame = CGRect(origin: CGPoint(x: baseItemFrame.minX + floor((baseItemFrame.width - buttonSize.width) / 2.0), y: baseItemFrame.minY + floor((baseItemFrame.height - buttonSize.height) / 2.0)), size: buttonSize) @@ -3404,11 +3428,11 @@ public final class EmojiPagerContentComponent: Component { itemTransition.setBounds(layer: itemLayer, bounds: CGRect(origin: CGPoint(), size: itemFrame.size)) if animateItemIn, !transition.animation.isImmediate { - if let previousItemPosition = previousItemPositions?[itemId], transitionHintInstalledGroupId != itemId.groupId, transitionHintExpandedGroupId != itemId.groupId { + if let previousItemPosition = previousItemPositions?[.item(id: itemId)], transitionHintInstalledGroupId != itemId.groupId, transitionHintExpandedGroupId != itemId.groupId { itemTransition = transition itemLayer.position = previousItemPosition } else { - if let contentAnimation = contentAnimation, case .groupExpanded(id: itemGroup.groupId) = contentAnimation.type { + if transitionHintInstalledGroupId == itemId.groupId || transitionHintExpandedGroupId == itemId.groupId { itemLayer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4) itemLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) } else { @@ -3454,7 +3478,18 @@ public final class EmojiPagerContentComponent: Component { removedIds.append(id) if !transition.animation.isImmediate { - if let position = updatedItemPositions?[id], transitionHintInstalledGroupId != id.groupId { + if let hintDisappearingGroupFrame = hintDisappearingGroupFrame, hintDisappearingGroupFrame.groupId == id.groupId { + if let previousAbsolutePosition = previousAbsoluteItemPositions?[.item(id: id)] { + itemLayer.position = self.convert(previousAbsolutePosition, to: self.scrollView) + transition.setPosition(layer: itemLayer, position: CGPoint(x: hintDisappearingGroupFrame.frame.midX, y: hintDisappearingGroupFrame.frame.minY + 20.0)) + } + + itemLayer.opacity = 0.0 + itemLayer.animateScale(from: 1.0, to: 0.01, duration: 0.16) + itemLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.16, completion: { [weak itemLayer] _ in + itemLayer?.removeFromSuperlayer() + }) + } else if let position = updatedItemPositions?[.item(id: id)], transitionHintInstalledGroupId != id.groupId { transition.setPosition(layer: itemLayer, position: position, completion: { [weak itemLayer] _ in itemLayer?.removeFromSuperlayer() }) @@ -3485,13 +3520,28 @@ public final class EmojiPagerContentComponent: Component { removedGroupHeaderIds.append(id) if !transition.animation.isImmediate { - groupHeaderLayer.alpha = 0.0 - groupHeaderLayer.layer.animateScale(from: 1.0, to: 0.5, duration: 0.2) + var isAnimatingDisappearance = false + if let hintDisappearingGroupFrame = hintDisappearingGroupFrame, hintDisappearingGroupFrame.groupId == id, let previousAbsolutePosition = previousAbsoluteItemPositions?[VisualItemKey.header(groupId: id)] { + groupHeaderLayer.center = self.convert(previousAbsolutePosition, to: self.scrollView) + transition.setPosition(layer: groupHeaderLayer.layer, position: CGPoint(x: hintDisappearingGroupFrame.frame.midX, y: hintDisappearingGroupFrame.frame.minY + 20.0)) + isAnimatingDisappearance = true + } + let tintContentLayer = groupHeaderLayer.tintContentLayer - groupHeaderLayer.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak groupHeaderLayer, weak tintContentLayer] _ in - groupHeaderLayer?.removeFromSuperview() - tintContentLayer?.removeFromSuperlayer() - }) + + if !isAnimatingDisappearance, let position = updatedItemPositions?[.header(groupId: id)] { + transition.setPosition(layer: groupHeaderLayer.layer, position: position, completion: { [weak groupHeaderLayer, weak tintContentLayer] _ in + groupHeaderLayer?.removeFromSuperview() + tintContentLayer?.removeFromSuperlayer() + }) + } else { + groupHeaderLayer.alpha = 0.0 + groupHeaderLayer.layer.animateScale(from: 1.0, to: 0.5, duration: 0.16) + groupHeaderLayer.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.16, completion: { [weak groupHeaderLayer, weak tintContentLayer] _ in + groupHeaderLayer?.removeFromSuperview() + tintContentLayer?.removeFromSuperlayer() + }) + } } else { groupHeaderLayer.removeFromSuperview() groupHeaderLayer.tintContentLayer.removeFromSuperlayer() @@ -3516,9 +3566,35 @@ public final class EmojiPagerContentComponent: Component { var removedGroupPremiumButtonIds: [AnyHashable] = [] for (id, groupPremiumButton) in self.visibleGroupPremiumButtons { - if !validGroupPremiumButtonIds.contains(id) { - removedGroupPremiumButtonIds.append(id) - groupPremiumButton.view?.removeFromSuperview() + if !validGroupPremiumButtonIds.contains(id), let buttonView = groupPremiumButton.view { + if !transition.animation.isImmediate { + var isAnimatingDisappearance = false + if let position = updatedItemPositions?[.groupActionButton(groupId: id)], position.y > buttonView.center.y { + } else if let hintDisappearingGroupFrame = hintDisappearingGroupFrame, hintDisappearingGroupFrame.groupId == id, let previousAbsolutePosition = previousAbsoluteItemPositions?[VisualItemKey.groupActionButton(groupId: id)] { + buttonView.center = self.convert(previousAbsolutePosition, to: self.scrollView) + transition.setPosition(layer: buttonView.layer, position: CGPoint(x: hintDisappearingGroupFrame.frame.midX, y: hintDisappearingGroupFrame.frame.minY + 20.0)) + isAnimatingDisappearance = true + } + + if !isAnimatingDisappearance, let position = updatedItemPositions?[.groupActionButton(groupId: id)] { + buttonView.alpha = 0.0 + buttonView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.16, completion: { [weak buttonView] _ in + buttonView?.removeFromSuperview() + }) + transition.setPosition(layer: buttonView.layer, position: position) + } else { + buttonView.alpha = 0.0 + if transitionHintExpandedGroupId == id || hintDisappearingGroupFrame?.groupId == id { + buttonView.layer.animateScale(from: 1.0, to: 0.5, duration: 0.16) + } + buttonView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.16, completion: { [weak buttonView] _ in + buttonView?.removeFromSuperview() + }) + } + } else { + removedGroupPremiumButtonIds.append(id) + buttonView.removeFromSuperview() + } } } for id in removedGroupPremiumButtonIds { @@ -3530,14 +3606,32 @@ public final class EmojiPagerContentComponent: Component { if !validGroupExpandActionButtons.contains(id) { removedGroupExpandActionButtonIds.append(id) - if !transition.animation.isImmediate && transitionHintExpandedGroupId == id { - button.alpha = 0.0 - button.layer.animateScale(from: 1.0, to: 0.5, duration: 0.2) + if !transition.animation.isImmediate { + var isAnimatingDisappearance = false + if self.visibleGroupHeaders[id] == nil, let hintDisappearingGroupFrame = hintDisappearingGroupFrame, hintDisappearingGroupFrame.groupId == id, let previousAbsolutePosition = previousAbsoluteItemPositions?[.groupExpandButton(groupId: id)] { + button.center = self.convert(previousAbsolutePosition, to: self.scrollView) + button.tintContainerLayer.position = button.center + transition.setPosition(layer: button.layer, position: CGPoint(x: hintDisappearingGroupFrame.frame.midX, y: hintDisappearingGroupFrame.frame.minY + 20.0)) + isAnimatingDisappearance = true + } + let tintContainerLayer = button.tintContainerLayer - button.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak button, weak tintContainerLayer] _ in - button?.removeFromSuperview() - tintContainerLayer?.removeFromSuperlayer() - }) + + if !isAnimatingDisappearance, let position = updatedItemPositions?[.groupExpandButton(groupId: id)] { + transition.setPosition(layer: button.layer, position: position, completion: { [weak button, weak tintContainerLayer] _ in + button?.removeFromSuperview() + tintContainerLayer?.removeFromSuperlayer() + }) + } else { + button.alpha = 0.0 + if transitionHintExpandedGroupId == id || hintDisappearingGroupFrame?.groupId == id { + button.layer.animateScale(from: 1.0, to: 0.5, duration: 0.16) + } + button.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.16, completion: { [weak button, weak tintContainerLayer] _ in + button?.removeFromSuperview() + tintContainerLayer?.removeFromSuperlayer() + }) + } } else { button.removeFromSuperview() button.tintContainerLayer.removeFromSuperlayer() @@ -3627,15 +3721,34 @@ public final class EmojiPagerContentComponent: Component { standaloneShimmerEffect.update(background: shimmerBackgroundColor, foreground: shimmerForegroundColor) } - var previousItemPositions: [ItemLayer.Key: CGPoint]? + var previousItemPositions: [VisualItemKey: CGPoint]? var calculateUpdatedItemPositions = false - var updatedItemPositions: [ItemLayer.Key: CGPoint]? + var updatedItemPositions: [VisualItemKey: CGPoint]? - var anchorItem: (key: ItemLayer.Key, frame: CGRect)? + let contentAnimation = transition.userData(ContentAnimation.self) + + var transitionHintInstalledGroupId: AnyHashable? + var transitionHintExpandedGroupId: AnyHashable? + if let contentAnimation = contentAnimation { + switch contentAnimation.type { + case let .groupInstalled(groupId): + transitionHintInstalledGroupId = groupId + case let .groupExpanded(groupId): + transitionHintExpandedGroupId = groupId + default: + break + } + } + let _ = transitionHintExpandedGroupId + + var hintDisappearingGroupFrame: (groupId: AnyHashable, frame: CGRect)? + var previousAbsoluteItemPositions: [VisualItemKey: CGPoint] = [:] + + var anchorItems: [ItemLayer.Key: CGRect] = [:] if let previousComponent = previousComponent, let previousItemLayout = self.itemLayout, previousComponent.itemGroups != component.itemGroups { if !transition.animation.isImmediate { - var previousItemPositionsValue: [ItemLayer.Key: CGPoint] = [:] + var previousItemPositionsValue: [VisualItemKey: CGPoint] = [:] for groupIndex in 0 ..< previousComponent.itemGroups.count { let itemGroup = previousComponent.itemGroups[groupIndex] for itemIndex in 0 ..< itemGroup.items.count { @@ -3649,7 +3762,7 @@ public final class EmojiPagerContentComponent: Component { continue } let itemFrame = previousItemLayout.frame(groupIndex: groupIndex, itemIndex: itemIndex) - previousItemPositionsValue[itemKey] = CGPoint(x: itemFrame.midX, y: itemFrame.midY) + previousItemPositionsValue[.item(id: itemKey)] = CGPoint(x: itemFrame.midX, y: itemFrame.midY) } } previousItemPositions = previousItemPositionsValue @@ -3657,23 +3770,78 @@ public final class EmojiPagerContentComponent: Component { } let effectiveVisibleBounds = CGRect(origin: self.scrollView.bounds.origin, size: self.effectiveVisibleSize) - let topVisibleDetectionBounds = effectiveVisibleBounds.offsetBy(dx: 0.0, dy: pagerEnvironment.containerInsets.top) + let topVisibleDetectionBounds = effectiveVisibleBounds for (key, itemLayer) in self.visibleItemLayers { if !topVisibleDetectionBounds.intersects(itemLayer.frame) { continue } - if let anchorItemValue = anchorItem { - if itemLayer.frame.minY < anchorItemValue.frame.minY { - anchorItem = (key, itemLayer.frame) - } else if itemLayer.frame.minY == anchorItemValue.frame.minY && itemLayer.frame.minX < anchorItemValue.frame.minX { - anchorItem = (key, itemLayer.frame) + + let absoluteFrame = self.scrollView.convert(itemLayer.frame, to: self) + + if let transitionHintInstalledGroupId = transitionHintInstalledGroupId, transitionHintInstalledGroupId == key.groupId { + if let hintDisappearingGroupFrameValue = hintDisappearingGroupFrame { + hintDisappearingGroupFrame = (hintDisappearingGroupFrameValue.groupId, absoluteFrame.union(hintDisappearingGroupFrameValue.frame)) + } else { + hintDisappearingGroupFrame = (key.groupId, absoluteFrame) } + previousAbsoluteItemPositions[.item(id: key)] = CGPoint(x: absoluteFrame.midX, y: absoluteFrame.midY) } else { - anchorItem = (key, itemLayer.frame) + anchorItems[key] = absoluteFrame } } - if let anchorItemValue = anchorItem { - anchorItem = (anchorItemValue.key, self.scrollView.convert(anchorItemValue.frame, to: self)) + + for (id, groupHeader) in self.visibleGroupHeaders { + if !topVisibleDetectionBounds.intersects(groupHeader.frame) { + continue + } + + let absoluteFrame = self.scrollView.convert(groupHeader.frame, to: self) + + if let transitionHintInstalledGroupId = transitionHintInstalledGroupId, transitionHintInstalledGroupId == id { + if let hintDisappearingGroupFrameValue = hintDisappearingGroupFrame { + hintDisappearingGroupFrame = (hintDisappearingGroupFrameValue.groupId, absoluteFrame.union(hintDisappearingGroupFrameValue.frame)) + } else { + hintDisappearingGroupFrame = (id, absoluteFrame) + } + previousAbsoluteItemPositions[.header(groupId: id)] = CGPoint(x: absoluteFrame.midX, y: absoluteFrame.midY) + } + } + + for (id, button) in self.visibleGroupExpandActionButtons { + if !topVisibleDetectionBounds.intersects(button.frame) { + continue + } + + let absoluteFrame = self.scrollView.convert(button.frame, to: self) + + if let transitionHintInstalledGroupId = transitionHintInstalledGroupId, transitionHintInstalledGroupId == id { + if let hintDisappearingGroupFrameValue = hintDisappearingGroupFrame { + hintDisappearingGroupFrame = (hintDisappearingGroupFrameValue.groupId, absoluteFrame.union(hintDisappearingGroupFrameValue.frame)) + } else { + hintDisappearingGroupFrame = (id, absoluteFrame) + } + previousAbsoluteItemPositions[.groupExpandButton(groupId: id)] = CGPoint(x: absoluteFrame.midX, y: absoluteFrame.midY) + } + } + + for (id, button) in self.visibleGroupPremiumButtons { + guard let buttonView = button.view else { + continue + } + if !topVisibleDetectionBounds.intersects(buttonView.frame) { + continue + } + + let absoluteFrame = self.scrollView.convert(buttonView.frame, to: self) + + if let transitionHintInstalledGroupId = transitionHintInstalledGroupId, transitionHintInstalledGroupId == id { + if let hintDisappearingGroupFrameValue = hintDisappearingGroupFrame { + hintDisappearingGroupFrame = (hintDisappearingGroupFrameValue.groupId, absoluteFrame.union(hintDisappearingGroupFrameValue.frame)) + } else { + hintDisappearingGroupFrame = (id, absoluteFrame) + } + previousAbsoluteItemPositions[.groupActionButton(groupId: id)] = CGPoint(x: absoluteFrame.midX, y: absoluteFrame.midY) + } } } @@ -3742,37 +3910,50 @@ public final class EmojiPagerContentComponent: Component { } self.previousScrollingOffset = ScrollingOffsetState(value: scrollView.contentOffset.y, isDraggingOrDecelerating: scrollView.isDragging || scrollView.isDecelerating) - if let anchorItem = anchorItem { - outer: for i in 0 ..< component.itemGroups.count { - if component.itemGroups[i].groupId != anchorItem.key.groupId { - continue + var animatedScrollOffset: CGFloat = 0.0 + if !anchorItems.isEmpty { + let sortedAnchorItems: [(ItemLayer.Key, CGRect)] = anchorItems.sorted(by: { lhs, rhs in + if lhs.value.minY != rhs.value.minY { + return lhs.value.minY < rhs.value.minY + } else { + return lhs.value.minX < rhs.value.minX } - for j in 0 ..< component.itemGroups[i].items.count { - let itemKey: ItemLayer.Key - if let file = component.itemGroups[i].items[j].file { - itemKey = ItemLayer.Key(groupId: component.itemGroups[i].groupId, fileId: file.fileId, staticEmoji: nil) - } else if let staticEmoji = component.itemGroups[i].items[j].staticEmoji { - itemKey = ItemLayer.Key(groupId: component.itemGroups[i].groupId, fileId: nil, staticEmoji: staticEmoji) - } else { + }) + + outer: for i in 0 ..< component.itemGroups.count { + for anchorItem in sortedAnchorItems { + if component.itemGroups[i].groupId != anchorItem.0.groupId { continue } - - if itemKey == anchorItem.key { - let itemFrame = itemLayout.frame(groupIndex: i, itemIndex: j) - - var contentOffsetY = itemFrame.minY - anchorItem.frame.minY - if contentOffsetY > self.scrollView.contentSize.height - self.scrollView.bounds.height { - contentOffsetY = self.scrollView.contentSize.height - self.scrollView.bounds.height - } - if contentOffsetY < 0.0 { - contentOffsetY = 0.0 + for j in 0 ..< component.itemGroups[i].items.count { + let itemKey: ItemLayer.Key + if let file = component.itemGroups[i].items[j].file { + itemKey = ItemLayer.Key(groupId: component.itemGroups[i].groupId, fileId: file.fileId, staticEmoji: nil) + } else if let staticEmoji = component.itemGroups[i].items[j].staticEmoji { + itemKey = ItemLayer.Key(groupId: component.itemGroups[i].groupId, fileId: nil, staticEmoji: staticEmoji) + } else { + continue } - let previousBounds = self.scrollView.bounds - self.scrollView.setContentOffset(CGPoint(x: 0.0, y: contentOffsetY), animated: false) - transition.animateBoundsOrigin(view: self.scrollView, from: CGPoint(x: 0.0, y: previousBounds.minY - contentOffsetY), to: CGPoint(), additive: true) - - break outer + if itemKey == anchorItem.0 { + let itemFrame = itemLayout.frame(groupIndex: i, itemIndex: j) + + var contentOffsetY = itemFrame.minY - anchorItem.1.minY + if contentOffsetY > self.scrollView.contentSize.height - self.scrollView.bounds.height { + contentOffsetY = self.scrollView.contentSize.height - self.scrollView.bounds.height + } + if contentOffsetY < 0.0 { + contentOffsetY = 0.0 + } + + let previousBounds = self.scrollView.bounds + self.scrollView.setContentOffset(CGPoint(x: 0.0, y: contentOffsetY), animated: false) + let scrollOffset = previousBounds.minY - contentOffsetY + transition.animateBoundsOrigin(view: self.scrollView, from: CGPoint(x: 0.0, y: scrollOffset), to: CGPoint(), additive: true) + animatedScrollOffset = scrollOffset + + break outer + } } } } @@ -3781,9 +3962,10 @@ public final class EmojiPagerContentComponent: Component { self.ignoreScrolling = false if calculateUpdatedItemPositions { - var updatedItemPositionsValue: [ItemLayer.Key: CGPoint] = [:] + var updatedItemPositionsValue: [VisualItemKey: CGPoint] = [:] for groupIndex in 0 ..< component.itemGroups.count { let itemGroup = component.itemGroups[groupIndex] + let itemGroupLayout = itemLayout.itemGroupLayouts[groupIndex] for itemIndex in 0 ..< itemGroup.items.count { let item = itemGroup.items[itemIndex] let itemKey: ItemLayer.Key @@ -3795,13 +3977,24 @@ public final class EmojiPagerContentComponent: Component { continue } let itemFrame = itemLayout.frame(groupIndex: groupIndex, itemIndex: itemIndex) - updatedItemPositionsValue[itemKey] = CGPoint(x: itemFrame.midX, y: itemFrame.midY) + updatedItemPositionsValue[.item(id: itemKey)] = CGPoint(x: itemFrame.midX, y: itemFrame.midY) } + + let groupPremiumButtonFrame = CGRect(origin: CGPoint(x: itemLayout.itemInsets.left, y: itemGroupLayout.frame.maxY - itemLayout.premiumButtonHeight + 1.0), size: CGSize(width: itemLayout.width - itemLayout.itemInsets.left - itemLayout.itemInsets.right, height: itemLayout.premiumButtonHeight)) + updatedItemPositionsValue[.groupActionButton(groupId: itemGroup.groupId)] = CGPoint(x: groupPremiumButtonFrame.midX, y: groupPremiumButtonFrame.midY) } updatedItemPositions = updatedItemPositionsValue } - self.updateVisibleItems(transition: itemTransition, attemptSynchronousLoads: !(scrollView.isDragging || scrollView.isDecelerating), previousItemPositions: previousItemPositions, updatedItemPositions: updatedItemPositions) + if let hintDisappearingGroupFrameValue = hintDisappearingGroupFrame { + hintDisappearingGroupFrame = (hintDisappearingGroupFrameValue.groupId, self.scrollView.convert(hintDisappearingGroupFrameValue.frame, from: self)) + } + + for (id, position) in previousAbsoluteItemPositions { + previousAbsoluteItemPositions[id] = position.offsetBy(dx: 0.0, dy: animatedScrollOffset) + } + + self.updateVisibleItems(transition: itemTransition, attemptSynchronousLoads: !(scrollView.isDragging || scrollView.isDecelerating), previousItemPositions: previousItemPositions, previousAbsoluteItemPositions: previousAbsoluteItemPositions, updatedItemPositions: updatedItemPositions, hintDisappearingGroupFrame: hintDisappearingGroupFrame) return availableSize } From c82d01d7ec77bc69d5db5e7ad89aa844ece01ded Mon Sep 17 00:00:00 2001 From: Ali <> Date: Mon, 25 Jul 2022 21:17:13 +0200 Subject: [PATCH 6/8] Fix chat background --- .../Sources/WallpaperBackgroundNode.swift | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift b/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift index 8b68f2a4ac..88a415a332 100644 --- a/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift +++ b/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift @@ -71,6 +71,22 @@ private final class EffectImageLayer: SimpleLayer, GradientBackgroundPatternOver case never } + var fillWithColorUntilLoaded: UIColor? { + didSet { + if self.fillWithColorUntilLoaded != oldValue { + if let fillWithColorUntilLoaded = self.fillWithColorUntilLoaded { + if self.currentContents == nil { + self.backgroundColor = fillWithColorUntilLoaded.cgColor + } else { + self.backgroundColor = nil + } + } else { + self.backgroundColor = nil + } + } + } + } + var patternContentImage: UIImage? { didSet { if self.patternContentImage !== oldValue { @@ -252,6 +268,8 @@ private final class EffectImageLayer: SimpleLayer, GradientBackgroundPatternOver self.allowSettingContents = true self.contents = contents?.cgImage self.allowSettingContents = false + + self.backgroundColor = nil } } @@ -836,16 +854,14 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode self.patternImageLayer.isHidden = false let invertPattern = intensity < 0 + + self.patternImageLayer.fillWithColorUntilLoaded = invertPattern ? .black : nil + if invertPattern { self.backgroundColor = .black let contentAlpha = abs(intensity) self.gradientBackgroundNode?.contentView.alpha = contentAlpha self.contentNode.alpha = contentAlpha - if self.patternImageLayer.contents != nil { - self.patternImageLayer.backgroundColor = nil - } else { - self.patternImageLayer.backgroundColor = nil - } } else { self.backgroundColor = nil self.gradientBackgroundNode?.contentView.alpha = 1.0 @@ -856,6 +872,7 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode self.patternImageDisposable.set(nil) self.validPatternImage = nil self.patternImageLayer.isHidden = true + self.patternImageLayer.fillWithColorUntilLoaded = nil self.patternImageLayer.backgroundColor = nil self.backgroundColor = nil self.gradientBackgroundNode?.contentView.alpha = 1.0 @@ -953,11 +970,6 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode if invertPattern { patternColor = .clear patternBackgroundColor = .clear - if self.patternImageLayer.contents == nil { - self.patternImageLayer.backgroundColor = UIColor.black.cgColor - } else { - self.patternImageLayer.backgroundColor = nil - } } else { if patternIsLight { patternColor = .black From 57213bbeb8ef013ac9728b6e014fa3ba3fd8057a Mon Sep 17 00:00:00 2001 From: Ali <> Date: Mon, 25 Jul 2022 21:17:41 +0200 Subject: [PATCH 7/8] Use round selection for featured tab --- .../Sources/EntityKeyboardTopPanelComponent.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardTopPanelComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardTopPanelComponent.swift index 1c4f0fe10e..7292fa2f3b 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardTopPanelComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardTopPanelComponent.swift @@ -1833,7 +1833,7 @@ final class EntityKeyboardTopPanelComponent: Component { } let isRound: Bool - if let string = activeContentItemId.base as? String, (string == "recent" || string == "static" || string == "trending") { + if let string = activeContentItemId.base as? String, (string == "featuredTop" || string == "recent" || string == "static" || string == "trending") { isRound = true } else { isRound = false From 0fd5879e5a68a3b03774471934f072b4423b8df0 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Mon, 25 Jul 2022 22:03:51 +0200 Subject: [PATCH 8/8] Support multiple removed packs toast --- .../Telegram-iOS/en.lproj/Localizable.strings | 3 ++ .../Sources/StickerPackScreen.swift | 29 +++++++++++++------ .../TelegramUI/Sources/ChatController.swift | 25 ++++++++++++++++ 3 files changed, 48 insertions(+), 9 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index d72f2fa112..473c025560 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -7893,6 +7893,9 @@ Sorry for the inconvenience."; "EmojiPackActionInfo.MultipleRemovedText_1" = "%@ emoji pack is no longer in your emoji."; "EmojiPackActionInfo.MultipleRemovedText_any" = "%@ emoji packs are no longer in your emoji."; +"StickerPackActionInfo.MultipleRemovedText_1" = "%@ sticker pack is no longer in your stickers."; +"StickerPackActionInfo.MultipleRemovedText_any" = "%@ sticker packs are no longer in your stickers."; + "MaskPackActionInfo.RemovedTitle" = "Masks Removed"; "MaskPackActionInfo.ArchivedTitle" = "Masks Archived"; "MaskPackActionInfo.RemovedText" = "%@ is no longer in your masks."; diff --git a/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift b/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift index 96786183cb..a0b1f4ec9e 100644 --- a/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift +++ b/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift @@ -620,17 +620,28 @@ private final class StickerPackContainer: ASDisplayNode { } if installedCount == self.currentStickerPacks.count { + var removedPacks: Signal<[(info: ItemCollectionInfo, index: Int, items: [ItemCollectionItem])], NoError> = .single([]) for (info, _, _) in self.currentStickerPacks { - let _ = (self.context.engine.stickers.removeStickerPackInteractively(id: info.id, option: .delete) - |> deliverOnMainQueue).start(next: { _ in -// guard let (positionInList, _) = indexAndItems else { -// return -// } -// if dismissed { -// actionPerformed?(info, items, .remove(positionInList: positionInList)) -// } - }) + removedPacks = removedPacks + |> mapToSignal { current -> Signal<[(info: ItemCollectionInfo, index: Int, items: [ItemCollectionItem])], NoError> in + return self.context.engine.stickers.removeStickerPackInteractively(id: info.id, option: .delete) + |> map { result -> [(info: ItemCollectionInfo, index: Int, items: [ItemCollectionItem])] in + if let result = result { + return current + [(info, result.0, result.1)] + } else { + return current + } + } + } } + let _ = (removedPacks + |> deliverOnMainQueue).start(next: { [weak self] results in + if !results.isEmpty { + self?.controller?.actionPerformed?(results.map { result -> (StickerPackCollectionInfo, [StickerPackItem], StickerPackScreenPerformedAction) in + return (result.0 as! StickerPackCollectionInfo, result.2.map { $0 as! StickerPackItem }, .remove(positionInList: result.1)) + }) + } + }) } else { var installedPacks: [(StickerPackCollectionInfo, [StickerPackItem], StickerPackScreenPerformedAction)] = [] for (info, items, isInstalled) in self.currentStickerPacks { diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 89e1a20048..ae6d74da35 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -1139,6 +1139,31 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.presentInGlobalOverlay(UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: presentationData.strings.EmojiPackActionInfo_AddedTitle, text: presentationData.strings.EmojiPackActionInfo_MultipleAddedText(Int32(actions.count)), undo: false, info: first.0, topItem: first.1.first, context: context), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return true })) + } else if actions.allSatisfy({ + if case .remove = $0.2 { + return true + } else { + return false + } + }) { + let isEmoji = actions[0].0.id.namespace == Namespaces.ItemCollection.CloudEmojiPacks + + strongSelf.presentInGlobalOverlay(UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: isEmoji ? presentationData.strings.EmojiPackActionInfo_RemovedTitle : presentationData.strings.StickerPackActionInfo_RemovedTitle, text: isEmoji ? presentationData.strings.EmojiPackActionInfo_MultipleRemovedText(Int32(actions.count)) : presentationData.strings.StickerPackActionInfo_MultipleRemovedText(Int32(actions.count)), undo: true, info: actions[0].0, topItem: actions[0].1.first, context: context), elevatedLayout: true, animateInAsReplacement: false, action: { action in + if case .undo = action { + var itemsAndIndices: [(StickerPackCollectionInfo, [StickerPackItem], Int)] = actions.compactMap { action -> (StickerPackCollectionInfo, [StickerPackItem], Int)? in + if case let .remove(index) = action.2 { + return (action.0, action.1, index) + } else { + return nil + } + } + itemsAndIndices.sort(by: { $0.2 < $1.2 }) + for (info, items, index) in itemsAndIndices.reversed() { + let _ = context.engine.stickers.addStickerPackInteractively(info: info, items: items, positionInList: index).start() + } + } + return true + })) } } else if let (info, items, action) = actions.first { let isEmoji = info.id.namespace == Namespaces.ItemCollection.CloudEmojiPacks