diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift index 296bab013a..4e25af6b4e 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift @@ -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,11 +929,13 @@ 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 { strongSelf.animationNode.started = { [weak self] in if let strongSelf = self { @@ -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) diff --git a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/ChatGiftPreviewItem.swift b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/ChatGiftPreviewItem.swift index ed635f4557..c91d2fb778 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/ChatGiftPreviewItem.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/ChatGiftPreviewItem.swift @@ -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 diff --git a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift index de249ea4da..eec3b4716c 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift @@ -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), - TelegramEngine.EngineData.Item.Peer.SendPaidMessageStars(id: component.peerId) - ) - |> deliverOnMainQueue).start(next: { [weak self] peer, accountPeer, sendPaidMessageStars in + 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) + ) + ).start(next: { [weak self] peers, sendPaidMessageStars in guard let self else { return } - if let peer { - self.peerMap[peer.id] = peer - } - if let accountPeer { - self.peerMap[accountPeer.id] = accountPeer + var peersMap: [EnginePeer.Id: EnginePeer] = [:] + for (peerId, maybePeer) in peers { + if let peer = maybePeer { + peersMap[peerId] = peer + } } + 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] = [] - 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, diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift index c070935f64..b0875d295a 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift @@ -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 { diff --git a/submodules/TextFormat/Sources/ChatTextInputAttributes.swift b/submodules/TextFormat/Sources/ChatTextInputAttributes.swift index 3777fa27a6..aceef6e4d8 100644 --- a/submodules/TextFormat/Sources/ChatTextInputAttributes.swift +++ b/submodules/TextFormat/Sources/ChatTextInputAttributes.swift @@ -588,6 +588,8 @@ private let textUrlEdgeCharacters: CharacterSet = { var set: CharacterSet = .alphanumerics set.formUnion(.symbols) set.formUnion(.punctuationCharacters) + set.remove("(") + set.remove(")") return set }()