From 848e342991081343f82baf1c03ebfc3ea16bfd91 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Fri, 11 Aug 2023 02:23:27 +0400 Subject: [PATCH] Stories --- submodules/Postbox/Sources/StoryView.swift | 52 ++++++++++++ submodules/Postbox/Sources/Views.swift | 11 +++ .../Sources/MediaEditorScreen.swift | 8 +- .../Sources/PeerListItemComponent.swift | 22 +++-- .../Sources/StoryChatContent.swift | 42 +++++----- .../Sources/StoryContainerScreen.swift | 26 ++++++ .../StoryItemSetContainerComponent.swift | 38 ++++++++- .../StoryItemSetViewListComponent.swift | 84 +++++++++++++++++-- 8 files changed, 242 insertions(+), 41 deletions(-) create mode 100644 submodules/Postbox/Sources/StoryView.swift diff --git a/submodules/Postbox/Sources/StoryView.swift b/submodules/Postbox/Sources/StoryView.swift new file mode 100644 index 0000000000..4503b7dd01 --- /dev/null +++ b/submodules/Postbox/Sources/StoryView.swift @@ -0,0 +1,52 @@ +import Foundation + +final class MutableStoryView: MutablePostboxView { + let id: StoryId + var item: CodableEntry? + + init(postbox: PostboxImpl, id: StoryId) { + self.id = id + self.item = postbox.storyTable.get(id: self.id) + } + + func replay(postbox: PostboxImpl, transaction: PostboxTransaction) -> Bool { + var updated = false + + for event in transaction.storyEvents { + switch event { + case .updated(self.id): + let item = postbox.storyTable.get(id: self.id) + if self.item != item { + self.item = item + updated = true + } + default: + break + } + } + + return updated + } + + func refreshDueToExternalTransaction(postbox: PostboxImpl) -> Bool { + let item = postbox.storyTable.get(id: self.id) + if self.item != item { + self.item = item + return true + } else { + return false + } + } + + func immutableView() -> PostboxView { + return StoryView(self) + } +} + +public final class StoryView: PostboxView { + public let item: CodableEntry? + + init(_ view: MutableStoryView) { + self.item = view.item + } +} diff --git a/submodules/Postbox/Sources/Views.swift b/submodules/Postbox/Sources/Views.swift index f8f1904bf5..779ce1c5e5 100644 --- a/submodules/Postbox/Sources/Views.swift +++ b/submodules/Postbox/Sources/Views.swift @@ -45,6 +45,7 @@ public enum PostboxViewKey: Hashable { case storyItems(peerId: PeerId) case storyExpirationTimeItems case peerStoryStats(peerIds: Set) + case story(id: StoryId) public func hash(into hasher: inout Hasher) { switch self { @@ -150,6 +151,8 @@ public enum PostboxViewKey: Hashable { hasher.combine(19) case let .peerStoryStats(peerIds): hasher.combine(peerIds) + case let .story(id): + hasher.combine(id) } } @@ -419,6 +422,12 @@ public enum PostboxViewKey: Hashable { } else { return false } + case let .story(id): + if case .story(id) = rhs { + return true + } else { + return false + } } } } @@ -513,5 +522,7 @@ func postboxViewForKey(postbox: PostboxImpl, key: PostboxViewKey) -> MutablePost return MutableStoryExpirationTimeItemsView(postbox: postbox) case let .peerStoryStats(peerIds): return MutablePeerStoryStatsView(postbox: postbox, peerIds: peerIds) + case let .story(id): + return MutableStoryView(postbox: postbox, id: id) } } diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index 9ed314cd98..302627871f 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -3250,11 +3250,13 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate transition.setFrame(view: self.selectionContainerView, frame: CGRect(origin: .zero, size: previewFrame.size)) self.interaction?.containerLayoutUpdated(layout: layout, transition: transition) - + + var presentationContextLayout = layout + presentationContextLayout.intrinsicInsets.top = max(presentationContextLayout.intrinsicInsets.top, topInset) // var layout = layout // layout.intrinsicInsets.top = topInset // layout.intrinsicInsets.bottom = bottomInset + 60.0 - controller.presentationContext.containerLayoutUpdated(layout, transition: transition.containedViewLayoutTransition) + controller.presentationContext.containerLayoutUpdated(presentationContextLayout, transition: transition.containedViewLayoutTransition) if isFirstTime { self.animateIn() @@ -3683,7 +3685,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate let controller = UndoOverlayController(presentationData: presentationData, content: .autoDelete(isOn: true, title: nil, text: text, customUndoText: nil), elevatedLayout: false, position: .top, animateInAsReplacement: false, action: { [weak self] action in if case .info = action, let self { - let controller = context.sharedContext.makePremiumIntroController(context: context, source: .stories, forceDark: true, dismissed: nil) + let controller = context.sharedContext.makePremiumIntroController(context: context, source: .storiesExpirationDurations, forceDark: true, dismissed: nil) self.push(controller) } return false } diff --git a/submodules/TelegramUI/Components/Stories/PeerListItemComponent/Sources/PeerListItemComponent.swift b/submodules/TelegramUI/Components/Stories/PeerListItemComponent/Sources/PeerListItemComponent.swift index d8d8cdab6d..3c443c050c 100644 --- a/submodules/TelegramUI/Components/Stories/PeerListItemComponent/Sources/PeerListItemComponent.swift +++ b/submodules/TelegramUI/Components/Stories/PeerListItemComponent/Sources/PeerListItemComponent.swift @@ -339,7 +339,7 @@ public final class PeerListItemComponent: Component { if let reaction = component.reaction, case .custom = reaction.reaction { reactionLayer.isVisibleForAnimations = true } - self.layer.addSublayer(reactionLayer) + self.containerButton.layer.addSublayer(reactionLayer) if var iconFrame = self.iconFrame { if let reaction = component.reaction, case .builtin = reaction.reaction { @@ -671,26 +671,22 @@ public final class PeerListItemComponent: Component { let imageSize = CGSize(width: 22.0, height: 22.0) self.iconFrame = CGRect(origin: CGPoint(x: availableSize.width - (contextInset * 2.0 + 14.0 + component.sideInset) - imageSize.width, y: floor((height - verticalInset * 2.0 - imageSize.height) * 0.5)), size: imageSize) + var reactionIconTransition = transition if previousComponent?.reaction != component.reaction { if let reaction = component.reaction, case .builtin("❤") = reaction.reaction { self.file = nil self.updateReactionLayer() - var reactionTransition = transition let heartReactionIcon: UIImageView if let current = self.heartReactionIcon { heartReactionIcon = current } else { - reactionTransition = reactionTransition.withAnimation(.none) + reactionIconTransition = reactionIconTransition.withAnimation(.none) heartReactionIcon = UIImageView() self.heartReactionIcon = heartReactionIcon self.containerButton.addSubview(heartReactionIcon) heartReactionIcon.image = PresentationResourcesChat.storyViewListLikeIcon(component.theme) } - - if let image = heartReactionIcon.image, let iconFrame = self.iconFrame { - reactionTransition.setFrame(view: heartReactionIcon, frame: image.size.centered(around: iconFrame.center)) - } } else { if let heartReactionIcon = self.heartReactionIcon { self.heartReactionIcon = nil @@ -719,6 +715,18 @@ public final class PeerListItemComponent: Component { } } + if let heartReactionIcon = self.heartReactionIcon, let image = heartReactionIcon.image, let iconFrame = self.iconFrame { + reactionIconTransition.setFrame(view: heartReactionIcon, frame: image.size.centered(around: iconFrame.center)) + } + + if let reactionLayer = self.reactionLayer, let iconFrame = self.iconFrame { + var adjustedIconFrame = iconFrame + if let reaction = component.reaction, case .builtin = reaction.reaction { + adjustedIconFrame = adjustedIconFrame.insetBy(dx: -adjustedIconFrame.width * 0.5, dy: -adjustedIconFrame.height * 0.5) + } + transition.setFrame(layer: reactionLayer, frame: adjustedIconFrame) + } + if themeUpdated { self.separatorLayer.backgroundColor = component.theme.list.itemPlainSeparatorColor.cgColor } diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryChatContent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryChatContent.swift index 62fb62a08c..6ddae15928 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryChatContent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryChatContent.swift @@ -961,32 +961,36 @@ public final class SingleStoryContentContextImpl: StoryContentContext { TelegramEngine.EngineData.Item.Peer.NotificationSettings(id: storyId.peerId), TelegramEngine.EngineData.Item.NotificationSettings.Global() ), - context.account.postbox.transaction { transaction -> (Stories.StoredItem?, [PeerId: Peer], [MediaId: TelegramMediaFile]) in - guard let item = transaction.getStory(id: storyId)?.get(Stories.StoredItem.self) else { - return (nil, [:], [:]) - } - var peers: [PeerId: Peer] = [:] - var allEntityFiles: [MediaId: TelegramMediaFile] = [:] - if case let .item(item) = item { - if let views = item.views { - for id in views.seenPeerIds { - if let peer = transaction.getPeer(id) { - peers[peer.id] = peer + context.account.postbox.combinedView(keys: [PostboxViewKey.story(id: storyId)]) |> mapToSignal { views -> Signal<(Stories.StoredItem?, [PeerId: Peer], [MediaId: TelegramMediaFile]), NoError> in + let item = (views.views[PostboxViewKey.story(id: storyId)] as? StoryView)?.item?.get(Stories.StoredItem.self) + + return context.account.postbox.transaction { transaction -> (Stories.StoredItem?, [PeerId: Peer], [MediaId: TelegramMediaFile]) in + guard let item else { + return (nil, [:], [:]) + } + var peers: [PeerId: Peer] = [:] + var allEntityFiles: [MediaId: TelegramMediaFile] = [:] + if case let .item(item) = item { + if let views = item.views { + for id in views.seenPeerIds { + if let peer = transaction.getPeer(id) { + peers[peer.id] = peer + } } } - } - for entity in item.entities { - if case let .CustomEmoji(_, fileId) = entity.type { - let mediaId = MediaId(namespace: Namespaces.Media.CloudFile, id: fileId) - if allEntityFiles[mediaId] == nil { - if let file = transaction.getMedia(mediaId) as? TelegramMediaFile { - allEntityFiles[file.fileId] = file + for entity in item.entities { + if case let .CustomEmoji(_, fileId) = entity.type { + let mediaId = MediaId(namespace: Namespaces.Media.CloudFile, id: fileId) + if allEntityFiles[mediaId] == nil { + if let file = transaction.getMedia(mediaId) as? TelegramMediaFile { + allEntityFiles[file.fileId] = file + } } } } } + return (item, peers, allEntityFiles) } - return (item, peers, allEntityFiles) } ) |> deliverOnMainQueue).start(next: { [weak self] data, itemAndPeers in diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift index 0f7af5404a..a319cc9e8b 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift @@ -153,6 +153,7 @@ private final class StoryPinchGesture: UIPinchGestureRecognizer { private(set) var currentTransform: (CGFloat, CGPoint, CGPoint)? + var shouldBegin: ((CGPoint) -> Bool)? var began: (() -> Void)? var updated: ((CGFloat, CGPoint, CGPoint) -> Void)? var ended: (() -> Void)? @@ -181,6 +182,11 @@ private final class StoryPinchGesture: UIPinchGestureRecognizer { } override func touchesBegan(_ touches: Set, with event: UIEvent) { + if let touch = touches.first, let shouldBegin = self.shouldBegin, !shouldBegin(touch.location(in: self.view)) { + self.state = .failed + return + } + super.touchesBegan(touches, with: event) //self.currentTouches.formUnion(touches) @@ -457,6 +463,9 @@ private final class StoryContainerScreenComponent: Component { guard let component = self.component, let stateValue = component.content.stateValue, let slice = stateValue.slice, let itemSetView = self.visibleItemSetViews[slice.peer.id], let itemSetComponentView = itemSetView.view.view as? StoryItemSetContainerComponent.View else { return false } + if !itemSetComponentView.allowsExternalGestures(point: touch.location(in: itemSetComponentView)) { + return false + } if !itemSetComponentView.isPointInsideContentArea(point: touch.location(in: itemSetComponentView)) { return false } @@ -467,6 +476,23 @@ private final class StoryContainerScreenComponent: Component { let pinchRecognizer = StoryPinchGesture() pinchRecognizer.delegate = self + pinchRecognizer.shouldBegin = { [weak self] pinchLocation in + guard let self else { + return false + } + if let component = self.component, let stateValue = component.content.stateValue, let slice = stateValue.slice, let itemSetView = self.visibleItemSetViews[slice.peer.id] { + if let itemSetComponentView = itemSetView.view.view as? StoryItemSetContainerComponent.View { + let itemLocation = self.convert(pinchLocation, to: itemSetComponentView) + if itemSetComponentView.allowsExternalGestures(point: itemLocation) { + return true + } else { + return false + } + } + } + + return false + } pinchRecognizer.updated = { [weak self] scale, pinchLocation, offset in guard let self else { return diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift index 309f3ef272..9e5afd3c64 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift @@ -542,6 +542,12 @@ public final class StoryItemSetContainerComponent: Component { return [] } + for (_, viewList) in self.viewLists { + if let view = viewList.view.view, view.hitTest(self.convert(point, to: view), with: nil) != nil { + return [.down] + } + } + if self.itemsContainerView.frame.contains(point) { if !self.isPointInsideContentArea(point: point) { return [] @@ -670,6 +676,13 @@ public final class StoryItemSetContainerComponent: Component { self.audioRecorderStatusDisposable?.dispose() } + func allowsExternalGestures(point: CGPoint) -> Bool { + if self.viewListDisplayState != .hidden { + return false + } + return true + } + func isPointInsideContentArea(point: CGPoint) -> Bool { if let inputPanelView = self.inputPanel.view, inputPanelView.alpha != 0.0 { if inputPanelView.frame.contains(point) { @@ -1146,6 +1159,9 @@ public final class StoryItemSetContainerComponent: Component { } if result === self.scroller { + if self.viewListDisplayState == .full { + return self + } return self.itemsContainerView } @@ -1598,7 +1614,7 @@ public final class StoryItemSetContainerComponent: Component { } var footerPanelY: CGFloat = self.itemsContainerView.frame.minY + itemLayout.contentFrame.center.y + itemLayout.contentFrame.height * 0.5 * itemScale - footerPanelY += (1.0 - footerExpandFraction) * 4.0 + footerExpandFraction * (-41.0) + footerPanelY += (1.0 - footerExpandFraction) * (10.0) + footerExpandFraction * (-41.0) let footerPanelMinScale: CGFloat = (1.0 - scaleFraction) + (itemLayout.sideVisibleItemScale / itemLayout.contentMinScale) * scaleFraction let footerPanelScale = itemLayout.contentScaleFraction * footerPanelMinScale + 1.0 * (1.0 - itemLayout.contentScaleFraction) @@ -1612,6 +1628,10 @@ public final class StoryItemSetContainerComponent: Component { itemTransition.setScale(view: footerPanelView, scale: footerPanelScale) var footerAlpha: CGFloat = 1.0 - itemLayout.contentOverflowFraction + + let minFooterAlpha: CGFloat = 1.0 - fractionDistanceToCenter + footerAlpha = footerAlpha * itemLayout.contentScaleFraction + minFooterAlpha * (1.0 - itemLayout.contentScaleFraction) + if component.hideUI || self.isEditingStory { footerAlpha = 0.0 } @@ -1711,8 +1731,17 @@ public final class StoryItemSetContainerComponent: Component { if canReply { if let inputPanelView = self.inputPanel.view as? MessageInputPanelComponent.View { - return { [weak inputPanelView] in - inputPanelView?.activateInput() + return { [weak self, weak inputPanelView] in + guard let self, let inputPanelView else { + return + } + + if self.displayLikeReactions { + self.displayLikeReactions = false + self.state?.updated(transition: Transition(animation: .curve(duration: 0.25, curve: .easeInOut))) + } + + inputPanelView.activateInput() } } } @@ -4273,6 +4302,9 @@ public final class StoryItemSetContainerComponent: Component { guard let self, let component = self.component else { return } + if component.slice.item.storyItem.privacy == privacy { + return + } let _ = component.context.engine.messages.editStoryPrivacy(id: component.slice.item.storyItem.id, privacy: privacy).start() self.presentPrivacyTooltip(privacy: privacy) diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetViewListComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetViewListComponent.swift index 7d72037fb8..3d2972fedb 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetViewListComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetViewListComponent.swift @@ -164,21 +164,23 @@ final class StoryItemSetViewListComponent: Component { var sideInset: CGFloat var itemHeight: CGFloat var itemCount: Int + var premiumFooterSize: CGSize? var contentSize: CGSize - init(containerSize: CGSize, bottomInset: CGFloat, topInset: CGFloat, sideInset: CGFloat, itemHeight: CGFloat, itemCount: Int) { + init(containerSize: CGSize, bottomInset: CGFloat, topInset: CGFloat, sideInset: CGFloat, itemHeight: CGFloat, itemCount: Int, premiumFooterSize: CGSize?) { self.containerSize = containerSize self.bottomInset = bottomInset self.topInset = topInset self.sideInset = sideInset self.itemHeight = itemHeight self.itemCount = itemCount + self.premiumFooterSize = premiumFooterSize self.contentSize = CGSize(width: containerSize.width, height: topInset + CGFloat(itemCount) * itemHeight + bottomInset) - #if DEBUG && false - self.contentSize.height += 1000.0 - #endif + if let premiumFooterSize { + self.contentSize.height += 13.0 + premiumFooterSize.height + 12.0 + } } func visibleItems(for rect: CGRect) -> Range? { @@ -256,6 +258,8 @@ final class StoryItemSetViewListComponent: Component { var emptyText: ComponentView? var emptyButton: ComponentView? + var premiumFooterText: ComponentView? + let scrollView: UIScrollView var itemLayout: ItemLayout? @@ -447,7 +451,7 @@ final class StoryItemSetViewListComponent: Component { ) }, selectionState: .none, - hasNext: index != viewListState.totalCount - 1, + hasNext: index != viewListState.totalCount - 1 || itemLayout.premiumFooterSize != nil, action: { [weak self] peer in guard let self, let component = self.component else { return @@ -513,6 +517,17 @@ final class StoryItemSetViewListComponent: Component { self.visiblePlaceholderViews.removeValue(forKey: id) } + if let premiumFooterTextView = self.premiumFooterText?.view, let premiumFooterSize = itemLayout.premiumFooterSize { + var premiumFooterTransition = transition + if premiumFooterTextView.superview == nil { + premiumFooterTransition = premiumFooterTransition.withAnimation(.none) + self.scrollView.addSubview(premiumFooterTextView) + } + let premiumFooterFrame = CGRect(origin: CGPoint(x: floor((itemLayout.contentSize.width - premiumFooterSize.width) * 0.5), y: itemLayout.itemFrame(for: itemLayout.itemCount - 1).maxY + 13.0), size: premiumFooterSize) + premiumFooterTransition.setPosition(view: premiumFooterTextView, position: premiumFooterFrame.center) + premiumFooterTransition.setBounds(view: premiumFooterTextView, bounds: CGRect(origin: CGPoint(), size: premiumFooterFrame.size)) + } + if let viewList = self.viewList, let viewListState = self.viewListState, viewListState.loadMoreToken != nil, visibleBounds.maxY >= self.scrollView.contentSize.height - 200.0 { if self.requestedLoadMoreToken != viewListState.loadMoreToken { self.requestedLoadMoreToken = viewListState.loadMoreToken @@ -737,13 +752,64 @@ final class StoryItemSetViewListComponent: Component { } } + var premiumFooterSize: CGSize? + if !component.hasPremium, let viewListState = self.viewListState, viewListState.loadMoreToken == nil, !viewListState.items.isEmpty, let views = component.storyItem.views, views.seenCount > viewListState.totalCount, component.storyItem.expirationTimestamp <= Int32(Date().timeIntervalSince1970) { + let premiumFooterText: ComponentView + if let current = self.premiumFooterText { + premiumFooterText = current + } else { + premiumFooterText = ComponentView() + self.premiumFooterText = premiumFooterText + } + + let fontSize: CGFloat = 13.0 + let body = MarkdownAttributeSet(font: Font.regular(fontSize), textColor: component.theme.list.itemSecondaryTextColor) + let bold = MarkdownAttributeSet(font: Font.semibold(fontSize), textColor: component.theme.list.itemSecondaryTextColor) + let link = MarkdownAttributeSet(font: Font.semibold(fontSize), textColor: component.theme.list.itemAccentColor) + let attributes = MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { _ in return ("URL", "") }) + + //TODO:localize + let text = "To unlock viewers' lists for expired and saved stories, subscribe to [Telegram Premium]()." + premiumFooterSize = premiumFooterText.update( + transition: .immediate, + component: AnyComponent(BalancedTextComponent( + text: .markdown(text: text, attributes: attributes), + horizontalAlignment: .center, + maximumNumberOfLines: 0, + lineSpacing: 0.2, + highlightColor: component.theme.list.itemAccentColor.withMultipliedAlpha(0.5), + highlightAction: { attributes in + if let _ = attributes[NSAttributedString.Key(rawValue: "URL")] { + return NSAttributedString.Key(rawValue: "URL") + } else { + return nil + } + }, + tapAction: { [weak self] _, _ in + guard let self, let component = self.component else { + return + } + component.openPremiumIntro() + } + )), + environment: {}, + containerSize: CGSize(width: min(320.0, availableSize.width - 16.0 * 2.0), height: 1000.0) + ) + } else { + if let premiumFooterText = self.premiumFooterText { + self.premiumFooterText = nil + premiumFooterText.view?.removeFromSuperview() + } + } + let itemLayout = ItemLayout( containerSize: CGSize(width: availableSize.width, height: visualHeight), bottomInset: component.safeInsets.bottom, topInset: navigationHeight, sideInset: sideInset, itemHeight: measureItemSize.height, - itemCount: self.viewListState?.items.count ?? 0 + itemCount: self.viewListState?.items.count ?? 0, + premiumFooterSize: premiumFooterSize ) self.itemLayout = itemLayout @@ -766,9 +832,6 @@ final class StoryItemSetViewListComponent: Component { self.scrollView.contentSize = scrollContentSize } - self.ignoreScrolling = false - self.updateScrolling(transition: transition) - if let viewListState = self.viewListState, viewListState.loadMoreToken == nil, viewListState.items.isEmpty, viewListState.totalCount == 0 { self.scrollView.isUserInteractionEnabled = false @@ -990,6 +1053,9 @@ final class StoryItemSetViewListComponent: Component { emptyButton.view?.removeFromSuperview() } } + + self.ignoreScrolling = false + self.updateScrolling(transition: transition) } }