Various improvements

This commit is contained in:
Ilya Laktyushin 2025-07-02 22:03:34 +02:00
parent 9a414b9f09
commit 72c466ca13
5 changed files with 133 additions and 45 deletions

View File

@ -57,6 +57,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
private let ribbonTextNode: TextNode
private let creatorButtonNode: HighlightTrackingButtonNode
private var creatorButtonBackgroundNode: NavigationBackgroundNode?
private let creatorButtonTitleNode: TextNode
private var shimmerEffectNode: ShimmerEffectForegroundNode?
@ -644,6 +645,10 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
if case let .unique(uniqueGift) = gift {
isStarGift = true
if let releasedBy = gift.releasedBy, let peer = item.message.peers[releasedBy], let addressName = peer.addressName {
creatorButtonTitle = item.presentationData.strings.Notification_StarGift_ReleasedBy("**@\(addressName)**").string
}
let isSelfGift = item.message.id.peerId == item.context.account.peerId
let authorName: String
if isUpgrade {
@ -831,9 +836,9 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
}
), textAlignment: .center)
let (creatorButtonTitleLayout, creatorButtonTitleApply) = makeCreatorButtonTitleLayout(TextNodeLayoutArguments(attributedString: creatorButtonAttributedString, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: giftSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
let (creatorButtonTitleLayout, creatorButtonTitleApply) = makeCreatorButtonTitleLayout(TextNodeLayoutArguments(attributedString: creatorButtonAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .middle, constrainedSize: CGSize(width: giftSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
if !creatorButtonTitle.isEmpty {
if modelTitle == nil && !creatorButtonTitle.isEmpty {
textSpacing += 28.0
}
@ -841,6 +846,10 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
if let _ = modelTitle {
giftSize.height += 70.0
if !creatorButtonTitle.isEmpty {
giftSize.height += 28.0
}
}
if !buttonTitle.isEmpty {
@ -920,9 +929,11 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
strongSelf.animationNode.isHidden = isStoryEntity
strongSelf.buttonNode.isHidden = buttonTitle.isEmpty
strongSelf.buttonNode.isUserInteractionEnabled = !item.presentationData.isPreview
strongSelf.buttonTitleNode.isHidden = buttonTitle.isEmpty
strongSelf.creatorButtonNode.isHidden = creatorButtonTitle.isEmpty
strongSelf.creatorButtonNode.isUserInteractionEnabled = !item.presentationData.isPreview
strongSelf.creatorButtonTitleNode.isHidden = creatorButtonTitle.isEmpty
if strongSelf.item == nil && !isStoryEntity {
@ -964,7 +975,6 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
strongSelf.labelNode.isHidden = !hasServiceMessage
strongSelf.buttonNode.backgroundColor = overlayColor
strongSelf.creatorButtonNode.backgroundColor = overlayColor
strongSelf.animationNode.updateLayout(size: iconSize)
strongSelf.placeholderNode.frame = animationFrame
@ -985,22 +995,45 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
let _ = moreApply()
let _ = creatorButtonTitleApply()
let labelFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((boundingWidth - labelLayout.size.width) / 2.0), y: 2.0), size: labelLayout.size)
strongSelf.labelNode.frame = labelFrame
let titleFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - titleLayout.size.width) / 2.0) , y: mediaBackgroundFrame.minY + 151.0), size: titleLayout.size)
strongSelf.titleNode.frame = titleFrame
let clippingTextFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - subtitleLayout.size.width) / 2.0), y: titleFrame.maxY + textSpacing), size: CGSize(width: subtitleLayout.size.width, height: clippedTextHeight))
var attributesOffsetY: CGFloat = 0.0
let creatorButtonSize = CGSize(width: creatorButtonTitleLayout.size.width + 18.0, height: 18.0)
let creatorButtonOriginY = titleFrame.maxY + 4.0
let creatorButtonOriginY = modelTitle == nil ? titleFrame.maxY + 4.0 : clippingTextFrame.maxY + 5.0
let creatorButtonFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - creatorButtonSize.width) / 2.0), y: creatorButtonOriginY), size: creatorButtonSize)
if !creatorButtonTitle.isEmpty {
attributesOffsetY += creatorButtonSize.height + 10.0
}
if !strongSelf.creatorButtonNode.isHidden, modelTitle != nil {
strongSelf.creatorButtonNode.backgroundColor = .clear
let creatorButtonBackgroundNode: NavigationBackgroundNode
if let current = strongSelf.creatorButtonBackgroundNode {
creatorButtonBackgroundNode = current
} else {
creatorButtonBackgroundNode = NavigationBackgroundNode(color: UIColor(rgb: 0x000000, alpha: 0.12), enableBlur: true, enableSaturation: false)
strongSelf.creatorButtonNode.insertSubnode(creatorButtonBackgroundNode, at: 0)
strongSelf.creatorButtonBackgroundNode = creatorButtonBackgroundNode
}
creatorButtonBackgroundNode.update(size: creatorButtonFrame.size, cornerRadius: 9.0, transition: .immediate)
} else {
strongSelf.creatorButtonNode.backgroundColor = overlayColor
if let creatorButtonBackgroundNode = strongSelf.creatorButtonBackgroundNode {
strongSelf.creatorButtonBackgroundNode = nil
creatorButtonBackgroundNode.removeFromSupernode()
}
}
strongSelf.creatorButtonTitleNode.frame = CGRect(origin: CGPoint(x: 9.0, y: 1.0), size: creatorButtonTitleLayout.size)
animation.animator.updateFrame(layer: strongSelf.creatorButtonNode.layer, frame: CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - creatorButtonSize.width) / 2.0), y: creatorButtonOriginY), size: creatorButtonSize), completion: nil)
let clippingTextFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - subtitleLayout.size.width) / 2.0), y: titleFrame.maxY + textSpacing), size: CGSize(width: subtitleLayout.size.width, height: clippedTextHeight))
animation.animator.updateFrame(layer: strongSelf.creatorButtonNode.layer, frame: creatorButtonFrame, completion: nil)
let subtitleFrame = CGRect(origin: .zero, size: subtitleLayout.size)
strongSelf.subtitleNode.textNode.frame = subtitleFrame
@ -1084,7 +1117,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
}
let _ = titleApply()
titleTextNode.frame = CGRect(
origin: CGPoint(x: titleMaxX - titleLayout.size.width, y: clippingTextFrame.maxY + yOffset),
origin: CGPoint(x: titleMaxX - titleLayout.size.width, y: clippingTextFrame.maxY + attributesOffsetY + yOffset),
size: titleLayout.size
)
}
@ -1094,7 +1127,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
}
let _ = valueApply()
valueTextNode.frame = CGRect(
origin: CGPoint(x: valueMinX, y: clippingTextFrame.maxY + yOffset),
origin: CGPoint(x: valueMinX, y: clippingTextFrame.maxY + attributesOffsetY + yOffset),
size: valueLayout.size
)
}
@ -1125,7 +1158,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
var buttonSize = CGSize(width: buttonTitleLayout.size.width + 38.0, height: 34.0)
var buttonOriginY = clippingTextFrame.maxY + 10.0
if modelTitleLayoutAndApply != nil {
buttonOriginY = clippingTextFrame.maxY + 80.0
buttonOriginY = clippingTextFrame.maxY + attributesOffsetY + 80.0
}
strongSelf.buttonTitleNode.frame = CGRect(origin: CGPoint(x: 19.0, y: 8.0), size: buttonTitleLayout.size)

View File

@ -294,7 +294,7 @@ final class ChatGiftPreviewItemNode: ListViewItemNode {
}
var contentSize = CGSize(width: params.width, height: 4.0 + 4.0)
contentSize.height = 346.0
contentSize.height = 370.0
insets = itemListNeighborsGroupedInsets(neighbors, params)
if params.width <= 320.0 {
insets.top = 0.0

View File

@ -559,25 +559,42 @@ final class GiftSetupScreenComponent: Component {
self.hideName = true
}
var releasedBy: EnginePeer.Id?
if case let .starGift(gift, true) = component.subject, gift.upgradeStars != nil {
self.includeUpgrade = true
}
if case let .starGift(gift, _) = component.subject {
releasedBy = gift.releasedBy
}
let _ = (component.context.engine.data.get(
TelegramEngine.EngineData.Item.Peer.Peer(id: component.peerId),
TelegramEngine.EngineData.Item.Peer.Peer(id: component.context.account.peerId),
var peerIds: [EnginePeer.Id] = [
component.context.account.peerId,
component.peerId
]
if let releasedBy {
peerIds.append(releasedBy)
}
let _ = combineLatest(queue: Queue.mainQueue(),
component.context.engine.data.get(EngineDataMap(
peerIds.map { peerId -> TelegramEngine.EngineData.Item.Peer.Peer in
return TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)
}
)),
component.context.engine.data.get(
TelegramEngine.EngineData.Item.Peer.SendPaidMessageStars(id: component.peerId)
)
|> deliverOnMainQueue).start(next: { [weak self] peer, accountPeer, sendPaidMessageStars in
).start(next: { [weak self] peers, sendPaidMessageStars in
guard let self else {
return
}
if let peer {
self.peerMap[peer.id] = peer
var peersMap: [EnginePeer.Id: EnginePeer] = [:]
for (peerId, maybePeer) in peers {
if let peer = maybePeer {
peersMap[peerId] = peer
}
if let accountPeer {
self.peerMap[accountPeer.id] = accountPeer
}
self.peerMap = peersMap
self.sendPaidMessageStars = sendPaidMessageStars
self.state?.updated()
@ -847,7 +864,7 @@ final class GiftSetupScreenComponent: Component {
let giftConfiguration = GiftConfiguration.with(appConfiguration: component.context.currentAppConfiguration.with { $0 })
var introSectionItems: [AnyComponentWithIdentity<Empty>] = []
introSectionItems.append(AnyComponentWithIdentity(id: introSectionItems.count, component: AnyComponent(Rectangle(color: .clear, height: 346.0, tag: self.introPlaceholderTag))))
introSectionItems.append(AnyComponentWithIdentity(id: introSectionItems.count, component: AnyComponent(Rectangle(color: .clear, height: 370.0, tag: self.introPlaceholderTag))))
if self.sendPaidMessageStars == nil {
introSectionItems.append(AnyComponentWithIdentity(id: introSectionItems.count, component: AnyComponent(ListMultilineTextFieldItemComponent(
@ -965,6 +982,7 @@ final class GiftSetupScreenComponent: Component {
if let accountPeer = self.peerMap[component.context.account.peerId] {
var upgradeStars: Int64?
let subject: ChatGiftPreviewItem.Subject
var releasedBy: EnginePeer.Id?
switch component.subject {
case let .premium(product):
if self.payWithStars, let starsPrice = product.starsPrice {
@ -976,12 +994,16 @@ final class GiftSetupScreenComponent: Component {
case let .starGift(gift, _):
subject = .starGift(gift: gift)
upgradeStars = gift.upgradeStars
releasedBy = gift.releasedBy
}
var peers: [EnginePeer] = [accountPeer]
if let peer = self.peerMap[component.peerId] {
peers.append(peer)
}
if let releasedBy, let peer = self.peerMap[releasedBy] {
peers.append(peer)
}
let introContentSize = self.introContent.update(
transition: transition,

View File

@ -1535,7 +1535,7 @@ private final class GiftViewSheetContent: CombinedComponent {
let title = Child(MultilineTextComponent.self)
let subtitle = Child(MultilineTextComponent.self)
let descriptionButton = Child(RoundedRectangle.self)
let descriptionButton = Child(PlainButtonComponent.self)
let description = Child(MultilineTextComponent.self)
let transferButton = Child(PlainButtonComponent.self)
@ -1602,6 +1602,7 @@ private final class GiftViewSheetContent: CombinedComponent {
var isSelfGift = false
var isChannelGift = false
var isMyUniqueGift = false
var releasedByPeer: EnginePeer?
if case let .soldOutGift(gift) = subject {
animationFile = gift.file
@ -1618,6 +1619,7 @@ private final class GiftViewSheetContent: CombinedComponent {
case let .generic(gift):
if let releasedBy = gift.releasedBy, let peer = state.peerMap[releasedBy], let addressName = peer.addressName {
subtitleString = strings.Gift_View_ReleasedBy("[@\(addressName)]()").string
releasedByPeer = peer
}
animationFile = gift.file
@ -2167,6 +2169,7 @@ private final class GiftViewSheetContent: CombinedComponent {
if let releasedBy = uniqueGift.releasedBy, let peer = state.peerMap[releasedBy], let addressName = peer.addressName {
descriptionText = strings.Gift_Unique_CollectibleBy("#\(presentationStringsFormattedNumber(uniqueGift.number, environment.dateTimeFormat.groupingSeparator))", "[@\(addressName)]()").string
hasDescriptionButton = true
releasedByPeer = peer
}
} else if soldOut {
descriptionText = strings.Gift_View_UnavailableDescription
@ -2235,16 +2238,34 @@ private final class GiftViewSheetContent: CombinedComponent {
var descriptionOffset: CGFloat = 0.0
if let subtitleString {
let textColor = theme.actionSheet.secondaryTextColor
let textFont = Font.regular(13.0)
let subtitleAttributedString = parseMarkdownIntoAttributedString(
subtitleString,
attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: textFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: theme.actionSheet.controlAccentColor), linkAttribute: { contents in
return (TelegramTextAttributes.URL, contents)
}),
textAlignment: .center
)
let subtitle = subtitle.update(
component: MultilineTextComponent(
text: .plain(NSAttributedString(
string: subtitleString,
font: Font.regular(13.0),
textColor: theme.actionSheet.secondaryTextColor,
paragraphAlignment: .center
)),
text: .plain(subtitleAttributedString),
horizontalAlignment: .center,
maximumNumberOfLines: 1
maximumNumberOfLines: 1,
highlightColor: theme.actionSheet.controlAccentColor.withAlphaComponent(0.1),
highlightAction: { attributes in
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)
} else {
return nil
}
},
tapAction: { [weak state] attributes, _ in
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String, let peer = releasedByPeer {
state?.openPeer(peer)
}
}
),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - 60.0, height: CGFloat.greatestFiniteMagnitude),
transition: .immediate
@ -2275,7 +2296,7 @@ private final class GiftViewSheetContent: CombinedComponent {
if let _ = uniqueGift {
textFont = Font.regular(13.0)
if hasDescriptionButton {
textColor = vibrantColor.mixedWith(UIColor.white, alpha: 0.5)
textColor = vibrantColor.mixedWith(UIColor.white, alpha: 0.4)
} else {
textColor = vibrantColor
}
@ -2307,14 +2328,14 @@ private final class GiftViewSheetContent: CombinedComponent {
highlightColor: linkColor.withAlphaComponent(0.1),
highlightInset: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: -8.0),
highlightAction: { attributes in
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
if !hasDescriptionButton, let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)
} else {
return nil
}
},
tapAction: { [weak state] attributes, _ in
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
if !hasDescriptionButton, let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
state?.openStarsIntro()
}
}
@ -2322,10 +2343,26 @@ private final class GiftViewSheetContent: CombinedComponent {
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - 50.0, height: CGFloat.greatestFiniteMagnitude),
transition: .immediate
)
context.add(description
.position(CGPoint(x: context.availableSize.width / 2.0, y: 207.0 + descriptionOffset + description.size.height / 2.0))
.appear(.default(alpha: true))
.disappear(.default(alpha: true))
)
if hasDescriptionButton {
let descriptionButton = descriptionButton.update(
component: RoundedRectangle(color: UIColor.white.withAlphaComponent(0.15), cornerRadius: 9.5),
component: PlainButtonComponent(
content: AnyComponent(
RoundedRectangle(color: UIColor.white.withAlphaComponent(0.15), cornerRadius: 9.5)
),
effectAlignment: .center,
action: { [weak state] in
if let releasedByPeer {
state?.openPeer(releasedByPeer)
}
},
animateScale: false
),
environment: {},
availableSize: CGSize(width: description.size.width + 18.0, height: 19.0),
transition: .immediate
@ -2337,12 +2374,6 @@ private final class GiftViewSheetContent: CombinedComponent {
)
}
context.add(description
.position(CGPoint(x: context.availableSize.width / 2.0, y: 207.0 + descriptionOffset + description.size.height / 2.0))
.appear(.default(alpha: true))
.disappear(.default(alpha: true))
)
originY += descriptionOffset
if uniqueGift != nil {

View File

@ -588,6 +588,8 @@ private let textUrlEdgeCharacters: CharacterSet = {
var set: CharacterSet = .alphanumerics
set.formUnion(.symbols)
set.formUnion(.punctuationCharacters)
set.remove("(")
set.remove(")")
return set
}()