diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/AdMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/AdMessages.swift index f2276b1545..acef2bc671 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/AdMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/AdMessages.swift @@ -36,8 +36,37 @@ private class AdMessagesHistoryContextImpl { } struct Invite: Equatable, Codable { + enum CodingKeys: String, CodingKey { + case title + case joinHash + case nameColor + } + var title: String var joinHash: String + var nameColor: PeerNameColor? + + init(title: String, joinHash: String, nameColor: PeerNameColor? = nil) { + self.title = title + self.joinHash = joinHash + self.nameColor = nameColor + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + self.title = try container.decode(String.self, forKey: .title) + self.joinHash = try container.decode(String.self, forKey: .joinHash) + self.nameColor = try container.decodeIfPresent(Int32.self, forKey: .nameColor).flatMap { PeerNameColor(rawValue: $0) } + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(self.title, forKey: .title) + try container.encode(self.joinHash, forKey: .joinHash) + try container.encodeIfPresent(self.nameColor?.rawValue, forKey: .nameColor) + } } struct WebPage: Equatable, Codable { @@ -265,7 +294,7 @@ private class AdMessagesHistoryContextImpl { defaultBannedRights: nil, usernames: [], storiesHidden: nil, - nameColor: nil, + nameColor: invite.nameColor, backgroundEmojiId: nil ) case let .webPage(webPage): @@ -286,7 +315,7 @@ private class AdMessagesHistoryContextImpl { defaultBannedRights: nil, usernames: [], storiesHidden: nil, - nameColor: nil, + nameColor: .blue, backgroundEmojiId: nil ) } @@ -522,24 +551,26 @@ private class AdMessagesHistoryContextImpl { let _ = flags let _ = participantsCount let _ = participants - let _ = nameColor target = .invite(CachedMessage.Target.Invite( title: title, - joinHash: chatInviteHash + joinHash: chatInviteHash, + nameColor: PeerNameColor(rawValue: nameColor) )) case let .chatInvitePeek(chat, _): if let peer = parseTelegramGroupOrChannel(chat: chat) { target = .invite(CachedMessage.Target.Invite( title: peer.debugDisplayTitle, - joinHash: chatInviteHash + joinHash: chatInviteHash, + nameColor: peer.nameColor )) } case let .chatInviteAlready(chat): if let peer = parseTelegramGroupOrChannel(chat: chat) { target = .invite(CachedMessage.Target.Invite( title: peer.debugDisplayTitle, - joinHash: chatInviteHash + joinHash: chatInviteHash, + nameColor: peer.nameColor )) } } diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift index 2a7e573952..63f6416e56 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift @@ -304,6 +304,8 @@ public enum PresentationResourceKey: Int32 { case chatReplyLineDashTemplateIncomingImage case chatReplyLineDashTemplateOutgoingImage + + case chatBubbleCloseIcon } public enum ChatExpiredStoryIndicatorType: Hashable { diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift index 5531b89a86..e5a44c1fb9 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift @@ -1350,4 +1350,30 @@ public struct PresentationResourcesChat { })?.resizableImage(withCapInsets: .zero, resizingMode: .tile).withRenderingMode(.alwaysTemplate) }) } + + public static func chatBubbleCloseIcon(_ theme: PresentationTheme) -> UIImage? { + return theme.image(PresentationResourceKey.chatBubbleCloseIcon.rawValue, { theme in + return generateImage(CGSize(width: 12.0, height: 12.0), rotatedContext: { size, context in + context.clear(CGRect(origin: .zero, size: size)) + + let color = theme.chat.message.incoming.secondaryTextColor + context.setAlpha(color.alpha) + context.setBlendMode(.copy) + + context.setStrokeColor(theme.chat.message.incoming.secondaryTextColor.withAlphaComponent(1.0).cgColor) + context.setLineWidth(1.0 + UIScreenPixel) + context.setLineCap(.round) + + let bounds = CGRect(origin: .zero, size: size).insetBy(dx: 1.0 + UIScreenPixel, dy: 1.0 + UIScreenPixel) + + context.move(to: CGPoint(x: bounds.minX, y: bounds.minY)) + context.addLine(to: CGPoint(x: bounds.maxX, y: bounds.maxY)) + context.strokePath() + + context.move(to: CGPoint(x: bounds.maxX, y: bounds.minY)) + context.addLine(to: CGPoint(x: bounds.minX, y: bounds.maxY)) + context.strokePath() + }) + }) + } } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift index 1924502961..7e2804813a 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift @@ -515,7 +515,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { } } } - + let (buttonWidth, continueLayout) = makeActionButtonLayout( maxContentsWidth, buttonIconImage, @@ -523,7 +523,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { actionTitle, mainColor, false, - false + message.adAttribute != nil ) actionButtonMinWidthAndFinalizeLayout = (buttonWidth, continueLayout) actualWidth = max(actualWidth, buttonWidth) @@ -588,9 +588,17 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { let maxStatusContentWidth: CGFloat = constrainedSize.width - layoutConstants.text.bubbleInsets.left - layoutConstants.text.bubbleInsets.right - var actionButtonTrailingContentWidth: CGFloat? - if !displayLine, let (actionButtonMinWidth, _) = actionButtonMinWidthAndFinalizeLayout { - actionButtonTrailingContentWidth = actionButtonMinWidth + var trailingContentWidth: CGFloat? + if let _ = message.adAttribute, let (textLayout, _) = textLayoutAndApply { + if textLayout.hasRTL { + trailingContentWidth = 10000.0 + } else { + trailingContentWidth = textLayout.trailingLineWidth + } + } else { + if !displayLine, let (actionButtonMinWidth, _) = actionButtonMinWidthAndFinalizeLayout { + trailingContentWidth = actionButtonMinWidth + } } let statusLayoutAndContinue = makeStatusLayout(ChatMessageDateAndStatusNode.Arguments( @@ -601,7 +609,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { dateText: dateText, type: statusType, layoutInput: .trailingContent( - contentWidth: actionButtonTrailingContentWidth, + contentWidth: trailingContentWidth, reactionSettings: ChatMessageDateAndStatusNode.TrailingReactionSettings(displayInline: shouldDisplayInlineDateReactions(message: message, isPremium: associatedData.isPremium, forceInline: associatedData.forceInlineReactions), preferAdditionalInset: false) ), constrainedSize: CGSize(width: maxStatusContentWidth, height: CGFloat.greatestFiniteMagnitude), @@ -1076,7 +1084,10 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { } if let item = contentDisplayOrder.first(where: { $0.item == .actionButton }), let (actionButtonSize, actionButtonApply) = actionButtonSizeAndApply { - let actionButtonFrame = CGRect(origin: CGPoint(x: insets.left, y: item.offsetY), size: actionButtonSize) + var actionButtonFrame = CGRect(origin: CGPoint(x: insets.left, y: item.offsetY), size: actionButtonSize) + if let _ = message.adAttribute { + actionButtonFrame.origin.y += statusSizeAndApply.0.height + } let actionButton = actionButtonApply(animation) @@ -1096,35 +1107,43 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { animation.animator.updateFrame(layer: actionButton.layer, frame: actionButtonFrame, completion: nil) } - let separatorFrame = CGRect(origin: CGPoint(x: actionButtonFrame.minX, y: actionButtonFrame.minY - 1.0), size: CGSize(width: actionButtonFrame.width, height: UIScreenPixel)) - - let actionButtonSeparator: SimpleLayer - if let current = self.actionButtonSeparator { - actionButtonSeparator = current - animation.animator.updateFrame(layer: actionButtonSeparator, frame: separatorFrame, completion: nil) + if let _ = message.adAttribute { + } else { - actionButtonSeparator = SimpleLayer() - self.actionButtonSeparator = actionButtonSeparator - self.layer.addSublayer(actionButtonSeparator) - actionButtonSeparator.frame = separatorFrame + let separatorFrame = CGRect(origin: CGPoint(x: actionButtonFrame.minX, y: actionButtonFrame.minY - 1.0), size: CGSize(width: actionButtonFrame.width, height: UIScreenPixel)) + + let actionButtonSeparator: SimpleLayer + if let current = self.actionButtonSeparator { + actionButtonSeparator = current + animation.animator.updateFrame(layer: actionButtonSeparator, frame: separatorFrame, completion: nil) + } else { + actionButtonSeparator = SimpleLayer() + self.actionButtonSeparator = actionButtonSeparator + self.layer.addSublayer(actionButtonSeparator) + actionButtonSeparator.frame = separatorFrame + } + + actionButtonSeparator.backgroundColor = mainColor.withMultipliedAlpha(0.2).cgColor } - - actionButtonSeparator.backgroundColor = mainColor.withMultipliedAlpha(0.2).cgColor } else { if let actionButton = self.actionButton { self.actionButton = nil actionButton.removeFromSupernode() } - if let actionButtonSeparator = self.actionButtonSeparator { - self.actionButtonSeparator = nil - actionButtonSeparator.removeFromSuperlayer() - } + } + + if self.actionButton == nil, let actionButtonSeparator = self.actionButtonSeparator { + self.actionButtonSeparator = nil + actionButtonSeparator.removeFromSuperlayer() } do { statusSizeAndApply.1(animation) - let statusFrame = CGRect(origin: CGPoint(x: actualSize.width - insets.right - statusSizeAndApply.0.width, y: actualSize.height - layoutConstants.text.bubbleInsets.bottom - statusSizeAndApply.0.height), size: statusSizeAndApply.0) + var statusFrame = CGRect(origin: CGPoint(x: actualSize.width - insets.right - statusSizeAndApply.0.width, y: actualSize.height - layoutConstants.text.bubbleInsets.bottom - statusSizeAndApply.0.height), size: statusSizeAndApply.0) + if let _ = message.adAttribute, let (actionButtonSize, _) = actionButtonSizeAndApply { + statusFrame.origin.y -= actionButtonSize.height + statusBackgroundSpacing + } animation.animator.updateFrame(layer: self.statusNode.layer, frame: statusFrame, completion: nil) self.statusNode.reactionSelected = { [weak self] value in diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index 6e02afa534..a9cf6b8602 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -538,6 +538,9 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI private var adminBadgeNode: TextNode? private var credibilityIconView: ComponentHostView? private var credibilityIconComponent: EmojiStatusComponent? + private var closeButtonNode: HighlightTrackingButtonNode? + private var closeIconNode: ASImageNode? + private var forwardInfoNode: ChatMessageForwardInfoNode? public var forwardInfoReferenceNode: ASDisplayNode? { return self.forwardInfoNode @@ -1029,6 +1032,12 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI return .fail } + + if let closeButtonNode = strongSelf.closeButtonNode { + if let _ = closeButtonNode.hitTest(strongSelf.view.convert(point, to: closeButtonNode.view), with: nil) { + return .fail + } + } if let shareButtonNode = strongSelf.shareButtonNode, shareButtonNode.frame.contains(point) { return .fail @@ -2965,6 +2974,55 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI strongSelf.adminBadgeNode?.removeFromSupernode() strongSelf.adminBadgeNode = nil } + + if let _ = item.message.adAttribute { + let buttonNode: HighlightTrackingButtonNode + let iconNode: ASImageNode + if let currentButton = strongSelf.closeButtonNode, let currentIcon = strongSelf.closeIconNode { + buttonNode = currentButton + iconNode = currentIcon + } else { + buttonNode = HighlightTrackingButtonNode() + iconNode = ASImageNode() + iconNode.displaysAsynchronously = false + iconNode.isUserInteractionEnabled = false + + buttonNode.addTarget(strongSelf, action: #selector(strongSelf.closeButtonPressed), forControlEvents: .touchUpInside) + buttonNode.highligthedChanged = { [weak iconNode] highlighted in + guard let iconNode else { + return + } + if highlighted { + iconNode.layer.removeAnimation(forKey: "opacity") + iconNode.alpha = 0.4 + } else { + iconNode.alpha = 1.0 + iconNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + } + } + + strongSelf.clippingNode.addSubnode(buttonNode) + strongSelf.clippingNode.addSubnode(iconNode) + + strongSelf.closeButtonNode = buttonNode + strongSelf.closeIconNode = iconNode + } + + iconNode.image = PresentationResourcesChat.chatBubbleCloseIcon(item.presentationData.theme.theme) + + let closeButtonSize = CGSize(width: 32.0, height: 32.0) + let closeIconSize = CGSize(width: 12.0, height: 12.0) + let closeButtonFrame = CGRect(origin: CGPoint(x: contentUpperRightCorner.x - closeButtonSize.width, y: layoutConstants.bubble.contentInsets.top), size: closeButtonSize) + let closeButtonIconFrame = CGRect(origin: CGPoint(x: contentUpperRightCorner.x - layoutConstants.text.bubbleInsets.left - closeIconSize.width + 1.0, y: layoutConstants.bubble.contentInsets.top + nameNodeOriginY + 2.0), size: closeIconSize) + + animation.animator.updateFrame(layer: buttonNode.layer, frame: closeButtonFrame, completion: nil) + animation.animator.updateFrame(layer: iconNode.layer, frame: closeButtonIconFrame, completion: nil) + } else { + strongSelf.closeButtonNode?.removeFromSupernode() + strongSelf.closeButtonNode = nil + strongSelf.closeIconNode?.removeFromSupernode() + strongSelf.closeIconNode = nil + } } else { if animation.isAnimated { if let nameNode = strongSelf.nameNode { @@ -4231,7 +4289,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI } return nil } - + if let threadInfoNode = self.threadInfoNode, let result = threadInfoNode.hitTest(self.view.convert(point, to: threadInfoNode.view), with: event) { return result } @@ -4637,6 +4695,12 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI } } + @objc private func closeButtonPressed() { + if let item = self.item { + item.controllerInteraction.openNoAdsDemo() + } + } + private var playedSwipeToReplyHaptic = false @objc private func swipeToReplyGesture(_ recognizer: ChatSwipeToReplyRecognizer) { var offset: CGFloat = 0.0 diff --git a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift index 4db80d297e..e45b0b5295 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift @@ -565,6 +565,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { }, activateAdAction: { _ in }, openRequestedPeerSelection: { _, _, _ in }, saveMediaToFiles: { _ in + }, openNoAdsDemo: { }, requestMessageUpdate: { _, _ in }, cancelInteractiveKeyboardGestures: { }, dismissTextInput: { diff --git a/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift b/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift index 44170ef387..1b5e74a0bc 100644 --- a/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift +++ b/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift @@ -196,6 +196,7 @@ public final class ChatControllerInteraction { public let activateAdAction: (EngineMessage.Id) -> Void public let openRequestedPeerSelection: (EngineMessage.Id, ReplyMarkupButtonRequestPeerType, Int32) -> Void public let saveMediaToFiles: (EngineMessage.Id) -> Void + public let openNoAdsDemo: () -> Void public let requestMessageUpdate: (MessageId, Bool) -> Void public let cancelInteractiveKeyboardGestures: () -> Void @@ -310,6 +311,7 @@ public final class ChatControllerInteraction { activateAdAction: @escaping (EngineMessage.Id) -> Void, openRequestedPeerSelection: @escaping (EngineMessage.Id, ReplyMarkupButtonRequestPeerType, Int32) -> Void, saveMediaToFiles: @escaping (EngineMessage.Id) -> Void, + openNoAdsDemo: @escaping () -> Void, requestMessageUpdate: @escaping (MessageId, Bool) -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void, dismissTextInput: @escaping () -> Void, @@ -406,6 +408,7 @@ public final class ChatControllerInteraction { self.activateAdAction = activateAdAction self.openRequestedPeerSelection = openRequestedPeerSelection self.saveMediaToFiles = saveMediaToFiles + self.openNoAdsDemo = openNoAdsDemo self.requestMessageUpdate = requestMessageUpdate self.cancelInteractiveKeyboardGestures = cancelInteractiveKeyboardGestures self.dismissTextInput = dismissTextInput diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index f4898f2dfb..21fb06b88e 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -4881,6 +4881,19 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } })) }) + }, openNoAdsDemo: { [weak self] in + guard let self else { + return + } + var replaceImpl: ((ViewController) -> Void)? + let controller = PremiumDemoScreen(context: self.context, subject: .noAds, action: { + let controller = PremiumIntroScreen(context: self.context, source: .ads) + replaceImpl?(controller) + }) + replaceImpl = { [weak controller] c in + controller?.replace(with: c) + } + self.push(controller) }, requestMessageUpdate: { [weak self] id, scroll in if let self { self.chatDisplayNode.historyNode.requestMessageUpdate(id, andScrollToItem: scroll) diff --git a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift index 648ceaeae5..40056dac9b 100644 --- a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift +++ b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift @@ -166,6 +166,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu }, activateAdAction: { _ in }, openRequestedPeerSelection: { _, _, _ in }, saveMediaToFiles: { _ in + }, openNoAdsDemo: { }, requestMessageUpdate: { _, _ in }, cancelInteractiveKeyboardGestures: { }, dismissTextInput: { diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index f5bea1e8c1..17fd1eb03c 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -2917,6 +2917,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro }, activateAdAction: { _ in }, openRequestedPeerSelection: { _, _, _ in }, saveMediaToFiles: { _ in + }, openNoAdsDemo: { }, requestMessageUpdate: { _, _ in }, cancelInteractiveKeyboardGestures: { }, dismissTextInput: { diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 90a2dab43c..fa2493f7a8 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -1555,6 +1555,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { }, activateAdAction: { _ in }, openRequestedPeerSelection: { _, _, _ in }, saveMediaToFiles: { _ in + }, openNoAdsDemo: { }, requestMessageUpdate: { _, _ in }, cancelInteractiveKeyboardGestures: { }, dismissTextInput: {