Various improvements

This commit is contained in:
isaac
2026-04-28 19:00:04 +04:00
parent 1ae854e2e9
commit d83734eb46
9 changed files with 587 additions and 32 deletions
@@ -98,6 +98,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
case fakeGlass(Bool)
case forceClearGlass(Bool)
case debugRipple(Bool)
case debugRichText(Bool)
case browserExperiment(Bool)
case allForumsHaveTabs(Bool)
case enableReactionOverrides(Bool)
@@ -137,7 +138,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return DebugControllerSection.web.rawValue
case .keepChatNavigationStack, .skipReadHistory, .alwaysDisplayTyping, .debugRatingLayout, .crashOnSlowQueries, .crashOnMemoryPressure:
return DebugControllerSection.experiments.rawValue
case .clearTips, .resetNotifications, .crash, .fillLocalSavedMessageCache, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .resetTagHoles, .reindexUnread, .resetCacheIndex, .reindexCache, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .compressedEmojiCache, .storiesJpegExperiment, .checkSerializedData, .enableQuickReactionSwitch, .experimentalCompatibility, .enableDebugDataDisplay, .fakeGlass, .forceClearGlass, .debugRipple, .browserExperiment, .allForumsHaveTabs, .enableReactionOverrides, .restorePurchases, .disableReloginTokens, .liveStreamV2, .experimentalCallMute, .playerV2, .devRequests, .enableUpdates, .pwa, .enableLocalTranslation:
case .clearTips, .resetNotifications, .crash, .fillLocalSavedMessageCache, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .resetTagHoles, .reindexUnread, .resetCacheIndex, .reindexCache, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .compressedEmojiCache, .storiesJpegExperiment, .checkSerializedData, .enableQuickReactionSwitch, .experimentalCompatibility, .enableDebugDataDisplay, .fakeGlass, .forceClearGlass, .debugRipple, .debugRichText, .browserExperiment, .allForumsHaveTabs, .enableReactionOverrides, .restorePurchases, .disableReloginTokens, .liveStreamV2, .experimentalCallMute, .playerV2, .devRequests, .enableUpdates, .pwa, .enableLocalTranslation:
return DebugControllerSection.experiments.rawValue
case .logTranslationRecognition, .resetTranslationStates:
return DebugControllerSection.translation.rawValue
@@ -234,44 +235,46 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return 40
case .debugRipple:
return 41
case .browserExperiment:
case .debugRichText:
return 42
case .allForumsHaveTabs:
case .browserExperiment:
return 43
case .enableReactionOverrides:
case .allForumsHaveTabs:
return 44
case .restorePurchases:
case .enableReactionOverrides:
return 45
case .logTranslationRecognition:
case .restorePurchases:
return 46
case .resetTranslationStates:
case .logTranslationRecognition:
return 47
case .compressedEmojiCache:
case .resetTranslationStates:
return 48
case .storiesJpegExperiment:
case .compressedEmojiCache:
return 49
case .disableReloginTokens:
case .storiesJpegExperiment:
return 50
case .checkSerializedData:
case .disableReloginTokens:
return 51
case .enableQuickReactionSwitch:
case .checkSerializedData:
return 52
case .liveStreamV2:
case .enableQuickReactionSwitch:
return 53
case .experimentalCallMute:
case .liveStreamV2:
return 54
case .playerV2:
case .experimentalCallMute:
return 55
case .devRequests:
case .playerV2:
return 56
case .pwa:
case .devRequests:
return 57
case .enableLocalTranslation:
case .pwa:
return 58
case .enableUpdates:
case .enableLocalTranslation:
return 59
case .enableUpdates:
return 60
case let .preferredVideoCodec(index, _, _, _):
return 60 + index
return 61 + index
case .disableVideoAspectScaling:
return 100
case .enableNetworkFramework:
@@ -1305,6 +1308,16 @@ private enum DebugControllerEntry: ItemListNodeEntry {
})
}).start()
})
case let .debugRichText(value):
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Debug Text", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
settings.debugRichText = value
return PreferencesEntry(settings)
})
}).start()
})
case let .browserExperiment(value):
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Inline UI", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
@@ -1583,6 +1596,7 @@ private func debugControllerEntries(context: AccountContext?, sharedContext: Sha
entries.append(.fakeGlass(experimentalSettings.fakeGlass))
entries.append(.forceClearGlass(experimentalSettings.forceClearGlass))
entries.append(.debugRipple(experimentalSettings.debugRipple))
entries.append(.debugRichText(experimentalSettings.debugRichText))
#if DEBUG
entries.append(.browserExperiment(experimentalSettings.browserExperiment))
#else
@@ -1046,7 +1046,7 @@ public func layoutInstantPageBlock(webpage: TelegramMediaWebpage, userLocation:
}
}
public func instantPageLayoutForWebPage(_ webPage: TelegramMediaWebpage, instantPage: InstantPage?, userLocation: MediaResourceUserLocation, boundingWidth: CGFloat, safeInset: CGFloat, strings: PresentationStrings, theme: InstantPageTheme, dateTimeFormat: PresentationDateTimeFormat, webEmbedHeights: [Int : CGFloat] = [:], cachedMessageSyntaxHighlight: CachedMessageSyntaxHighlight? = nil) -> InstantPageLayout {
public func instantPageLayoutForWebPage(_ webPage: TelegramMediaWebpage, instantPage: InstantPage?, userLocation: MediaResourceUserLocation, boundingWidth: CGFloat, safeInset: CGFloat, strings: PresentationStrings, theme: InstantPageTheme, dateTimeFormat: PresentationDateTimeFormat, webEmbedHeights: [Int : CGFloat] = [:], cachedMessageSyntaxHighlight: CachedMessageSyntaxHighlight? = nil, addFeedback: Bool = true) -> InstantPageLayout {
var maybeLoadedContent: TelegramMediaWebpageLoadedContent?
if case let .Loaded(content) = webPage.content {
maybeLoadedContent = content
@@ -1088,7 +1088,7 @@ public func instantPageLayoutForWebPage(_ webPage: TelegramMediaWebpage, instant
let closingSpacing = spacingBetweenBlocks(upper: previousBlock, lower: nil)
contentSize.height += closingSpacing
if webPage.webpageId.id != 0 {
if webPage.webpageId.id != 0 && addFeedback {
let feedbackItem = InstantPageFeedbackItem(frame: CGRect(x: 0.0, y: contentSize.height, width: boundingWidth, height: 40.0), webPage: webPage)
contentSize.height += feedbackItem.frame.height
items.append(feedbackItem)
@@ -1,6 +1,7 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
private final class InstantPageTileNodeParameters: NSObject {
let tile: InstantPageTile
@@ -45,9 +46,11 @@ public final class InstantPageTileNode: ASDisplayNode {
if let parameters = parameters as? InstantPageTileNodeParameters {
if !isRasterizing {
context.setBlendMode(.copy)
context.setFillColor(parameters.backgroundColor.cgColor)
context.fill(bounds)
if !parameters.backgroundColor.alpha.isZero {
context.setBlendMode(.copy)
context.setFillColor(parameters.backgroundColor.cgColor)
context.fill(bounds)
}
}
parameters.tile.draw(context: context)
@@ -96,6 +96,7 @@ swift_library(
"//submodules/AvatarNode",
"//submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode",
"//submodules/TelegramUI/Components/PremiumAlertController",
"//submodules/TelegramUI/Components/Chat/ChatMessageRichDataBubbleContentNode",
],
visibility = [
"//visibility:public",
@@ -35,6 +35,7 @@ import ChatMessageDateAndStatusNode
import ChatMessageBubbleContentNode
import ChatHistoryEntry
import ChatMessageTextBubbleContentNode
import ChatMessageRichDataBubbleContentNode
import ChatMessageItemCommon
import ChatMessageReplyInfoNode
import ChatMessageCallBubbleContentNode
@@ -382,7 +383,11 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
if let attribute = message.attributes.first(where: { $0 is WebpagePreviewMessageAttribute }) as? WebpagePreviewMessageAttribute, attribute.leadingPreview {
result.insert((message, ChatMessageWebpageBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)), at: addedPriceInfo ? 1 : 0)
} else {
result.append((message, ChatMessageWebpageBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
if content.instantPage != nil && item.context.sharedContext.immediateExperimentalUISettings.debugRichText {
result.append((message, ChatMessageRichDataBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
} else {
result.append((message, ChatMessageWebpageBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
}
}
needReactions = false
}
@@ -1620,6 +1625,12 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
var allowFullWidth = false
let chatLocationPeerId: PeerId = item.chatLocation.peerId ?? item.content.firstMessage.id.peerId
var isInlinePage = false
if item.context.sharedContext.immediateExperimentalUISettings.debugRichText, let webpage = item.message.media.first(where: { $0 is TelegramMediaWebpage }) as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content, content.instantPage != nil {
allowFullWidth = true
isInlinePage = true
}
do {
let peerId = chatLocationPeerId
@@ -1888,6 +1899,10 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
if let subject = item.associatedData.subject, case .messageOptions = subject {
needsShareButton = false
}
if isInlinePage {
needsShareButton = false
}
var tmpWidth: CGFloat
if allowFullWidth {
@@ -1895,7 +1910,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
if (needsShareButton && !isSidePanelOpen) || isAd {
tmpWidth -= 45.0
} else {
tmpWidth -= 4.0
tmpWidth -= 3.0
}
} else {
tmpWidth = layoutConstants.bubble.maximumWidthFill.widthFor(baseWidth)
@@ -2262,7 +2277,11 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
bubbleReactions = ReactionsMessageAttribute(canViewList: false, isTags: false, reactions: [], recentPeers: [], topPeers: [])
}
if !bubbleReactions.reactions.isEmpty && !item.presentationData.isPreview {
bottomNodeMergeStatus = .Right
if incoming {
bottomNodeMergeStatus = .Both
} else {
bottomNodeMergeStatus = .Right
}
}
var currentCredibilityIcon: (EmojiStatusComponent.Content, UIColor?)?
@@ -7348,7 +7367,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
}
for contentNode in self.contentNodes {
if contentNode is ChatMessageMediaBubbleContentNode || contentNode is ChatMessageGiftBubbleContentNode || contentNode is ChatMessageWebpageBubbleContentNode || contentNode is ChatMessageInvoiceBubbleContentNode || contentNode is ChatMessageGameBubbleContentNode || contentNode is ChatMessageInstantVideoBubbleContentNode {
if contentNode is ChatMessageMediaBubbleContentNode || contentNode is ChatMessageGiftBubbleContentNode || contentNode is ChatMessageWebpageBubbleContentNode || contentNode is ChatMessageInvoiceBubbleContentNode || contentNode is ChatMessageGameBubbleContentNode || contentNode is ChatMessageInstantVideoBubbleContentNode || contentNode is ChatMessageRichDataBubbleContentNode {
contentNode.visibility = mapVisibility(effectiveMediaVisibility, boundsSize: self.bounds.size, insets: self.insets, to: contentNode)
} else {
contentNode.visibility = mapVisibility(effectiveVisibility, boundsSize: self.bounds.size, insets: self.insets, to: contentNode)
@@ -0,0 +1,27 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "ChatMessageRichDataBubbleContentNode",
module_name = "ChatMessageRichDataBubbleContentNode",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/AsyncDisplayKit",
"//submodules/Display",
"//submodules/TelegramCore",
"//submodules/Postbox",
"//submodules/SSignalKit/SwiftSignalKit",
"//submodules/AccountContext",
"//submodules/InstantPageUI",
"//submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode",
"//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon",
"//submodules/TelegramUIPreferences",
],
visibility = [
"//visibility:public",
],
)
@@ -0,0 +1,478 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
import TelegramCore
import Postbox
import SwiftSignalKit
import AccountContext
import ChatMessageBubbleContentNode
import ChatMessageItemCommon
import InstantPageUI
import TelegramUIPreferences
public class ChatMessageRichDataBubbleContentNode: ChatMessageBubbleContentNode {
public final class ContainerNode: ASDisplayNode {
}
private let containerNode: ContainerNode
private var currentLayoutTiles: [InstantPageTile] = []
private var visibleTiles: [Int: InstantPageTileNode] = [:]
private var visibleItemsWithNodes: [Int: InstantPageNode] = [:]
private var currentPageLayout: (boundingWidth: CGFloat, layout: InstantPageLayout)?
private var distanceThresholdGroupCount: [Int: Int] = [:]
private var currentLayoutItemsWithNodes: [InstantPageItem] = []
private var currentExpandedDetails: [Int : Bool]?
override public var visibility: ListViewItemNodeVisibility {
didSet {
if oldValue != self.visibility {
self.updateVisibility()
}
}
}
required public init() {
self.containerNode = ContainerNode()
self.containerNode.clipsToBounds = true
super.init()
self.addSubnode(self.containerNode)
}
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
}
override public func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) {
let currentPageLayout = self.currentPageLayout
let previousCurrentLayoutTiles = self.currentLayoutTiles
return { [weak self] item, layoutConstants, _, _, _, _ in
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 0.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)
return (contentProperties, nil, CGFloat.greatestFiniteMagnitude, { constrainedSize, position in
let suggestedBoundingWidth: CGFloat = constrainedSize.width
return (suggestedBoundingWidth, { boundingWidth in
var boundingSize = CGSize(width: boundingWidth, height: 0.0)
var pageLayout: InstantPageLayout?
var currentLayoutTiles: [InstantPageTile] = []
if let webpage = item.message.media.first(where: { $0 is TelegramMediaWebpage }) as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content, let instantPage = content.instantPage {
if let current = currentPageLayout, current.boundingWidth == boundingSize.width {
pageLayout = current.layout
currentLayoutTiles = previousCurrentLayoutTiles
} else {
let pageTheme = instantPageThemeForType(item.presentationData.theme.theme.overallDarkAppearance ? .dark : .light, settings: InstantPagePresentationSettings(
themeType: item.presentationData.theme.theme.overallDarkAppearance ? .dark : .light,
fontSize: .standard,
forceSerif: false,
autoNightMode: false,
ignoreAutoNightModeUntil: 0
))
pageLayout = instantPageLayoutForWebPage(webpage, instantPage: instantPage._parse(), userLocation: .other, boundingWidth: boundingWidth - 2.0, safeInset: 0.0, strings: item.presentationData.strings, theme: pageTheme, dateTimeFormat: item.presentationData.dateTimeFormat, webEmbedHeights: [:], addFeedback: false)
if let pageLayout {
currentLayoutTiles = instantPageTilesFromLayout(pageLayout, boundingWidth: boundingWidth)
}
}
}
if let pageLayout {
boundingSize.height = pageLayout.contentSize.height + 2.0
}
return (boundingSize, { animation, synchronousLoads, itemApply in
guard let self else {
return
}
self.item = item
self.containerNode.frame = CGRect(origin: CGPoint(x: 1.0, y: 1.0), size: CGSize(width: boundingSize.width - 2.0, height: boundingSize.height - 2.0))
if let pageLayout {
self.currentPageLayout = (boundingSize.width, pageLayout)
self.currentLayoutTiles = currentLayoutTiles
var distanceThresholdGroupCount: [Int : Int] = [:]
for item in pageLayout.items {
if item.wantsNode {
self.currentLayoutItemsWithNodes.append(item)
if let group = item.distanceThresholdGroup() {
let count: Int
if let currentCount = distanceThresholdGroupCount[Int(group)] {
count = currentCount
} else {
count = 0
}
distanceThresholdGroupCount[Int(group)] = count + 1
}
}
}
self.distanceThresholdGroupCount = distanceThresholdGroupCount
} else {
self.currentPageLayout = nil
self.currentLayoutTiles = []
self.distanceThresholdGroupCount = [:]
}
self.updateVisibility()
})
})
})
}
}
private func effectiveFrameForTile(_ tile: InstantPageTile) -> CGRect {
let layoutOrigin = tile.frame.origin
let origin = layoutOrigin
return CGRect(origin: origin, size: tile.frame.size)
}
private func updateVisibility() {
switch self.visibility {
case .none:
self.updateVisibleItems(visibleBounds: CGRect(), animated: false)
case let .visible(_, subRect):
self.updateVisibleItems(visibleBounds: subRect, animated: false)
}
}
private func updateVisibleItems(visibleBounds: CGRect, animated: Bool = false) {
guard let messageItem = self.item else {
return
}
let pageTheme = instantPageThemeForType(messageItem.presentationData.theme.theme.overallDarkAppearance ? .dark : .light, settings: InstantPagePresentationSettings(
themeType: messageItem.presentationData.theme.theme.overallDarkAppearance ? .dark : .light,
fontSize: .standard,
forceSerif: false,
autoNightMode: false,
ignoreAutoNightModeUntil: 0
))
let sourceLocation = InstantPageSourceLocation(userLocation: .other, peerType: .otherPrivate)
var visibleTileIndices = Set<Int>()
var visibleItemIndices = Set<Int>()
var topNode: ASDisplayNode?
let topTileNode = topNode
if let containerSubnodes = self.containerNode.subnodes {
for node in containerSubnodes.reversed() {
if let node = node as? InstantPageTileNode {
topNode = node
break
}
}
}
var collapseOffset: CGFloat = 0.0
collapseOffset = 0.0
let transition: ContainedViewLayoutTransition
if animated {
transition = .animated(duration: 0.3, curve: .spring)
} else {
transition = .immediate
}
var itemIndex = -1
var embedIndex = -1
var detailsIndex = -1
var previousDetailsNode: InstantPageDetailsNode?
for item in self.currentLayoutItemsWithNodes {
itemIndex += 1
if item is InstantPageWebEmbedItem {
embedIndex += 1
}
if let imageItem = item as? InstantPageImageItem, case .webpage = imageItem.media.media {
embedIndex += 1
}
if item is InstantPageDetailsItem {
detailsIndex += 1
}
var itemThreshold: CGFloat = 0.0
if let group = item.distanceThresholdGroup() {
var count: Int = 0
if let currentCount = self.distanceThresholdGroupCount[group] {
count = currentCount
}
itemThreshold = item.distanceThresholdWithGroupCount(count)
}
let itemFrame = item.frame.offsetBy(dx: 0.0, dy: -collapseOffset)
var thresholdedItemFrame = itemFrame
thresholdedItemFrame.origin.y -= itemThreshold
thresholdedItemFrame.size.height += itemThreshold * 2.0
if visibleBounds.intersects(thresholdedItemFrame) {
visibleItemIndices.insert(itemIndex)
var itemNode = self.visibleItemsWithNodes[itemIndex]
if let currentItemNode = itemNode {
if !item.matchesNode(currentItemNode) {
currentItemNode.removeFromSupernode()
self.visibleItemsWithNodes.removeValue(forKey: itemIndex)
itemNode = nil
}
}
if itemNode == nil {
let itemIndex = itemIndex
//let embedIndex = embedIndex
//let detailsIndex = detailsIndex
if let newNode = item.node(context: messageItem.context, strings: messageItem.presentationData.strings, nameDisplayOrder: messageItem.presentationData.nameDisplayOrder, theme: pageTheme, sourceLocation: sourceLocation, openMedia: { [weak self] media in
let _ = self
//self?.openMedia(media)
}, longPressMedia: { [weak self] media in
//self?.longPressMedia(media)
let _ = self
}, activatePinchPreview: { [weak self] sourceNode in
/*guard let strongSelf = self, let controller = strongSelf.controller else {
return
}
let pinchController = makePinchController(sourceNode: sourceNode, getContentAreaInScreenSpace: {
guard let strongSelf = self else {
return CGRect()
}
let localRect = CGRect(origin: CGPoint(x: 0.0, y: strongSelf.navigationBar.frame.maxY), size: CGSize(width: strongSelf.bounds.width, height: strongSelf.bounds.height - strongSelf.navigationBar.frame.maxY))
return strongSelf.view.convert(localRect, to: nil)
})
controller.window?.presentInGlobalOverlay(pinchController)*/
let _ = self
}, pinchPreviewFinished: { [weak self] itemNode in
/*guard let strongSelf = self else {
return
}
for (_, listItemNode) in strongSelf.visibleItemsWithNodes {
if let listItemNode = listItemNode as? InstantPagePeerReferenceNode {
if listItemNode.frame.intersects(itemNode.frame) && listItemNode.frame.maxY <= itemNode.frame.maxY + 2.0 {
listItemNode.layer.animateAlpha(from: 0.0, to: listItemNode.alpha, duration: 0.25)
break
}
}
}*/
let _ = self
}, openPeer: { [weak self] peerId in
let _ = self
//self?.openPeer(peerId)
}, openUrl: { [weak self] url in
let _ = self
//self?.openUrl(url)
}, updateWebEmbedHeight: { [weak self] height in
let _ = self
//self?.updateWebEmbedHeight(embedIndex, height)
}, updateDetailsExpanded: { [weak self] expanded in
let _ = self
//self?.updateDetailsExpanded(detailsIndex, expanded)
}, currentExpandedDetails: self.currentExpandedDetails, getPreloadedResource: { _ in return nil }) {
newNode.frame = itemFrame
newNode.updateLayout(size: itemFrame.size, transition: transition)
if let topNode = topNode {
self.containerNode.insertSubnode(newNode, aboveSubnode: topNode)
} else {
self.containerNode.insertSubnode(newNode, at: 0)
}
topNode = newNode
self.visibleItemsWithNodes[itemIndex] = newNode
itemNode = newNode
if let itemNode = itemNode as? InstantPageDetailsNode {
itemNode.requestLayoutUpdate = { [weak self] animated in
let _ = self
/*if let strongSelf = self {
strongSelf.updateVisibleItems(visibleBounds: strongSelf.scrollNode.view.bounds, animated: animated)
}*/
}
if let previousDetailsNode = previousDetailsNode {
if itemNode.frame.minY - previousDetailsNode.frame.maxY < 1.0 {
itemNode.previousNode = previousDetailsNode
}
}
previousDetailsNode = itemNode
}
}
} else {
if let itemNode = itemNode, itemNode.frame != itemFrame {
transition.updateFrame(node: itemNode, frame: itemFrame)
itemNode.updateLayout(size: itemFrame.size, transition: transition)
}
}
if let itemNode = itemNode as? InstantPageDetailsNode {
itemNode.updateVisibleItems(visibleBounds: visibleBounds.offsetBy(dx: -itemNode.frame.minX, dy: -itemNode.frame.minY), animated: animated)
}
}
}
topNode = topTileNode
var tileIndex = -1
for tile in self.currentLayoutTiles {
tileIndex += 1
let tileFrame = effectiveFrameForTile(tile)
var tileVisibleFrame = tileFrame
tileVisibleFrame.origin.y -= 400.0
tileVisibleFrame.size.height += 400.0 * 2.0
if tileVisibleFrame.intersects(visibleBounds) {
visibleTileIndices.insert(tileIndex)
if self.visibleTiles[tileIndex] == nil {
let tileNode = InstantPageTileNode(tile: tile, backgroundColor: .clear)
tileNode.frame = tileFrame
if let topNode = topNode {
self.containerNode.insertSubnode(tileNode, aboveSubnode: topNode)
} else {
self.containerNode.insertSubnode(tileNode, at: 0)
}
topNode = tileNode
self.visibleTiles[tileIndex] = tileNode
} else {
if let tileNode = self.visibleTiles[tileIndex] {
tileNode.update(tile: tile, backgroundColor: .clear)
if tileNode.frame != tileFrame {
transition.updateFrame(node: tileNode, frame: tileFrame)
}
}
}
}
}
var removeTileIndices: [Int] = []
for (index, tileNode) in self.visibleTiles {
if !visibleTileIndices.contains(index) {
removeTileIndices.append(index)
tileNode.removeFromSupernode()
}
}
for index in removeTileIndices {
self.visibleTiles.removeValue(forKey: index)
}
var removeItemIndices: [Int] = []
for (index, itemNode) in self.visibleItemsWithNodes {
if !visibleItemIndices.contains(index) {
removeItemIndices.append(index)
itemNode.removeFromSupernode()
} else {
var itemFrame = itemNode.frame
let itemThreshold: CGFloat = 200.0
itemFrame.origin.y -= itemThreshold
itemFrame.size.height += itemThreshold * 2.0
itemNode.updateIsVisible(visibleBounds.intersects(itemFrame))
}
}
for index in removeItemIndices {
self.visibleItemsWithNodes.removeValue(forKey: index)
}
}
override public func animateInsertion(_ currentTimestamp: Double, duration: Double) {
/*self.textNode.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
if let statusNode = self.statusNode, statusNode.alpha != 0.0 {
statusNode.layer.animateAlpha(from: 0.0, to: statusNode.alpha, duration: 0.2)
}*/
}
override public func animateAdded(_ currentTimestamp: Double, duration: Double) {
/*self.textNode.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
if let statusNode = self.statusNode, statusNode.alpha != 0.0 {
statusNode.layer.animateAlpha(from: 0.0, to: statusNode.alpha, duration: 0.2)
}*/
}
override public func animateRemoved(_ currentTimestamp: Double, duration: Double) {
/*self.textNode.textNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
if let statusNode = self.statusNode, statusNode.alpha != 0.0 {
statusNode.layer.animateAlpha(from: statusNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false)
}*/
}
override public func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction {
if case .tap = gesture {
} else {
if let item = self.item, let subject = item.associatedData.subject, case .messageOptions = subject {
return ChatMessageBubbleContentTapAction(content: .none)
}
}
/*func makeActivate(_ urlRange: NSRange?) -> (() -> Promise<Bool>?)? {
return { [weak self] in
guard let self else {
return nil
}
let promise = Promise<Bool>()
self.linkProgressDisposable?.dispose()
if self.linkProgressRange != nil {
self.linkProgressRange = nil
self.updateLinkProgressState()
}
self.linkProgressDisposable = (promise.get() |> deliverOnMainQueue).startStrict(next: { [weak self] value in
guard let self else {
return
}
let updatedRange: NSRange? = value ? urlRange : nil
if self.linkProgressRange != updatedRange {
self.linkProgressRange = updatedRange
self.updateLinkProgressState()
}
})
return promise
}
}*/
return ChatMessageBubbleContentTapAction(content: .none)
}
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
return super.hitTest(point, with: event)
}
override public func updateTouchesAtPoint(_ point: CGPoint?) {
}
override public func updateSearchTextHighlightState(text: String?, messages: [MessageIndex]?) {
}
override public func willUpdateIsExtractedToContextPreview(_ value: Bool) {
}
override public func updateIsExtractedToContextPreview(_ value: Bool) {
}
override public func reactionTargetView(value: MessageReaction.Reaction) -> UIView? {
/*if let statusNode = self.statusNode, !statusNode.isHidden {
return statusNode.reactionView(value: value)
}*/
return nil
}
override public func messageEffectTargetView() -> UIView? {
/*if let statusNode = self.statusNode, !statusNode.isHidden {
return statusNode.messageEffectTargetView()
}*/
return nil
}
override public func getStatusNode() -> ASDisplayNode? {
return nil
//return self.statusNode
}
}
@@ -822,6 +822,7 @@ private final class TextStyleEditSheetComponent: Component {
theme: theme,
strings: environmentValue.strings,
actionTitle: actionButtonTitle,
displayProgress: self.isActionInProgress,
action: isMainActionEnabled ? performMainAction : nil
)
),
@@ -998,17 +999,20 @@ private final class ActionButtonsComponent: Component {
let theme: PresentationTheme
let strings: PresentationStrings
let actionTitle: String
let displayProgress: Bool
let action: (() -> Void)?
init(
theme: PresentationTheme,
strings: PresentationStrings,
actionTitle: String,
displayProgress: Bool,
action: (() -> Void)?
) {
self.theme = theme
self.strings = strings
self.actionTitle = actionTitle
self.displayProgress = displayProgress
self.action = action
}
@@ -1022,6 +1026,9 @@ private final class ActionButtonsComponent: Component {
if lhs.actionTitle != rhs.actionTitle {
return false
}
if lhs.displayProgress != rhs.displayProgress {
return false
}
if (lhs.action == nil) != (rhs.action == nil) {
return false
}
@@ -1070,7 +1077,7 @@ private final class ActionButtonsComponent: Component {
))
),
isEnabled: component.action != nil,
displaysProgress: false,
displaysProgress: component.displayProgress,
action: { [weak self] in
guard let self, let component = self.component else {
return
@@ -72,6 +72,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
public var enablePWA: Bool
public var forceClearGlass: Bool
public var debugRipple: Bool
public var debugRichText: Bool
public static var defaultSettings: ExperimentalUISettings {
return ExperimentalUISettings(
@@ -121,7 +122,8 @@ public struct ExperimentalUISettings: Codable, Equatable {
enableUpdates: false,
enablePWA: false,
forceClearGlass: false,
debugRipple: false
debugRipple: false,
debugRichText: false
)
}
@@ -172,7 +174,8 @@ public struct ExperimentalUISettings: Codable, Equatable {
enableUpdates: Bool,
enablePWA: Bool,
forceClearGlass: Bool,
debugRipple: Bool
debugRipple: Bool,
debugRichText: Bool
) {
self.keepChatNavigationStack = keepChatNavigationStack
self.skipReadHistory = skipReadHistory
@@ -221,6 +224,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
self.enablePWA = enablePWA
self.forceClearGlass = forceClearGlass
self.debugRipple = debugRipple
self.debugRichText = debugRichText
}
public init(from decoder: Decoder) throws {
@@ -273,6 +277,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
self.enablePWA = try container.decodeIfPresent(Bool.self, forKey: "enablePWA") ?? false
self.forceClearGlass = try container.decodeIfPresent(Bool.self, forKey: "forceClearGlass") ?? false
self.debugRipple = try container.decodeIfPresent(Bool.self, forKey: "debugRipple") ?? false
self.debugRichText = try container.decodeIfPresent(Bool.self, forKey: "debugRichText") ?? false
}
public func encode(to encoder: Encoder) throws {
@@ -325,6 +330,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
try container.encodeIfPresent(self.enablePWA, forKey: "enablePWA")
try container.encodeIfPresent(self.forceClearGlass, forKey: "forceClearGlass")
try container.encodeIfPresent(self.debugRipple, forKey: "debugRipple")
try container.encodeIfPresent(self.debugRichText, forKey: "debugRichText")
}
}