diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift index f2e11752ad..203b53e543 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift @@ -1150,9 +1150,33 @@ private final class ProfileGiftsContextImpl { self.actionDisposable.set( _internal_updateStarGiftAddedToProfile(account: self.account, reference: reference, added: added).startStrict() ) + if let index = self.gifts.firstIndex(where: { $0.reference == reference }) { - self.gifts[index] = self.gifts[index].withSavedToProfile(added) + if !added && self.gifts[index].pinnedToTop { + let pinnedGifts = self.gifts.filter { $0.pinnedToTop && $0.reference != reference } + let existingGifts = Set(pinnedGifts.compactMap { $0.reference }) + + var updatedGifts: [ProfileGiftsContext.State.StarGift] = [] + for gift in self.gifts { + if let reference = gift.reference, existingGifts.contains(reference) { + continue + } + var gift = gift + if gift.reference == reference { + gift = gift.withPinnedToTop(false).withSavedToProfile(false) + } + updatedGifts.append(gift) + } + updatedGifts.sort { lhs, rhs in + lhs.date > rhs.date + } + updatedGifts.insert(contentsOf: pinnedGifts, at: 0) + self.gifts = updatedGifts + } else { + self.gifts[index] = self.gifts[index].withSavedToProfile(added) + } } + if let index = self.filteredGifts.firstIndex(where: { $0.reference == reference }) { self.filteredGifts[index] = self.filteredGifts[index].withSavedToProfile(added) if !self.filter.contains(.hidden) && !added { @@ -1164,9 +1188,14 @@ private final class ProfileGiftsContextImpl { func updateStarGiftPinnedToTop(reference: StarGiftReference, pinnedToTop: Bool) { var pinnedGifts = self.gifts.filter { $0.pinnedToTop } + var saveToProfile = false if var gift = self.gifts.first(where: { $0.reference == reference }) { gift = gift.withPinnedToTop(pinnedToTop) if pinnedToTop { + if !gift.savedToProfile { + gift = gift.withSavedToProfile(true) + saveToProfile = true + } pinnedGifts.append(gift) } else { pinnedGifts.removeAll(where: { $0.reference == reference }) @@ -1192,8 +1221,15 @@ private final class ProfileGiftsContextImpl { } self.pushState() + var signal = _internal_updateStarGiftsPinnedToTop(account: self.account, peerId: self.peerId, references: pinnedGifts.compactMap { $0.reference }) + + if saveToProfile { + signal = _internal_updateStarGiftAddedToProfile(account: self.account, reference: reference, added: true) + |> then(signal) + } + self.actionDisposable.set( - _internal_updateStarGiftsPinnedToTop(account: self.account, peerId: self.peerId, references: pinnedGifts.compactMap { $0.reference }).startStrict(completed: { [weak self] in + (signal |> deliverOn(self.queue)).startStrict(completed: { [weak self] in self?.reload() }) ) @@ -1201,13 +1237,19 @@ private final class ProfileGiftsContextImpl { public func updatePinnedToTopStarGifts(references: [StarGiftReference]) { let existingGifts = Set(references) + var saveSignals: [Signal] = [] let currentPinnedGifts = self.gifts.filter { gift in if let reference = gift.reference { return existingGifts.contains(reference) } else { return false } - }.map { $0.withPinnedToTop(true) } + }.map { gift in + if !gift.savedToProfile, let reference = gift.reference { + saveSignals.append(_internal_updateStarGiftAddedToProfile(account: self.account, reference: reference, added: true)) + } + return gift.withPinnedToTop(true).withSavedToProfile(true) + } var updatedGifts: [ProfileGiftsContext.State.StarGift] = [] for gift in self.gifts { @@ -1231,8 +1273,15 @@ private final class ProfileGiftsContextImpl { self.pushState() + var signal = _internal_updateStarGiftsPinnedToTop(account: self.account, peerId: self.peerId, references: pinnedGifts.compactMap { $0.reference }) + if !saveSignals.isEmpty { + signal = combineLatest(saveSignals) + |> ignoreValues + |> then(signal) + } + self.actionDisposable.set( - _internal_updateStarGiftsPinnedToTop(account: self.account, peerId: self.peerId, references: pinnedGifts.compactMap { $0.reference }).startStrict(completed: { [weak self] in + (signal |> deliverOn(self.queue)).startStrict(completed: { [weak self] in self?.reload() }) ) diff --git a/submodules/TelegramUI/Components/Gifts/GiftItemComponent/Sources/GiftItemComponent.swift b/submodules/TelegramUI/Components/Gifts/GiftItemComponent/Sources/GiftItemComponent.swift index 4d23e846a7..c60cc9df39 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftItemComponent/Sources/GiftItemComponent.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftItemComponent/Sources/GiftItemComponent.swift @@ -664,7 +664,7 @@ public final class GiftItemComponent: Component { var iconBackgroundSize: CGSize? if component.isEditing { - if !component.isPinned { + if !component.isPinned && backgroundColor != nil { iconBackgroundSize = CGSize(width: 48.0, height: 48.0) } } else { @@ -710,7 +710,7 @@ public final class GiftItemComponent: Component { iconBackground.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false) } - if component.isPinned || component.isEditing { + if component.isPinned || (component.isEditing && backgroundColor != nil) { let pinnedIcon: UIImageView if let currentIcon = self.pinnedIcon { pinnedIcon = currentIcon diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoPaneContainerNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoPaneContainerNode.swift index 7407e65380..91ad9d8083 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoPaneContainerNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoPaneContainerNode.swift @@ -43,9 +43,11 @@ final class PeerInfoPaneTabsContainerPaneNode: ASDisplayNode { private let titleNode: ImmediateTextNode private let buttonNode: HighlightTrackingButtonNode - private var iconLayers: [InlineStickerItemLayer] = [] + private var iconLayers: [AnyHashable: InlineStickerItemLayer] = [:] private var isSelected: Bool = false + private var icons: [ProfileGiftsContext.State.StarGift] = [] + private var titleWidth: CGFloat? init(pressed: @escaping () -> Void) { self.pressed = pressed @@ -67,27 +69,53 @@ final class PeerInfoPaneTabsContainerPaneNode: ASDisplayNode { self.pressed() } - func updateText(context: AccountContext, title: String, icons: [TelegramMediaFile] = [], isSelected: Bool, presentationData: PresentationData) { + func updateText(context: AccountContext, title: String, icons: [ProfileGiftsContext.State.StarGift] = [], isSelected: Bool, presentationData: PresentationData) { self.isSelected = isSelected self.titleNode.attributedText = NSAttributedString(string: title, font: Font.medium(14.0), textColor: isSelected ? presentationData.theme.list.itemAccentColor : presentationData.theme.list.itemSecondaryTextColor) + self.icons = icons if !icons.isEmpty { - if self.iconLayers.isEmpty { - for icon in icons { - let iconSize = CGSize(width: 18.0, height: 18.0) + var validIds = Set() + var index = 0 + for icon in icons { + let id: AnyHashable + if let reference = icon.reference { + id = reference + } else { + id = index + } + validIds.insert(id) + + let iconSize = CGSize(width: 18.0, height: 18.0) + if let _ = self.iconLayers[id] { + + } else { + var file: TelegramMediaFile? + switch icon.gift { + case let .generic(gift): + file = gift.file + case let .unique(gift): + for attribute in gift.attributes { + if case let .model(_, fileValue, _) = attribute { + file = fileValue + } + } + } + guard let file else { + continue + } let emoji = ChatTextInputTextCustomEmojiAttribute( interactivelySelectedFromPackId: nil, - fileId: icon.fileId.id, - file: icon + fileId: file.fileId.id, + file: file ) - let animationLayer = InlineStickerItemLayer( context: .account(context), userLocation: .other, attemptSynchronousLoad: false, emoji: emoji, - file: icon, + file: file, cache: context.animationCache, renderer: context.animationRenderer, unique: true, @@ -96,12 +124,30 @@ final class PeerInfoPaneTabsContainerPaneNode: ASDisplayNode { loopCount: 1 ) animationLayer.isVisibleForAnimations = true - self.iconLayers.append(animationLayer) + self.iconLayers[id] = animationLayer self.layer.addSublayer(animationLayer) + + animationLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + animationLayer.animateScale(from: 0.01, to: 1.0, duration: 0.2) + } + index += 1 + } + + var removeIds: [AnyHashable] = [] + for (id, layer) in self.iconLayers { + if !validIds.contains(id) { + removeIds.append(id) + layer.animateScale(from: 1.0, to: 0.01, duration: 0.25, removeOnCompletion: false) + layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in + layer.removeFromSuperlayer() + }) } } + for id in removeIds { + self.iconLayers.removeValue(forKey: id) + } } else { - for layer in self.iconLayers { + for (_, layer) in self.iconLayers { layer.removeFromSuperlayer() } self.iconLayers.removeAll() @@ -115,24 +161,54 @@ final class PeerInfoPaneTabsContainerPaneNode: ASDisplayNode { } func updateLayout(height: CGFloat) -> CGFloat { - var totalWidth: CGFloat = 0.0 let titleSize = self.titleNode.updateLayout(CGSize(width: 200.0, height: .greatestFiniteMagnitude)) - self.titleNode.frame = CGRect(origin: CGPoint(x: 0.0, y: floor((height - titleSize.height) / 2.0)), size: titleSize) - totalWidth = titleSize.width + let iconSize = CGSize(width: 18.0, height: 18.0) + let spacing: CGFloat = 1.0 + self.titleNode.frame = CGRect(origin: CGPoint(x: 0.0, y: floor((height - titleSize.height) / 2.0)), size: titleSize) + self.titleWidth = titleSize.width + + var totalWidth = titleSize.width if !self.iconLayers.isEmpty { totalWidth += 2.0 - let iconSize = CGSize(width: 18.0, height: 18.0) - let spacing: CGFloat = 1.0 - for iconlayer in self.iconLayers { - iconlayer.frame = CGRect(origin: CGPoint(x: totalWidth, y: 15.0), size: iconSize) - totalWidth += iconSize.width + spacing - } + totalWidth += (iconSize.width + spacing) * CGFloat(self.iconLayers.count) totalWidth -= spacing } + + self.layoutIcons(transition: .animated(duration: 0.3, curve: .spring)) + return totalWidth } + func layoutIcons(transition: ContainedViewLayoutTransition) { + guard let titleWidth = self.titleWidth else { + return + } + let iconSize = CGSize(width: 18.0, height: 18.0) + let spacing: CGFloat = 1.0 + + var origin = CGPoint(x: titleWidth + 2.0, y: 15.0) + + var index = 0 + for icon in self.icons { + let id: AnyHashable + if let reference = icon.reference { + id = reference + } else { + id = index + } + if let layer = self.iconLayers[id] { + var iconTransition = transition + if layer.frame.width.isZero { + iconTransition = .immediate + } + iconTransition.updateFrame(layer: layer, frame: CGRect(origin: origin, size: iconSize)) + } + origin.x += iconSize.width + spacing + index += 1 + } + } + func updateArea(size: CGSize, sideInset: CGFloat) { self.buttonNode.frame = CGRect(origin: CGPoint(x: -sideInset, y: 0.0), size: CGSize(width: size.width + sideInset * 2.0, height: size.height)) } @@ -141,7 +217,7 @@ final class PeerInfoPaneTabsContainerPaneNode: ASDisplayNode { struct PeerInfoPaneSpecifier: Equatable { var key: PeerInfoPaneKey var title: String - var icons: [TelegramMediaFile] + var icons: [ProfileGiftsContext.State.StarGift] } private func interpolateFrame(from fromValue: CGRect, to toValue: CGRect, t: CGFloat) -> CGRect { @@ -1189,7 +1265,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat self.tabsContainerNode.update(size: CGSize(width: size.width - sideInset * 2.0, height: tabsHeight), presentationData: presentationData, paneList: availablePanes.map { key in let title: String - var icons: [TelegramMediaFile] = [] + var icons: [ProfileGiftsContext.State.StarGift] = [] switch key { case .stories: title = presentationData.strings.PeerInfo_PaneStories @@ -1223,19 +1299,9 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat title = presentationData.strings.PeerInfo_SavedMessagesTabTitle case .gifts: title = presentationData.strings.PeerInfo_PaneGifts - icons = data?.profileGiftsContext?.currentState?.gifts.prefix(3).compactMap { gift in - switch gift.gift { - case let .generic(gift): - return gift.file - case let .unique(gift): - for attribute in gift.attributes { - if case let .model(_, file, _) = attribute { - return file - } - } - return nil - } - } ?? [] + if let gifts = data?.profileGiftsContext?.currentState?.gifts.prefix(3) { + icons = Array(gifts) + } } return PeerInfoPaneSpecifier(key: key, title: title, icons: icons) }, selectedPane: self.currentPaneKey, disableSwitching: disableTabSwitching, transitionFraction: self.transitionFraction, transition: transition) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift index de57c1328c..30b66f1639 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift @@ -76,7 +76,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr } private var starsProducts: [ProfileGiftsContext.State.StarGift]? - private var starsItems: [AnyHashable: (ProfileGiftsContext.State.StarGift, ComponentView)] = [:] + private var starsItems: [AnyHashable: (StarGiftReference?, ComponentView)] = [:] private var resultsAreFiltered = false private var resultsAreEmpty = false @@ -90,6 +90,13 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr } private var reorderedReferencesPromise = ValuePromise<[StarGiftReference]?>(nil) + private var reorderedPinnedReferences: Set? { + didSet { + self.reorderedPinnedReferencesPromise.set(self.reorderedPinnedReferences) + } + } + private var reorderedPinnedReferencesPromise = ValuePromise?>(nil) + private var reorderRecognizer: ReorderGestureRecognizer? private let maxPinnedCount: Int @@ -136,17 +143,24 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr for reference in reorderedReferences { if let index = stateItems.firstIndex(where: { $0.reference == reference }) { seenIds.insert(reference) - fixedStateItems.append(stateItems[index]) + var item = stateItems[index] + if self.reorderedPinnedReferences?.contains(reference) == true, !item.pinnedToTop { + item = item.withPinnedToTop(true) + } + fixedStateItems.append(item) } } for item in stateItems { if let reference = item.reference, !seenIds.contains(reference) { + var item = item + if self.reorderedPinnedReferences?.contains(reference) == true, !item.pinnedToTop { + item = item.withPinnedToTop(true) + } fixedStateItems.append(item) } } stateItems = fixedStateItems - //self.reorderedReferences = fixedStateItems.compactMap(\.reference) } self.starsProducts = stateItems self.pinnedReferences = Array(stateItems.filter { $0.pinnedToTop }.compactMap { $0.reference }) @@ -218,7 +232,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr private func item(at point: CGPoint) -> (AnyHashable, ComponentView)? { let localPoint = self.scrollNode.view.convert(point, from: self.view) for (id, visibleItem) in self.starsItems { - if let view = visibleItem.1.view, view.frame.contains(localPoint) { + if let view = visibleItem.1.view, view.frame.contains(localPoint), let reference = visibleItem.0, self.pinnedReferences.contains(reference) { return (id, visibleItem.1) } } @@ -258,6 +272,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr Queue.mainQueue().after(1.0) { self.reorderedReferences = nil + self.reorderedPinnedReferences = nil } } @@ -281,7 +296,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr } else { self.reorderingItem = nil } - self.updateScrolling(transition: .spring(duration: 0.3)) + self.updateScrolling(transition: item == nil ? .spring(duration: 0.3) : .immediate) } } @@ -296,9 +311,9 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr if visibleItem.1 === visibleReorderingItem.1 { continue } - if let view = visibleItem.1.view, view.frame.contains(targetPosition), let reorderItem = self.starsItems[id]?.0 { - if let targetIndex = starsProducts.firstIndex(where: { $0.reference == visibleItem.0.reference }) { - self.reorderIfPossible(item: reorderItem, toIndex: targetIndex) + if let view = visibleItem.1.view, view.frame.contains(targetPosition), let reorderItemReference = self.starsItems[id]?.0 { + if let targetIndex = starsProducts.firstIndex(where: { $0.reference == visibleItem.0 }) { + self.reorderIfPossible(reference: reorderItemReference, toIndex: targetIndex) } break } @@ -307,7 +322,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr } } - private func reorderIfPossible(item: ProfileGiftsContext.State.StarGift, toIndex: Int) { + private func reorderIfPossible(reference: StarGiftReference, toIndex: Int) { if let items = self.starsProducts { var toIndex = toIndex @@ -322,7 +337,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr return item.reference } - if let reference = item.reference, let fromIndex = ids.firstIndex(of: reference) { + if let fromIndex = ids.firstIndex(of: reference) { if fromIndex < toIndex { ids.insert(reference, at: toIndex + 1) ids.remove(at: fromIndex) @@ -401,7 +416,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr visibleItem = current } else { visibleItem = ComponentView() - self.starsItems[itemId] = (product, visibleItem) + self.starsItems[itemId] = (product.reference, visibleItem) itemTransition = .immediate } @@ -450,11 +465,38 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr isEditing: self.isReordering, mode: .profile, action: { [weak self] in - guard let self else { + guard let self, let presentationData = self.currentParams?.presentationData else { return } if self.isReordering { - + if !product.pinnedToTop, let reference = product.reference, let items = self.starsProducts { + if self.pinnedReferences.count >= self.maxPinnedCount { + self.parentController?.present(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: presentationData.strings.PeerInfo_Gifts_ToastPinLimit_Text(Int32(self.maxPinnedCount)), timeout: nil, customUndoText: nil), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root)) + return + } + + var reorderedPinnedReferences = Set() + if let current = self.reorderedPinnedReferences { + reorderedPinnedReferences = current + } + reorderedPinnedReferences.insert(reference) + self.reorderedPinnedReferences = reorderedPinnedReferences + + if let maxPinnedIndex = items.lastIndex(where: { $0.pinnedToTop }) { + var reorderedReferences: [StarGiftReference] + if let current = self.reorderedReferences { + reorderedReferences = current + } else { + let ids = items.compactMap { item -> StarGiftReference? in + return item.reference + } + reorderedReferences = ids + } + reorderedReferences.removeAll(where: { $0 == reference }) + reorderedReferences.insert(reference, at: maxPinnedIndex + 1) + self.reorderedReferences = reorderedReferences + } + } } else { let controller = GiftViewScreen( context: self.context, @@ -517,10 +559,15 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr } } var itemFrame = itemFrame + var isReordering = false if let reorderingItem = self.reorderingItem, itemId == reorderingItem.id { itemFrame = itemFrame.size.centered(around: reorderingItem.position) + isReordering = true + } + if itemView.layer.animation(forKey: "position") != nil && !isReordering { + } else { + itemTransition.setFrame(view: itemView, frame: itemFrame) } - itemTransition.setFrame(view: itemView, frame: itemFrame) if self.isReordering && product.pinnedToTop { if itemView.layer.animation(forKey: "shaking_position") == nil { @@ -933,34 +980,36 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr var items: [ContextMenuItem] = [] if canManage { - items.append(.action(ContextMenuActionItem(text: gift.pinnedToTop ? strings.PeerInfo_Gifts_Context_Unpin : strings.PeerInfo_Gifts_Context_Pin , icon: { theme in generateTintedImage(image: UIImage(bundleImageName: gift.pinnedToTop ? "Chat/Context Menu/Unpin" : "Chat/Context Menu/Pin"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, f in - c?.dismiss(completion: { [weak self] in - guard let self else { - return - } - let pinnedToTop = !gift.pinnedToTop - - if pinnedToTop && self.pinnedReferences.count >= self.maxPinnedCount { - self.parentController?.present(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: presentationData.strings.PeerInfo_Gifts_ToastPinLimit_Text(Int32(self.maxPinnedCount)), timeout: nil, customUndoText: nil), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root)) - return - } - - if let reference = gift.reference { - self.profileGifts.updateStarGiftPinnedToTop(reference: reference, pinnedToTop: pinnedToTop) - } - - let toastTitle: String? - let toastText: String - if !pinnedToTop { - toastTitle = nil - toastText = presentationData.strings.PeerInfo_Gifts_ToastUnpinned_Text - } else { - toastTitle = presentationData.strings.PeerInfo_Gifts_ToastPinned_Title - toastText = presentationData.strings.PeerInfo_Gifts_ToastPinned_Text - } - self.parentController?.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: !pinnedToTop ? "anim_toastunpin" : "anim_toastpin", scale: 0.06, colors: [:], title: toastTitle, text: toastText, customUndoText: nil, timeout: 5), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root)) - }) - }))) + if case .unique = gift.gift { + items.append(.action(ContextMenuActionItem(text: gift.pinnedToTop ? strings.PeerInfo_Gifts_Context_Unpin : strings.PeerInfo_Gifts_Context_Pin , icon: { theme in generateTintedImage(image: UIImage(bundleImageName: gift.pinnedToTop ? "Chat/Context Menu/Unpin" : "Chat/Context Menu/Pin"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, f in + c?.dismiss(completion: { [weak self] in + guard let self else { + return + } + let pinnedToTop = !gift.pinnedToTop + + if pinnedToTop && self.pinnedReferences.count >= self.maxPinnedCount { + self.parentController?.present(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: presentationData.strings.PeerInfo_Gifts_ToastPinLimit_Text(Int32(self.maxPinnedCount)), timeout: nil, customUndoText: nil), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root)) + return + } + + if let reference = gift.reference { + self.profileGifts.updateStarGiftPinnedToTop(reference: reference, pinnedToTop: pinnedToTop) + } + + let toastTitle: String? + let toastText: String + if !pinnedToTop { + toastTitle = nil + toastText = presentationData.strings.PeerInfo_Gifts_ToastUnpinned_Text + } else { + toastTitle = presentationData.strings.PeerInfo_Gifts_ToastPinned_Title + toastText = presentationData.strings.PeerInfo_Gifts_ToastPinned_Text + } + self.parentController?.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: !pinnedToTop ? "anim_toastunpin" : "anim_toastpin", scale: 0.06, colors: [:], title: toastTitle, text: toastText, customUndoText: nil, timeout: 5), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root)) + }) + }))) + } if canReorder { items.append(.action(ContextMenuActionItem(text: strings.PeerInfo_Gifts_Context_Reorder, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReorderItems"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, f in