mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-06-20 18:24:43 +00:00
Merge commit 'b5bede3d8bef92acee1d7152bdf5736eabae1638'
This commit is contained in:
@@ -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
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -304,6 +304,8 @@ public enum PresentationResourceKey: Int32 {
|
||||
|
||||
case chatReplyLineDashTemplateIncomingImage
|
||||
case chatReplyLineDashTemplateOutgoingImage
|
||||
|
||||
case chatBubbleCloseIcon
|
||||
}
|
||||
|
||||
public enum ChatExpiredStoryIndicatorType: Hashable {
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+43
-24
@@ -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
|
||||
|
||||
+65
-1
@@ -538,6 +538,9 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
private var adminBadgeNode: TextNode?
|
||||
private var credibilityIconView: ComponentHostView<Empty>?
|
||||
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
|
||||
|
||||
+1
@@ -565,6 +565,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
}, activateAdAction: { _ in
|
||||
}, openRequestedPeerSelection: { _, _, _ in
|
||||
}, saveMediaToFiles: { _ in
|
||||
}, openNoAdsDemo: {
|
||||
}, requestMessageUpdate: { _, _ in
|
||||
}, cancelInteractiveKeyboardGestures: {
|
||||
}, dismissTextInput: {
|
||||
|
||||
+3
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -166,6 +166,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
|
||||
}, activateAdAction: { _ in
|
||||
}, openRequestedPeerSelection: { _, _, _ in
|
||||
}, saveMediaToFiles: { _ in
|
||||
}, openNoAdsDemo: {
|
||||
}, requestMessageUpdate: { _, _ in
|
||||
}, cancelInteractiveKeyboardGestures: {
|
||||
}, dismissTextInput: {
|
||||
|
||||
@@ -2917,6 +2917,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||
}, activateAdAction: { _ in
|
||||
}, openRequestedPeerSelection: { _, _, _ in
|
||||
}, saveMediaToFiles: { _ in
|
||||
}, openNoAdsDemo: {
|
||||
}, requestMessageUpdate: { _, _ in
|
||||
}, cancelInteractiveKeyboardGestures: {
|
||||
}, dismissTextInput: {
|
||||
|
||||
@@ -1555,6 +1555,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
}, activateAdAction: { _ in
|
||||
}, openRequestedPeerSelection: { _, _, _ in
|
||||
}, saveMediaToFiles: { _ in
|
||||
}, openNoAdsDemo: {
|
||||
}, requestMessageUpdate: { _, _ in
|
||||
}, cancelInteractiveKeyboardGestures: {
|
||||
}, dismissTextInput: {
|
||||
|
||||
Reference in New Issue
Block a user