Merge commit 'ba58f0c694f46409768f4c6ab090cb133eed2b0b'

This commit is contained in:
Isaac 2025-10-02 00:15:18 +08:00
commit 27159968f4
2 changed files with 54 additions and 16 deletions

View File

@ -200,6 +200,10 @@ public final class MultilineTextWithEntitiesComponent: Component {
return false return false
} }
public var hasRTL: Bool {
return self.textNode.cachedLayout?.hasRTL ?? false
}
public func updateVisibility(_ isVisible: Bool) { public func updateVisibility(_ isVisible: Bool) {
self.textNode.visibility = isVisible self.textNode.visibility = isVisible
} }

View File

@ -129,13 +129,18 @@ final class MessageItemComponent: Component {
self.container.transform = .identity self.container.transform = .identity
superview.addSubview(self.container) superview.addSubview(self.container)
let hasRTL = (self.text.view as? MultilineTextWithEntitiesComponent.View)?.hasRTL ?? false
let direction: CGFloat = hasRTL ? -1.0 : 1.0
let initialSize = self.background.frame.size
self.container.addSubview(textSnapshotView) self.container.addSubview(textSnapshotView)
transition.setAlpha(view: textSnapshotView, alpha: 0.0, completion: { _ in transition.setAlpha(view: textSnapshotView, alpha: 0.0, completion: { _ in
textSnapshotView.removeFromSuperview() textSnapshotView.removeFromSuperview()
}) })
transition.setPosition(view: textSnapshotView, position: CGPoint(x: textSnapshotView.center.x + 71.0, y: textSnapshotView.center.y))
let initialSize = self.background.frame.size let additionalOffset = hasRTL ? globalFrame.size.width - initialSize.width : 0.0
transition.setPosition(view: textSnapshotView, position: CGPoint(x: textSnapshotView.center.x + 71.0 * direction - additionalOffset, y: textSnapshotView.center.y))
self.background.update(size: globalFrame.size, cornerRadius: cornerRadius, isDark: true, tintColor: .init(kind: .custom, color: glassColor), transition: .immediate) self.background.update(size: globalFrame.size, cornerRadius: cornerRadius, isDark: true, tintColor: .init(kind: .custom, color: glassColor), transition: .immediate)
self.background.update(size: initialSize, cornerRadius: 18.0, isDark: true, tintColor: .init(kind: .custom, color: glassColor), transition: transition) self.background.update(size: initialSize, cornerRadius: 18.0, isDark: true, tintColor: .init(kind: .custom, color: glassColor), transition: transition)
@ -151,7 +156,7 @@ final class MessageItemComponent: Component {
}) })
if let textView = self.text.view { if let textView = self.text.view {
transition.animatePosition(view: textView, from: CGPoint(x: -71.0, y: 0.0), to: .zero, additive: true) transition.animatePosition(view: textView, from: CGPoint(x: -71.0 * direction, y: 0.0), to: .zero, additive: true)
transition.animateAlpha(view: textView, from: 0.0, to: 1.0) transition.animateAlpha(view: textView, from: 0.0, to: 1.0)
} }
transition.animateAlpha(view: self.avatarNode.view, from: 0.0, to: 1.0) transition.animateAlpha(view: self.avatarNode.view, from: 0.0, to: 1.0)
@ -184,8 +189,6 @@ final class MessageItemComponent: Component {
let iconSpacing: CGFloat = 10.0 let iconSpacing: CGFloat = 10.0
let rightInset: CGFloat = component.isNotification ? 15.0 : 13.0 let rightInset: CGFloat = component.isNotification ? 15.0 : 13.0
let avatarFrame = CGRect(origin: CGPoint(x: avatarInset, y: avatarInset), size: avatarSize)
var peerName = "" var peerName = ""
if !component.isNotification, case let .peer(peer) = component.icon { if !component.isNotification, case let .peer(peer) = component.icon {
peerName = peer.compactDisplayTitle peerName = peer.compactDisplayTitle
@ -244,7 +247,7 @@ final class MessageItemComponent: Component {
} else { } else {
let textWithAppliedEntities = stringWithAppliedEntities(text, entities: entities, baseColor: textColor, linkColor: linkColor, baseFont: textFont, linkFont: textFont, boldFont: boldTextFont, italicFont: italicFont, boldItalicFont: boldItalicTextFont, fixedFont: monospaceFont, blockQuoteFont: textFont, message: nil, entityFiles: self.entityFiles).mutableCopy() as! NSMutableAttributedString let textWithAppliedEntities = stringWithAppliedEntities(text, entities: entities, baseColor: textColor, linkColor: linkColor, baseFont: textFont, linkFont: textFont, boldFont: boldTextFont, italicFont: italicFont, boldItalicFont: boldItalicTextFont, fixedFont: monospaceFont, blockQuoteFont: textFont, message: nil, entityFiles: self.entityFiles).mutableCopy() as! NSMutableAttributedString
if !peerName.isEmpty { if !peerName.isEmpty {
textWithAppliedEntities.insert(NSAttributedString(string: peerName + " ", font: boldTextFont, textColor: textColor), at: 0) textWithAppliedEntities.insert(NSAttributedString(string: "\u{2066}\(peerName)\u{2069} ", font: boldTextFont, textColor: textColor), at: 0)
} }
attributedText = textWithAppliedEntities attributedText = textWithAppliedEntities
} }
@ -276,7 +279,12 @@ final class MessageItemComponent: Component {
containerSize: CGSize(width: availableSize.width - avatarInset - avatarSize.width - spacing - rightInset, height: .greatestFiniteMagnitude) containerSize: CGSize(width: availableSize.width - avatarInset - avatarSize.width - spacing - rightInset, height: .greatestFiniteMagnitude)
) )
let size = CGSize(width: avatarInset + avatarSize.width + spacing + textSize.width + rightInset, height: max(minimalHeight, textSize.height + 15.0)) let hasRTL = (self.text.view as? MultilineTextWithEntitiesComponent.View)?.hasRTL ?? false
let size = CGSize(
width: avatarInset + avatarSize.width + spacing + textSize.width + rightInset,
height: max(minimalHeight, textSize.height + 15.0)
)
switch component.icon { switch component.icon {
case let .peer(peer): case let .peer(peer):
@ -302,10 +310,11 @@ final class MessageItemComponent: Component {
displayDimensions: avatarSize displayDimensions: avatarSize
) )
} }
let avatarFrame = CGRect(origin: CGPoint(x: avatarInset, y: avatarInset), size: avatarSize)
if self.avatarNode.bounds.isEmpty { if self.avatarNode.bounds.isEmpty {
self.avatarNode.frame = avatarFrame self.avatarNode.frame = mappedFrame(avatarFrame, containerSize: size, hasRTL: hasRTL)
} else { } else {
transition.setFrame(view: self.avatarNode.view, frame: avatarFrame) transition.setFrame(view: self.avatarNode.view, frame: mappedFrame(avatarFrame, containerSize: size, hasRTL: hasRTL))
} }
self.avatarNode.isHidden = false self.avatarNode.isHidden = false
case let .icon(iconName): case let .icon(iconName):
@ -315,12 +324,18 @@ final class MessageItemComponent: Component {
environment: {}, environment: {},
containerSize: CGSize(width: 44.0, height: 44.0) containerSize: CGSize(width: 44.0, height: 44.0)
) )
let iconFrame = CGRect(origin: CGPoint(x: avatarInset, y: floorToScreenPixels((size.height - iconSize.height) / 2.0)), size: iconSize) let iconFrame = CGRect(
origin: CGPoint(
x: avatarInset,
y: floorToScreenPixels((size.height - iconSize.height) / 2.0)
),
size: iconSize
)
if let iconView = self.icon.view { if let iconView = self.icon.view {
if iconView.superview == nil { if iconView.superview == nil {
self.container.addSubview(iconView) self.container.addSubview(iconView)
} }
transition.setFrame(view: iconView, frame: iconFrame) transition.setFrame(view: iconView, frame: mappedFrame(iconFrame, containerSize: size, hasRTL: hasRTL))
} }
self.avatarNode.isHidden = true self.avatarNode.isHidden = true
case let .animation(animationName): case let .animation(animationName):
@ -338,23 +353,42 @@ final class MessageItemComponent: Component {
environment: {}, environment: {},
containerSize: CGSize(width: 40.0, height: 40.0) containerSize: CGSize(width: 40.0, height: 40.0)
) )
let iconFrame = CGRect(origin: CGPoint(x: avatarInset - 3.0, y: floorToScreenPixels((size.height - iconSize.height) / 2.0)), size: iconSize) let iconFrame = CGRect(
origin: CGPoint(
x: avatarInset - 3.0,
y: floorToScreenPixels((size.height - iconSize.height) / 2.0)
),
size: iconSize
)
if let iconView = self.icon.view as? LottieComponent.View { if let iconView = self.icon.view as? LottieComponent.View {
if iconView.superview == nil { if iconView.superview == nil {
self.container.addSubview(iconView) self.container.addSubview(iconView)
iconView.playOnce() iconView.playOnce()
} }
transition.setFrame(view: iconView, frame: iconFrame) transition.setFrame(view: iconView, frame: mappedFrame(iconFrame, containerSize: size, hasRTL: hasRTL))
} }
self.avatarNode.isHidden = true self.avatarNode.isHidden = true
} }
let textFrame = CGRect(origin: CGPoint(x: avatarInset + avatarSize.width + spacing, y: floorToScreenPixels((size.height - textSize.height) / 2.0)), size: textSize) func mappedFrame(_ frame: CGRect, containerSize: CGSize, hasRTL: Bool) -> CGRect {
return CGRect(
origin: CGPoint(x: hasRTL ? containerSize.width - frame.minX - frame.width : frame.minX, y: frame.origin.y),
size: frame.size
)
}
let textFrame = CGRect(
origin: CGPoint(
x: avatarInset + avatarSize.width + spacing,
y: floorToScreenPixels((size.height - textSize.height) / 2.0)
),
size: textSize
)
if let textView = self.text.view { if let textView = self.text.view {
if textView.superview == nil { if textView.superview == nil {
self.container.addSubview(textView) self.container.addSubview(textView)
} }
transition.setFrame(view: textView, frame: textFrame) transition.setFrame(view: textView, frame: mappedFrame(textFrame, containerSize: size, hasRTL: hasRTL))
} }
transition.setFrame(view: self.container, frame: CGRect(origin: CGPoint(), size: size)) transition.setFrame(view: self.container, frame: CGRect(origin: CGPoint(), size: size))