Various improvements

This commit is contained in:
isaac
2026-04-28 16:58:04 +04:00
parent 51a7348db6
commit 027ac77ad7
19 changed files with 263 additions and 86 deletions
@@ -2958,7 +2958,7 @@ extension Customoji {
if let cg = (image as UIImage).cgImage { return cg }
var rendered: CGImage?
let work = { rendered = renderCGImage(image as! UIImage) }
let work = { rendered = renderCGImage(image) }
if Thread.isMainThread {
work()
} else {
@@ -580,7 +580,7 @@ final class AvatarEditorScreenComponent: Component {
if installed {
return .complete()
} else {
return context.engine.stickers.addStickerPackInteractively(info: info._parse(), items: items)
return context.engine.stickers.addStickerPackInteractively(info: info._parse(), items: items) |> map { _ in return Void() }
}
case .fetching:
break
@@ -718,7 +718,7 @@ final class AvatarEditorScreenComponent: Component {
if installed {
return .complete()
} else {
return context.engine.stickers.addStickerPackInteractively(info: info._parse(), items: items)
return context.engine.stickers.addStickerPackInteractively(info: info._parse(), items: items) |> map { _ in return Void() }
}
case .fetching:
break
@@ -329,7 +329,7 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
messageText = updatingMedia.text
}
if !messageText.isEmpty || isUnsupportedMedia || isStoryWithText {
if !messageText.isEmpty || message.attributes.contains(where: { $0 is TypingDraftMessageAttribute }) || isUnsupportedMedia || isStoryWithText {
if !skipText {
if case .group = item.content, !isFile {
messageWithCaptionToAdd = (message, itemAttributes)
@@ -2262,7 +2262,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
bubbleReactions = ReactionsMessageAttribute(canViewList: false, isTags: false, reactions: [], recentPeers: [], topPeers: [])
}
if !bubbleReactions.reactions.isEmpty && !item.presentationData.isPreview {
bottomNodeMergeStatus = .Both
bottomNodeMergeStatus = .Right
}
var currentCredibilityIcon: (EmojiStatusComponent.Content, UIColor?)?
@@ -6259,7 +6259,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
if strongSelf.backgroundNode.supernode != nil, let backgroundView = strongSelf.backgroundNode.view.snapshotContentTree(unhide: true) {
let backgroundContainer = UIView()
let backdropView = strongSelf.backgroundWallpaperNode.view.snapshotContentTree(unhide: true)
let backdropView = strongSelf.backgroundWallpaperNode.view.snapshotContentTree(unhide: true, keepPortals: true)
if let backdropView = backdropView {
let backdropFrame = strongSelf.backgroundWallpaperNode.layer.convert(strongSelf.backgroundWallpaperNode.bounds, to: strongSelf.backgroundNode.layer)
backdropView.frame = backdropFrame
@@ -698,6 +698,15 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
}
}
var hasDraft = false
if item.message.attributes.contains(where: { $0 is TypingDraftMessageAttribute }) {
hasDraft = true
}
var hadDraft = false
if let previousItem, previousItem.message.attributes.contains(where: { $0 is TypingDraftMessageAttribute }) {
hadDraft = true
}
let textInsets = UIEdgeInsets(top: 2.0, left: 2.0, bottom: 5.0, right: 2.0)
let (textLayout, textApply) = textLayout(InteractiveTextNodeLayoutArguments(
attributedString: attributedText,
@@ -712,18 +721,10 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
displayContentsUnderSpoilers: displayContentsUnderSpoilers.value,
customTruncationToken: customTruncationToken,
expandedBlocks: expandedBlockIds,
computeCharacterRects: true
computeCharacterRects: true,
minWidth: (attributedText.string.isEmpty && hasDraft) ? 40.0 : nil
))
var hasDraft = false
if item.message.attributes.contains(where: { $0 is TypingDraftMessageAttribute }) {
hasDraft = true
}
var hadDraft = false
if let previousItem, previousItem.message.attributes.contains(where: { $0 is TypingDraftMessageAttribute }) {
hadDraft = true
}
var maxGlyphCount = currentMaxGlyphCount
if maxGlyphCount == nil && (hasDraft || hadDraft) {
maxGlyphCount = previousGlyphCount
@@ -173,6 +173,10 @@ public final class ChatTextInputActionButtonsNode: ASDisplayNode, ChatSendMessag
public var customSendColor: UIColor?
public var isSendDisabled: Bool = false
private var slowmodeProgressTimestamp: (duration: Int32, timestamp: Int32)?
private var slowmodeProgressTimer: Foundation.Timer?
private var slowmodeProgressLayer: SimpleShapeLayer?
public init(context: AccountContext, presentationInterfaceState: ChatPresentationInterfaceState, presentationContext: ChatPresentationContext?, presentController: @escaping (ViewController) -> Void) {
self.context = context
self.presentationContext = presentationContext
@@ -255,6 +259,10 @@ public final class ChatTextInputActionButtonsNode: ASDisplayNode, ChatSendMessag
}
}
deinit {
self.slowmodeProgressTimer?.invalidate()
}
override public func didLoad() {
super.didLoad()
@@ -357,8 +365,72 @@ public final class ChatTextInputActionButtonsNode: ASDisplayNode, ChatSendMessag
transition.updateFrame(layer: self.micButton.layer, frame: CGRect(origin: CGPoint(), size: size))
self.micButton.layoutItems()
var sendSlowmodeTimerTimestamp: (duration: Int32, timestamp: Int32)?
if let slowmodeState = interfaceState.slowmodeState {
switch slowmodeState.variant {
case .pendingMessages:
break
case let .timestamp(timeoutTimestamp):
sendSlowmodeTimerTimestamp = (slowmodeState.timeout, timeoutTimestamp)
}
}
let sendButtonBackgroundFrame = CGRect(origin: CGPoint(), size: innerSize).insetBy(dx: 3.0, dy: 3.0)
transition.updateFrame(view: self.sendButtonBackgroundView, frame: sendButtonBackgroundFrame)
let slowmodeInset: CGFloat = 4.0
self.slowmodeProgressTimestamp = sendSlowmodeTimerTimestamp
if sendSlowmodeTimerTimestamp != nil {
let slowmodeProgressLayer: SimpleShapeLayer
var slowmodeProgressTransition = transition
if let current = self.slowmodeProgressLayer {
slowmodeProgressLayer = current
} else {
slowmodeProgressTransition = .immediate
slowmodeProgressLayer = SimpleShapeLayer()
self.slowmodeProgressLayer = slowmodeProgressLayer
self.sendButtonBackgroundView.layer.superlayer?.insertSublayer(slowmodeProgressLayer, below: self.sendButtonBackgroundView.layer)
slowmodeProgressLayer.fillColor = nil
slowmodeProgressLayer.lineWidth = 2.0
slowmodeProgressLayer.lineCap = .round
}
slowmodeProgressLayer.strokeColor = (self.customSendColor ?? interfaceState.theme.chat.inputPanel.panelControlAccentColor).cgColor
if slowmodeProgressLayer.bounds.size != sendButtonBackgroundFrame.size {
let pathFrame = CGRect(origin: CGPoint(), size: sendButtonBackgroundFrame.size).insetBy(dx: 2.0, dy: 2.0)
slowmodeProgressLayer.path = UIBezierPath(roundedRect: pathFrame, cornerRadius: pathFrame.height * 0.5).cgPath
}
slowmodeProgressTransition.updateFrame(layer: slowmodeProgressLayer, frame: sendButtonBackgroundFrame)
if self.slowmodeProgressTimer == nil {
self.slowmodeProgressTimer = Foundation.Timer.scheduledTimer(withTimeInterval: 1.0 / 60.0, repeats: true, block: { [weak self] _ in
guard let self else {
return
}
self.updateSlowmodeProgress()
})
}
self.updateSlowmodeProgress()
} else {
if let slowmodeProgressLayer = self.slowmodeProgressLayer {
self.slowmodeProgressLayer = nil
slowmodeProgressLayer.removeFromSuperlayer()
}
if let slowmodeProgressTimer = self.slowmodeProgressTimer {
self.slowmodeProgressTimer = nil
slowmodeProgressTimer.invalidate()
}
}
ComponentTransition(transition).setPosition(view: self.sendButtonBackgroundView, position: sendButtonBackgroundFrame.center)
ComponentTransition(transition).setBounds(view: self.sendButtonBackgroundView, bounds: CGRect(origin: CGPoint(), size: sendButtonBackgroundFrame.size))
var sendButtonBackgroundScale: CGFloat = 1.0
if sendSlowmodeTimerTimestamp != nil, let image = self.sendButtonBackgroundView.image {
sendButtonBackgroundScale = (image.size.height - slowmodeInset * 2.0) / image.size.height
}
transition.updateTransformScale(layer: self.sendButtonBackgroundView.layer, scale: sendButtonBackgroundScale)
if self.isSendDisabled {
transition.updateTintColor(view: self.sendButtonBackgroundView, color: interfaceState.theme.chat.inputPanel.panelControlAccentColor.withMultiplied(hue: 1.0, saturation: 0.0, brightness: 0.5).withMultipliedAlpha(0.25))
@@ -441,6 +513,18 @@ public final class ChatTextInputActionButtonsNode: ASDisplayNode, ChatSendMessag
return innerSize
}
private func updateSlowmodeProgress() {
guard let slowmodeProgressLayer = self.slowmodeProgressLayer, let slowmodeProgressTimestamp = self.slowmodeProgressTimestamp else {
return
}
let timestamp = Date().timeIntervalSince1970
let timeout = max(0.0, Double(slowmodeProgressTimestamp.timestamp) - timestamp)
let fraction = timeout / max(0.1, Double(slowmodeProgressTimestamp.duration))
slowmodeProgressLayer.strokeEnd = CGFloat(fraction)
}
public func updateAccessibility() {
self.accessibilityTraits = .button
if !self.micButton.alpha.isZero {
@@ -2308,6 +2308,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
}
}
}
sendActionButtonsSize = self.sendActionButtons.updateLayout(size: CGSize(width: 40.0, height: minimalHeight), isMediaInputExpanded: isMediaInputExpanded, showTitle: showTitle, currentMessageEffectId: presentationInterfaceState.interfaceState.sendMessageEffect, transition: transition, interfaceState: presentationInterfaceState)
mediaActionButtonsSize = self.mediaActionButtons.updateLayout(size: CGSize(width: 40.0, height: minimalHeight), isMediaInputExpanded: isMediaInputExpanded, showTitle: false, currentMessageEffectId: presentationInterfaceState.interfaceState.sendMessageEffect, transition: transition, interfaceState: presentationInterfaceState)
}
@@ -5600,6 +5601,9 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
}
public func frameForInputActionButton() -> CGRect? {
if !self.sendActionButtons.alpha.isZero && self.sendActionButtons.frame.minX < self.bounds.width {
return self.sendActionButtons.frame.insetBy(dx: 0.0, dy: -4.0).offsetBy(dx: -3.0, dy: 0.0)
}
if !self.mediaActionButtons.alpha.isZero && self.mediaActionButtons.frame.minX < self.bounds.width {
return self.mediaActionButtons.frame.insetBy(dx: 0.0, dy: -4.0).offsetBy(dx: -3.0, dy: 0.0)
}
@@ -1371,7 +1371,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
if installed {
return .complete()
} else {
return context.engine.stickers.addStickerPackInteractively(info: info._parse(), items: items)
return context.engine.stickers.addStickerPackInteractively(info: info._parse(), items: items) |> map { _ in return Void() }
}
case .fetching:
break
@@ -309,7 +309,7 @@ final class StickerAttachmentScreenComponent: Component {
if installed {
return .complete()
} else {
return context.engine.stickers.addStickerPackInteractively(info: info._parse(), items: items)
return context.engine.stickers.addStickerPackInteractively(info: info._parse(), items: items) |> map { _ in return Void() }
}
case .fetching:
break
@@ -719,7 +719,7 @@ final class StickerAttachmentScreenComponent: Component {
if installed {
return .complete()
} else {
return context.engine.stickers.addStickerPackInteractively(info: info._parse(), items: items)
return context.engine.stickers.addStickerPackInteractively(info: info._parse(), items: items) |> map { _ in return Void() }
}
case .fetching:
break
@@ -134,17 +134,18 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
private final class ItemContentNode: ASDisplayNode {
let offsetContainerNode: ASDisplayNode
var containingItem: ContextControllerTakeViewInfo.ContainingItem
var animateClippingFromContentAreaInScreenSpace: CGRect?
var storedGlobalFrame: CGRect?
var storedGlobalBoundsFrame: CGRect?
var presentationScale: CGFloat = 1.0
init(containingItem: ContextControllerTakeViewInfo.ContainingItem) {
self.offsetContainerNode = ASDisplayNode()
self.containingItem = containingItem
super.init()
self.addSubnode(self.offsetContainerNode)
}
@@ -633,6 +634,21 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
}
let contentNodeValue = ItemContentNode(containingItem: takeInfo.containingItem)
contentNodeValue.animateClippingFromContentAreaInScreenSpace = takeInfo.contentAreaInScreenSpace
// Mirror any ancestor scale on the source (e.g. a sheet's container transform) onto the offset
// container so the extracted contents render at the same visual size as in-place without this
// they pop to 1:1 when reparented into the unscaled overlay window.
let sourceView = takeInfo.containingItem.view
let modeledWidth = sourceView.bounds.width
if modeledWidth > 0.001 {
let visualWidth = sourceView.convert(sourceView.bounds, to: nil).width
let detectedScale = visualWidth / modeledWidth
if abs(detectedScale - 1.0) > 0.001 {
contentNodeValue.presentationScale = detectedScale
contentNodeValue.offsetContainerNode.layer.transform = CATransform3DMakeScale(detectedScale, detectedScale, 1.0)
}
}
self.scrollNode.insertSubnode(contentNodeValue, aboveSubnode: self.actionsContainerNode)
self.itemContentNode = contentNodeValue
itemContentNode = contentNodeValue
@@ -1165,6 +1181,13 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
if let contentNode = itemContentNode {
var contentFrame = CGRect(origin: CGPoint(x: contentParentGlobalFrame.minX + contentRect.minX - contentNode.containingItem.contentRect.minX, y: contentRect.minY - contentNode.containingItem.contentRect.minY + contentVerticalOffset + additionalVisibleOffsetY), size: contentNode.containingItem.view.bounds.size)
// contentRect.minY was derived from storedGlobalFrame.maxY (visual) minus contentRect.height (modeled);
// when an ancestor scale is in effect those don't cancel cleanly, leaving a (1 - scale) * (cy + ch)
// residue that pulls the content upward. Add it back so the content lands at the source's visual Y.
if contentNode.presentationScale != 1.0 {
let cr = contentNode.containingItem.contentRect
contentFrame.origin.y += (1.0 - contentNode.presentationScale) * (cr.minY + cr.height)
}
if case let .extracted(extracted) = self.source {
if extracted.adjustContentHorizontally {
contentFrame.origin.x = combinedActionsFrame.minX
@@ -1542,6 +1565,12 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
switch result {
case .default, .custom:
animationInContentYDistance = currentContentLocalFrame.minY - currentContentScreenFrame.minY
// Same modeled-vs-visual mismatch as the static contentFrame compensation: contentRect.minY (used by
// currentContentLocalFrame) was derived with modeled height while the source-side reference uses visual
// height, leaving a `ch * (1 - scale)` residue that animates the content downward on dismiss.
if let contentNode = itemContentNode, contentNode.presentationScale != 1.0 {
animationInContentYDistance += contentNode.containingItem.contentRect.height * (1.0 - contentNode.presentationScale)
}
case .dismissWithoutContent:
animationInContentYDistance = 0.0
if let contentNode = itemContentNode {
@@ -339,24 +339,24 @@ public final class EmojiStatusSelectionController: ViewController {
self.componentHost = ComponentView<Empty>()
self.componentShadowLayer = SimpleLayer()
self.componentShadowLayer.shadowOpacity = 0.12
self.componentShadowLayer.shadowOpacity = 0.35
self.componentShadowLayer.shadowColor = UIColor(white: 0.0, alpha: 1.0).cgColor
self.componentShadowLayer.shadowOffset = CGSize(width: 0.0, height: 2.0)
self.componentShadowLayer.shadowRadius = 16.0
self.componentShadowLayer.shadowOffset = CGSize(width: 0.0, height: 10.0)
self.componentShadowLayer.shadowRadius = 30.0
self.cloudLayer0 = SimpleLayer()
self.cloudShadowLayer0 = SimpleLayer()
self.cloudShadowLayer0.shadowOpacity = 0.12
self.cloudShadowLayer0.shadowColor = UIColor(white: 0.0, alpha: 1.0).cgColor
self.cloudShadowLayer0.shadowOffset = CGSize(width: 0.0, height: 2.0)
self.cloudShadowLayer0.shadowRadius = 16.0
self.cloudShadowLayer0.shadowOpacity = self.componentShadowLayer.shadowOpacity
self.cloudShadowLayer0.shadowColor = self.componentShadowLayer.shadowColor
self.cloudShadowLayer0.shadowOffset = self.componentShadowLayer.shadowOffset
self.cloudShadowLayer0.shadowRadius = self.componentShadowLayer.shadowRadius
self.cloudLayer1 = SimpleLayer()
self.cloudShadowLayer1 = SimpleLayer()
self.cloudShadowLayer1.shadowOpacity = 0.12
self.cloudShadowLayer1.shadowColor = UIColor(white: 0.0, alpha: 1.0).cgColor
self.cloudShadowLayer1.shadowOffset = CGSize(width: 0.0, height: 2.0)
self.cloudShadowLayer1.shadowRadius = 16.0
self.cloudShadowLayer1.shadowOpacity = self.componentShadowLayer.shadowOpacity
self.cloudShadowLayer1.shadowColor = self.componentShadowLayer.shadowColor
self.cloudShadowLayer1.shadowOffset = self.componentShadowLayer.shadowOffset
self.cloudShadowLayer1.shadowRadius = self.componentShadowLayer.shadowRadius
super.init()
@@ -973,16 +973,12 @@ public final class EmojiStatusSelectionController: ViewController {
if self.presentationData.theme.overallDarkAppearance {
listBackgroundColor = self.presentationData.theme.list.itemBlocksBackgroundColor
separatorColor = self.presentationData.theme.list.itemBlocksSeparatorColor
self.componentShadowLayer.shadowOpacity = 0.32
self.cloudShadowLayer0.shadowOpacity = 0.32
self.cloudShadowLayer1.shadowOpacity = 0.32
} else {
listBackgroundColor = self.presentationData.theme.list.plainBackgroundColor
separatorColor = self.presentationData.theme.list.itemPlainSeparatorColor.withMultipliedAlpha(0.5)
self.componentShadowLayer.shadowOpacity = 0.12
self.cloudShadowLayer0.shadowOpacity = 0.12
self.cloudShadowLayer1.shadowOpacity = 0.12
}
self.cloudShadowLayer0.shadowOpacity = self.componentShadowLayer.shadowOpacity
self.cloudShadowLayer1.shadowOpacity = self.componentShadowLayer.shadowOpacity
self.cloudLayer0.backgroundColor = listBackgroundColor.cgColor
self.cloudLayer1.backgroundColor = listBackgroundColor.cgColor
@@ -1241,6 +1241,9 @@ public extension EmojiPagerContentComponent {
tintMode: tintMode
)
case let .text(text):
if !areUnicodeEmojiEnabled {
continue
}
resultItem = EmojiPagerContentComponent.Item(
animationData: nil,
content: .staticEmoji(text),
@@ -298,6 +298,7 @@ public final class InteractiveTextNodeLayoutArguments {
public let customTruncationToken: ((UIFont, Bool) -> NSAttributedString?)?
public let expandedBlocks: Set<Int>
public let computeCharacterRects: Bool
public let minWidth: CGFloat?
public init(
attributedString: NSAttributedString?,
@@ -318,7 +319,8 @@ public final class InteractiveTextNodeLayoutArguments {
displayContentsUnderSpoilers: Bool = false,
customTruncationToken: ((UIFont, Bool) -> NSAttributedString?)? = nil,
expandedBlocks: Set<Int> = Set(),
computeCharacterRects: Bool = false
computeCharacterRects: Bool = false,
minWidth: CGFloat? = nil
) {
self.attributedString = attributedString
self.backgroundColor = backgroundColor
@@ -339,6 +341,7 @@ public final class InteractiveTextNodeLayoutArguments {
self.customTruncationToken = customTruncationToken
self.expandedBlocks = expandedBlocks
self.computeCharacterRects = computeCharacterRects
self.minWidth = minWidth
}
public func withAttributedString(_ attributedString: NSAttributedString?) -> InteractiveTextNodeLayoutArguments {
@@ -361,7 +364,8 @@ public final class InteractiveTextNodeLayoutArguments {
displayContentsUnderSpoilers: self.displayContentsUnderSpoilers,
customTruncationToken: self.customTruncationToken,
expandedBlocks: self.expandedBlocks,
computeCharacterRects: self.computeCharacterRects
computeCharacterRects: self.computeCharacterRects,
minWidth: self.minWidth
)
}
}
@@ -424,6 +428,7 @@ public final class InteractiveTextNodeLayout: NSObject {
fileprivate let textStroke: (UIColor, CGFloat)?
public let displayContentsUnderSpoilers: Bool
fileprivate let expandedBlocks: Set<Int>
public let minWidth: CGFloat?
fileprivate init(
attributedString: NSAttributedString?,
@@ -447,7 +452,8 @@ public final class InteractiveTextNodeLayout: NSObject {
textShadowBlur: CGFloat?,
textStroke: (UIColor, CGFloat)?,
displayContentsUnderSpoilers: Bool,
expandedBlocks: Set<Int>
expandedBlocks: Set<Int>,
minWidth: CGFloat?
) {
self.attributedString = attributedString
self.maximumNumberOfLines = maximumNumberOfLines
@@ -471,6 +477,7 @@ public final class InteractiveTextNodeLayout: NSObject {
self.textStroke = textStroke
self.displayContentsUnderSpoilers = displayContentsUnderSpoilers
self.expandedBlocks = expandedBlocks
self.minWidth = minWidth
}
func withUpdatedDisplayContentsUnderSpoilers(_ displayContentsUnderSpoilers: Bool) -> InteractiveTextNodeLayout {
@@ -496,7 +503,8 @@ public final class InteractiveTextNodeLayout: NSObject {
textShadowBlur: self.textShadowBlur,
textStroke: self.textStroke,
displayContentsUnderSpoilers: displayContentsUnderSpoilers,
expandedBlocks: self.expandedBlocks
expandedBlocks: self.expandedBlocks,
minWidth: self.minWidth
)
}
@@ -1102,6 +1110,12 @@ public final class InteractiveTextNodeLayout: NSObject {
}
height += self.insets.top + self.insets.bottom + 2.0
if let minWidth = self.minWidth {
width = max(width, minWidth)
trailingLineWidth = max(trailingLineWidth, minWidth)
}
return TextNodeLayout.LayoutInfo(
size: CGSize(width: width, height: ceil(height)),
trailingLineWidth: trailingLineWidth
@@ -1474,7 +1488,8 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn
displayContentsUnderSpoilers: Bool,
customTruncationToken: ((UIFont, Bool) -> NSAttributedString?)?,
expandedBlocks: Set<Int>,
computeCharacterRects: Bool = false
computeCharacterRects: Bool = false,
minWidth: CGFloat?
) -> InteractiveTextNodeLayout {
let blockQuoteLeftInset: CGFloat = 9.0
let blockQuoteRightInset: CGFloat = 0.0
@@ -2046,6 +2061,10 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn
size.width += insets.left + insets.right
size.height += insets.top + insets.bottom
if let minWidth {
size.width = max(size.width, minWidth)
}
return InteractiveTextNodeLayout(
attributedString: attributedString,
maximumNumberOfLines: maximumNumberOfLines,
@@ -2068,16 +2087,17 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn
textShadowBlur: textShadowBlur,
textStroke: textStroke,
displayContentsUnderSpoilers: displayContentsUnderSpoilers,
expandedBlocks: expandedBlocks
expandedBlocks: expandedBlocks,
minWidth: minWidth
)
}
static func calculateLayout(attributedString: NSAttributedString?, minimumNumberOfLines: Int, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, backgroundColor: UIColor?, constrainedSize: CGSize, alignment: NSTextAlignment, verticalAlignment: TextVerticalAlignment, lineSpacingFactor: CGFloat, cutout: TextNodeCutout?, insets: UIEdgeInsets, lineColor: UIColor?, textShadowColor: UIColor?, textShadowBlur: CGFloat?, textStroke: (UIColor, CGFloat)?, displayContentsUnderSpoilers: Bool, customTruncationToken: ((UIFont, Bool) -> NSAttributedString?)?, expandedBlocks: Set<Int>, computeCharacterRects: Bool = false) -> InteractiveTextNodeLayout {
static func calculateLayout(attributedString: NSAttributedString?, minimumNumberOfLines: Int, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, backgroundColor: UIColor?, constrainedSize: CGSize, alignment: NSTextAlignment, verticalAlignment: TextVerticalAlignment, lineSpacingFactor: CGFloat, cutout: TextNodeCutout?, insets: UIEdgeInsets, lineColor: UIColor?, textShadowColor: UIColor?, textShadowBlur: CGFloat?, textStroke: (UIColor, CGFloat)?, displayContentsUnderSpoilers: Bool, customTruncationToken: ((UIFont, Bool) -> NSAttributedString?)?, expandedBlocks: Set<Int>, computeCharacterRects: Bool = false, minWidth: CGFloat? = nil) -> InteractiveTextNodeLayout {
guard let attributedString else {
return InteractiveTextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, explicitAlignment: alignment, resolvedAlignment: alignment, verticalAlignment: verticalAlignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(), rawTextSize: CGSize(), truncated: false, firstLineOffset: 0.0, segments: [], backgroundColor: backgroundColor, lineColor: lineColor, textShadowColor: textShadowColor, textShadowBlur: textShadowBlur, textStroke: textStroke, displayContentsUnderSpoilers: displayContentsUnderSpoilers, expandedBlocks: expandedBlocks)
return InteractiveTextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, explicitAlignment: alignment, resolvedAlignment: alignment, verticalAlignment: verticalAlignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(), rawTextSize: CGSize(), truncated: false, firstLineOffset: 0.0, segments: [], backgroundColor: backgroundColor, lineColor: lineColor, textShadowColor: textShadowColor, textShadowBlur: textShadowBlur, textStroke: textStroke, displayContentsUnderSpoilers: displayContentsUnderSpoilers, expandedBlocks: expandedBlocks, minWidth: minWidth)
}
return calculateLayoutV2(attributedString: attributedString, minimumNumberOfLines: minimumNumberOfLines, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, backgroundColor: backgroundColor, constrainedSize: constrainedSize, alignment: alignment, verticalAlignment: verticalAlignment, lineSpacingFactor: lineSpacingFactor, cutout: cutout, insets: insets, lineColor: lineColor, textShadowColor: textShadowColor, textShadowBlur: textShadowBlur, textStroke: textStroke, displayContentsUnderSpoilers: displayContentsUnderSpoilers, customTruncationToken: customTruncationToken, expandedBlocks: expandedBlocks, computeCharacterRects: computeCharacterRects)
return calculateLayoutV2(attributedString: attributedString, minimumNumberOfLines: minimumNumberOfLines, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, backgroundColor: backgroundColor, constrainedSize: constrainedSize, alignment: alignment, verticalAlignment: verticalAlignment, lineSpacingFactor: lineSpacingFactor, cutout: cutout, insets: insets, lineColor: lineColor, textShadowColor: textShadowColor, textShadowBlur: textShadowBlur, textStroke: textStroke, displayContentsUnderSpoilers: displayContentsUnderSpoilers, customTruncationToken: customTruncationToken, expandedBlocks: expandedBlocks, computeCharacterRects: computeCharacterRects, minWidth: minWidth)
}
private func updateContentItems(arguments: ApplyArguments) {
@@ -2222,7 +2242,7 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn
return { arguments in
var layout: InteractiveTextNodeLayout
if let existingLayout = existingLayout, existingLayout.constrainedSize == arguments.constrainedSize && existingLayout.maximumNumberOfLines == arguments.maximumNumberOfLines && existingLayout.truncationType == arguments.truncationType && existingLayout.cutout == arguments.cutout && existingLayout.explicitAlignment == arguments.alignment && existingLayout.lineSpacing.isEqual(to: arguments.lineSpacing) && existingLayout.expandedBlocks == arguments.expandedBlocks {
if let existingLayout = existingLayout, existingLayout.constrainedSize == arguments.constrainedSize && existingLayout.maximumNumberOfLines == arguments.maximumNumberOfLines && existingLayout.truncationType == arguments.truncationType && existingLayout.cutout == arguments.cutout && existingLayout.explicitAlignment == arguments.alignment && existingLayout.lineSpacing.isEqual(to: arguments.lineSpacing) && existingLayout.expandedBlocks == arguments.expandedBlocks && existingLayout.minWidth == arguments.minWidth {
let stringMatch: Bool
var colorMatch: Bool = true
@@ -2250,10 +2270,10 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn
layout = layout.withUpdatedDisplayContentsUnderSpoilers(arguments.displayContentsUnderSpoilers)
}
} else {
layout = InteractiveTextNode.calculateLayout(attributedString: arguments.attributedString, minimumNumberOfLines: arguments.minimumNumberOfLines, maximumNumberOfLines: arguments.maximumNumberOfLines, truncationType: arguments.truncationType, backgroundColor: arguments.backgroundColor, constrainedSize: arguments.constrainedSize, alignment: arguments.alignment, verticalAlignment: arguments.verticalAlignment, lineSpacingFactor: arguments.lineSpacing, cutout: arguments.cutout, insets: arguments.insets, lineColor: arguments.lineColor, textShadowColor: arguments.textShadowColor, textShadowBlur: arguments.textShadowBlur, textStroke: arguments.textStroke, displayContentsUnderSpoilers: arguments.displayContentsUnderSpoilers, customTruncationToken: arguments.customTruncationToken, expandedBlocks: arguments.expandedBlocks, computeCharacterRects: arguments.computeCharacterRects)
layout = InteractiveTextNode.calculateLayout(attributedString: arguments.attributedString, minimumNumberOfLines: arguments.minimumNumberOfLines, maximumNumberOfLines: arguments.maximumNumberOfLines, truncationType: arguments.truncationType, backgroundColor: arguments.backgroundColor, constrainedSize: arguments.constrainedSize, alignment: arguments.alignment, verticalAlignment: arguments.verticalAlignment, lineSpacingFactor: arguments.lineSpacing, cutout: arguments.cutout, insets: arguments.insets, lineColor: arguments.lineColor, textShadowColor: arguments.textShadowColor, textShadowBlur: arguments.textShadowBlur, textStroke: arguments.textStroke, displayContentsUnderSpoilers: arguments.displayContentsUnderSpoilers, customTruncationToken: arguments.customTruncationToken, expandedBlocks: arguments.expandedBlocks, computeCharacterRects: arguments.computeCharacterRects, minWidth: arguments.minWidth)
}
} else {
layout = InteractiveTextNode.calculateLayout(attributedString: arguments.attributedString, minimumNumberOfLines: arguments.minimumNumberOfLines, maximumNumberOfLines: arguments.maximumNumberOfLines, truncationType: arguments.truncationType, backgroundColor: arguments.backgroundColor, constrainedSize: arguments.constrainedSize, alignment: arguments.alignment, verticalAlignment: arguments.verticalAlignment, lineSpacingFactor: arguments.lineSpacing, cutout: arguments.cutout, insets: arguments.insets, lineColor: arguments.lineColor, textShadowColor: arguments.textShadowColor, textShadowBlur: arguments.textShadowBlur, textStroke: arguments.textStroke, displayContentsUnderSpoilers: arguments.displayContentsUnderSpoilers, customTruncationToken: arguments.customTruncationToken, expandedBlocks: arguments.expandedBlocks, computeCharacterRects: arguments.computeCharacterRects)
layout = InteractiveTextNode.calculateLayout(attributedString: arguments.attributedString, minimumNumberOfLines: arguments.minimumNumberOfLines, maximumNumberOfLines: arguments.maximumNumberOfLines, truncationType: arguments.truncationType, backgroundColor: arguments.backgroundColor, constrainedSize: arguments.constrainedSize, alignment: arguments.alignment, verticalAlignment: arguments.verticalAlignment, lineSpacingFactor: arguments.lineSpacing, cutout: arguments.cutout, insets: arguments.insets, lineColor: arguments.lineColor, textShadowColor: arguments.textShadowColor, textShadowBlur: arguments.textShadowBlur, textStroke: arguments.textStroke, displayContentsUnderSpoilers: arguments.displayContentsUnderSpoilers, customTruncationToken: arguments.customTruncationToken, expandedBlocks: arguments.expandedBlocks, computeCharacterRects: arguments.computeCharacterRects, minWidth: arguments.minWidth)
}
let node = maybeNode ?? InteractiveTextNode()
@@ -1073,17 +1073,18 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar {
}
if self.titleNode.view.superview != nil {
let titleSize = self.titleNode.updateLayout(CGSize(width: max(1.0, size.width - max(leftTitleInset, rightTitleInset) * 2.0), height: nominalHeight))
var transition = transition
if self.titleNode.frame.width.isZero {
transition = .immediate
}
self.titleNode.alpha = 1.0
do {
var transition = transition
if self.titleNode.frame.width.isZero {
transition = .immediate
}
self.titleNode.alpha = 1.0
let titleOffset: CGFloat = 0.0
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: contentVerticalOrigin + titleOffset + floorToScreenPixels((nominalHeight - titleSize.height) / 2.0)), size: titleSize))
let titleSize = self.titleNode.updateLayout(CGSize(width: max(1.0, size.width - leftTitleInset - rightTitleInset), height: nominalHeight))
if titleSize.width <= size.width - max(leftTitleInset, rightTitleInset) * 2.0 {
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: contentVerticalOrigin + floorToScreenPixels((nominalHeight - titleSize.height) / 2.0)), size: titleSize))
} else {
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: leftTitleInset + floor((size.width - leftTitleInset - rightTitleInset - titleSize.width) / 2.0), y: contentVerticalOrigin + floorToScreenPixels((nominalHeight - titleSize.height) / 2.0)), size: titleSize))
}
}
@@ -1350,7 +1350,10 @@ final class PeerInfoHeaderNode: ASDisplayNode {
let textSideInset: CGFloat = 36.0
let expandedAvatarHeight: CGFloat = expandedAvatarListSize.height
let titleConstrainedSize = CGSize(width: width - textSideInset * 2.0 - (isPremium || isVerified || isFake ? 20.0 : 0.0), height: .greatestFiniteMagnitude)
var titleConstrainedSize = CGSize(width: width - textSideInset * 2.0 - (isPremium || isVerified || isFake ? 20.0 : 0.0), height: .greatestFiniteMagnitude)
if self.navigationButtonContainer.rightButtonNodes.count > 1 {
titleConstrainedSize.width -= 60.0
}
let titleNodeLayout = self.titleNode.updateLayout(text: titleStringText, states: [
TitleNodeStateRegular: MultiScaleTextState(attributes: titleAttributes, constrainedSize: titleConstrainedSize),
@@ -470,7 +470,8 @@ private final class ItemComponent: Component {
guard let component = self.component else {
return
}
let containerFrame = CGRect(origin: CGPoint(x: floor((size.width - measuredSize.width) * 0.5), y: floor((measuredSize.height - size.height) * 0.5)), size: measuredSize)
let containerFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - measuredSize.width) * 0.5), y: floor((measuredSize.height - size.height) * 0.5)), size: measuredSize)
let contentRect = CGRect(origin: CGPoint(x: 0.0, y: -5.0 - 4.0), size: CGSize(width: size.width + 0.0, height: size.height + 5.0 + 3.0 + 6.0))
transition.setFrame(view: self.backgroundContainer, frame: contentRect)
self.backgroundContainer.update(size: contentRect.size, isDark: component.theme.overallDarkAppearance, transition: transition)
@@ -8740,6 +8740,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
getAnimatedTransitionSource: ((String) -> UIView?)? = nil,
completion: @escaping () -> Void = {}
) {
var animateTransition = true
if let validLayout = self.chatDisplayNode.validLayout?.0 {
if validLayout.metrics.widthClass != .compact {
animateTransition = false
}
}
self.enqueueMediaMessageDisposable.set((legacyAssetPickerEnqueueMessages(context: self.context, account: self.context.account, signals: signals!, originalMediaReference: originalMediaReference)
|> deliverOnMainQueue).startStrict(next: { [weak self] items in
guard let strongSelf = self else {
@@ -8766,6 +8773,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if shouldDivert {
skipAddingTransitions = true
}
if !animateTransition {
skipAddingTransitions = true
}
for item in items {
var message = item.message
@@ -4058,8 +4058,8 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
func frameForInputActionButton() -> CGRect? {
if let textInputPanelNode = self.textInputPanelNode, self.inputPanelNode === textInputPanelNode {
return textInputPanelNode.frameForInputActionButton().flatMap {
return $0.offsetBy(dx: textInputPanelNode.frame.minX, dy: textInputPanelNode.frame.minY)
return textInputPanelNode.frameForInputActionButton().flatMap { rect in
return self.view.convert(rect, from: textInputPanelNode.view)
}
}
return nil
@@ -18,6 +18,9 @@ import ChatControllerInteraction
import ChatContextResultPeekContent
import ChatInputContextPanelNode
import BatchVideoRendering
import GlassBackgroundComponent
import ComponentFlow
import ComponentDisplayAdapters
private struct ChatContextResultStableId: Hashable {
let result: ChatContextResult
@@ -82,6 +85,9 @@ private func preparedTransition(from fromEntries: [HorizontalListContextResultsC
}
final class HorizontalListContextResultsChatInputContextPanelNode: ChatInputContextPanelNode {
private let backgroundContainerView: GlassBackgroundContainerView
private let backgroundView: GlassBackgroundView
private let listClippingView: UIView
private let listView: ListView
private var currentExternalResults: ChatContextResultCollection?
private var currentProcessedResults: ChatContextResultCollection?
@@ -95,9 +101,14 @@ final class HorizontalListContextResultsChatInputContextPanelNode: ChatInputCont
private let batchVideoContext: QueueLocalObject<BatchVideoRenderingContext>
override init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize, chatPresentationContext: ChatPresentationContext) {
self.backgroundContainerView = GlassBackgroundContainerView()
self.backgroundView = GlassBackgroundView()
self.backgroundContainerView.contentView.addSubview(self.backgroundView)
self.listClippingView = UIView()
self.listClippingView.clipsToBounds = true
self.listView = ListViewImpl()
self.listView.isOpaque = true
self.listView.backgroundColor = theme.list.plainBackgroundColor
self.listView.isOpaque = false
self.listView.transform = CATransform3DMakeRotation(-CGFloat(CGFloat.pi / 2.0), 0.0, 0.0, 1.0)
self.listView.isHidden = true
self.listView.accessibilityPageScrolledString = { row, count in
@@ -111,9 +122,12 @@ final class HorizontalListContextResultsChatInputContextPanelNode: ChatInputCont
super.init(context: context, theme: theme, strings: strings, fontSize: fontSize, chatPresentationContext: chatPresentationContext)
self.isOpaque = false
self.clipsToBounds = true
self.clipsToBounds = false
self.layer.allowsGroupOpacity = true
self.addSubnode(self.listView)
self.view.addSubview(self.backgroundContainerView)
self.listClippingView.addSubview(self.listView.view)
self.backgroundView.contentView.addSubview(self.listClippingView)
self.listView.displayedItemRangeChanged = { [weak self] displayedRange, opaqueTransactionState in
if let strongSelf = self, let state = opaqueTransactionState as? HorizontalListContextResultsOpaqueState {
@@ -361,12 +375,26 @@ final class HorizontalListContextResultsChatInputContextPanelNode: ChatInputCont
override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) {
let listHeight: CGFloat = 105.0
let sideInset: CGFloat = 8.0
let innerInset: CGFloat = 4.0
let cornerRadius: CGFloat = 8.0
let innerRadius: CGFloat = cornerRadius - innerInset
self.listView.bounds = CGRect(x: 0.0, y: 0.0, width: listHeight, height: size.width)
let listFrame = CGRect(x: sideInset, y: size.height - bottomInset - 8.0 - listHeight, width: size.width - sideInset * 2.0, height: listHeight)
let transformedListFrame = CGSize(width: listFrame.height, height: listFrame.width).centered(in: listFrame)
self.listView.bounds = CGRect(origin: CGPoint(), size: transformedListFrame.size)
transition.updatePosition(node: self.listView, position: CGRect(origin: CGPoint(x: -innerInset, y: -innerInset), size: listFrame.size).center)
//transition.updateFrame(node: self.listView, frame: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height))
transition.updateFrame(view: self.listClippingView, frame: CGRect(origin: CGPoint(), size: listFrame.size).insetBy(dx: innerInset, dy: innerInset))
self.listClippingView.layer.cornerRadius = innerRadius
transition.updatePosition(node: self.listView, position: CGPoint(x: size.width / 2.0, y: size.height - bottomInset - 8.0 - listHeight / 2.0))
let backgroundContainerInset: CGFloat = 32.0
let backgroundContainerFrame = listFrame.insetBy(dx: -backgroundContainerInset, dy: -backgroundContainerInset)
transition.updateFrame(view: self.backgroundContainerView, frame: backgroundContainerFrame)
self.backgroundContainerView.update(size: backgroundContainerFrame.size, isDark: interfaceState.theme.overallDarkAppearance, transition: ComponentTransition(transition))
transition.updateFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: listFrame.size).offsetBy(dx: backgroundContainerInset, dy: backgroundContainerInset))
self.backgroundView.update(size: listFrame.size, cornerRadius: cornerRadius, isDark: interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .panel), transition: ComponentTransition(transition))
var insets = UIEdgeInsets()
insets.top = leftInset
@@ -391,22 +419,18 @@ final class HorizontalListContextResultsChatInputContextPanelNode: ChatInputCont
}
override func animateOut(completion: @escaping () -> Void) {
/*let position = self.listView.layer.position
self.listView.layer.animatePosition(from: position, to: CGPoint(x: position.x, y: position.y + self.listView.bounds.size.width), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in
completion()
})*/
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { _ in
ComponentTransition.easeInOut(duration: 0.3).setAlpha(view: self.backgroundContainerView, alpha: 0.01, completion: { _ in
completion()
})
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let listViewBounds = self.listView.bounds
let listViewPosition = self.listView.position
let listViewFrame = CGRect(origin: CGPoint(x: listViewPosition.x - listViewBounds.height / 2.0, y: listViewPosition.y - listViewBounds.width / 2.0), size: CGSize(width: listViewBounds.height, height: listViewBounds.width))
if !listViewFrame.contains(point) {
guard let result = super.hitTest(point, with: event) else {
return nil
}
return super.hitTest(point, with: event)
if result === self.view {
return nil
}
return result
}
}
+3 -2
View File
@@ -29,15 +29,16 @@ cd ..
if [ "$ARCH" = "arm64" ]; then
IOS_PLATFORMDIR="$(xcode-select -p)/Platforms/iPhoneOS.platform"
IOS_SYSROOT=($IOS_PLATFORMDIR/Developer/SDKs/iPhoneOS*.sdk)
export CFLAGS="-arch arm64 --target=arm64-apple-ios13.0 -miphoneos-version-min=13.0"
export CFLAGS="-arch arm64 --target=arm64-apple-ios13.0 -miphoneos-version-min=13.0 -w"
elif [ "$ARCH" = "sim_arm64" ]; then
IOS_PLATFORMDIR="$(xcode-select -p)/Platforms/iPhoneSimulator.platform"
IOS_SYSROOT=($IOS_PLATFORMDIR/Developer/SDKs/iPhoneSimulator*.sdk)
export CFLAGS="-arch arm64 --target=arm64-apple-ios13.0-simulator -miphonesimulator-version-min=13.0"
export CFLAGS="-arch arm64 --target=arm64-apple-ios13.0-simulator -miphonesimulator-version-min=13.0 -w"
else
echo "Unsupported architecture $ARCH"
exit 1
fi
export CXXFLAGS="$CFLAGS"
# Common build steps
mkdir build