mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Stories
This commit is contained in:
parent
a46c372d68
commit
848e342991
52
submodules/Postbox/Sources/StoryView.swift
Normal file
52
submodules/Postbox/Sources/StoryView.swift
Normal file
@ -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
|
||||
}
|
||||
}
|
@ -45,6 +45,7 @@ public enum PostboxViewKey: Hashable {
|
||||
case storyItems(peerId: PeerId)
|
||||
case storyExpirationTimeItems
|
||||
case peerStoryStats(peerIds: Set<PeerId>)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -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 }
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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<UITouch>, 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
|
||||
|
@ -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)
|
||||
|
@ -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<Int>? {
|
||||
@ -256,6 +258,8 @@ final class StoryItemSetViewListComponent: Component {
|
||||
var emptyText: ComponentView<Empty>?
|
||||
var emptyButton: ComponentView<Empty>?
|
||||
|
||||
var premiumFooterText: ComponentView<Empty>?
|
||||
|
||||
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<Empty>
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user