Reaction improvements

This commit is contained in:
Ali 2021-12-22 20:35:45 +04:00
parent 216ddc4a6e
commit 4803a3c6cc
19 changed files with 215 additions and 75 deletions

View File

@ -487,6 +487,12 @@ private func compressFrame(width: Int, height: Int, rgbData: Data) -> Data? {
} }
private final class AnimatedStickerDirectFrameSourceCache { private final class AnimatedStickerDirectFrameSourceCache {
private enum FrameRangeResult {
case range(Range<Int>)
case notFound
case corruptedFile
}
private let queue: Queue private let queue: Queue
private let storeQueue: Queue private let storeQueue: Queue
private let file: ManagedFileImpl private let file: ManagedFileImpl
@ -548,27 +554,31 @@ private final class AnimatedStickerDirectFrameSourceCache {
} }
} }
private func readFrameRange(index: Int) -> Range<Int>? { private func readFrameRange(index: Int) -> FrameRangeResult {
if index < 0 || index >= self.frameCount { if index < 0 || index >= self.frameCount {
return nil return .notFound
} }
self.file.seek(position: Int64(index * 4 * 2)) self.file.seek(position: Int64(index * 4 * 2))
var offset: Int32 = 0 var offset: Int32 = 0
var length: Int32 = 0 var length: Int32 = 0
if self.file.read(&offset, 4) != 4 { if self.file.read(&offset, 4) != 4 {
return nil return .corruptedFile
} }
if self.file.read(&length, 4) != 4 { if self.file.read(&length, 4) != 4 {
return nil return .corruptedFile
} }
if length == 0 { if length == 0 {
return nil return .notFound
} }
if length < 0 || offset < 0 { if length < 0 || offset < 0 {
return nil return .corruptedFile
} }
return (Int(offset) ..< Int(offset + length)) if Int64(offset) + Int64(length) > 100 * 1024 * 1024 {
return .corruptedFile
}
return .range(Int(offset) ..< Int(offset + length))
} }
func storeUncompressedRgbFrame(index: Int, rgbData: Data) { func storeUncompressedRgbFrame(index: Int, rgbData: Data) {
@ -617,9 +627,10 @@ private final class AnimatedStickerDirectFrameSourceCache {
if index < 0 || index >= self.frameCount { if index < 0 || index >= self.frameCount {
return nil return nil
} }
guard let range = self.readFrameRange(index: index) else { let rangeResult = self.readFrameRange(index: index)
return nil
} switch rangeResult {
case let .range(range):
self.file.seek(position: Int64(range.lowerBound)) self.file.seek(position: Int64(range.lowerBound))
let length = range.upperBound - range.lowerBound let length = range.upperBound - range.lowerBound
let compressedData = self.file.readData(count: length) let compressedData = self.file.readData(count: length)
@ -654,6 +665,14 @@ private final class AnimatedStickerDirectFrameSourceCache {
} }
return frameData return frameData
case .notFound:
return nil
case .corruptedFile:
self.file.truncate(count: 0)
self.initializeFrameTable()
return nil
}
} }
} }

View File

@ -78,14 +78,14 @@ public final class JoinLinkPreviewController: ViewController {
if let strongSelf = self { if let strongSelf = self {
strongSelf.resolvedState = result strongSelf.resolvedState = result
switch result { switch result {
case let .invite(flags, title, about, photoRepresentation, participantsCount, participants): case let .invite(invite):
if flags.requestNeeded { if invite.flags.requestNeeded {
strongSelf.isRequest = true strongSelf.isRequest = true
strongSelf.isGroup = !flags.isBroadcast strongSelf.isGroup = !invite.flags.isBroadcast
strongSelf.controllerNode.setRequestPeer(image: photoRepresentation, title: title, about: about, memberCount: participantsCount, isGroup: !flags.isBroadcast) strongSelf.controllerNode.setRequestPeer(image: invite.photoRepresentation, title: invite.title, about: invite.about, memberCount: invite.participantsCount, isGroup: !invite.flags.isBroadcast)
} else { } else {
let data = JoinLinkPreviewData(isGroup: participants != nil, isJoined: false) let data = JoinLinkPreviewData(isGroup: invite.participants != nil, isJoined: false)
strongSelf.controllerNode.setInvitePeer(image: photoRepresentation, title: title, memberCount: participantsCount, members: participants?.map({ EnginePeer($0) }) ?? [], data: data) strongSelf.controllerNode.setInvitePeer(image: invite.photoRepresentation, title: invite.title, memberCount: invite.participantsCount, members: invite.participants?.map({ $0 }) ?? [], data: data)
} }
case let .alreadyJoined(peerId): case let .alreadyJoined(peerId):
strongSelf.navigateToPeer(peerId, nil) strongSelf.navigateToPeer(peerId, nil)

View File

@ -185,11 +185,16 @@ final class ReactionContextBackgroundNode: ASDisplayNode {
self.backgroundLayer.animateSpring(from: NSValue(cgPoint: CGPoint(x: sourceBackgroundFrame.midX - size.width / 2.0, y: 0.0)), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: springDuration, delay: springDelay, initialVelocity: 0.0, damping: springDamping, additive: true) self.backgroundLayer.animateSpring(from: NSValue(cgPoint: CGPoint(x: sourceBackgroundFrame.midX - size.width / 2.0, y: 0.0)), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: springDuration, delay: springDelay, initialVelocity: 0.0, damping: springDamping, additive: true)
self.backgroundLayer.animateSpring(from: NSValue(cgRect: CGRect(origin: CGPoint(), size: sourceBackgroundFrame.size)), to: NSValue(cgRect: CGRect(origin: CGPoint(), size: size)), keyPath: "bounds", duration: springDuration, delay: springDelay, initialVelocity: 0.0, damping: springDamping) self.backgroundLayer.animateSpring(from: NSValue(cgRect: CGRect(origin: CGPoint(), size: sourceBackgroundFrame.size)), to: NSValue(cgRect: CGRect(origin: CGPoint(), size: size)), keyPath: "bounds", duration: springDuration, delay: springDelay, initialVelocity: 0.0, damping: springDamping)
self.backgroundShadowLayer.animateSpring(from: NSValue(cgPoint: CGPoint(x: sourceBackgroundFrame.midX - size.width / 2.0, y: 0.0)), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: springDuration, delay: springDelay, initialVelocity: 0.0, damping: springDamping, additive: true)
self.backgroundShadowLayer.animateSpring(from: NSValue(cgRect: CGRect(origin: CGPoint(), size: sourceBackgroundFrame.size)), to: NSValue(cgRect: CGRect(origin: CGPoint(), size: size)), keyPath: "bounds", duration: springDuration, delay: springDelay, initialVelocity: 0.0, damping: springDamping)
} }
func animateOut() { func animateOut() {
self.backgroundLayer.animateAlpha(from: CGFloat(self.backgroundLayer.opacity), to: 0.0, duration: 0.2, removeOnCompletion: false) self.backgroundLayer.animateAlpha(from: CGFloat(self.backgroundLayer.opacity), to: 0.0, duration: 0.2, removeOnCompletion: false)
self.backgroundShadowLayer.animateAlpha(from: CGFloat(self.backgroundShadowLayer.opacity), to: 0.0, duration: 0.2, removeOnCompletion: false)
self.largeCircleLayer.animateAlpha(from: CGFloat(self.largeCircleLayer.opacity), to: 0.0, duration: 0.2, removeOnCompletion: false) self.largeCircleLayer.animateAlpha(from: CGFloat(self.largeCircleLayer.opacity), to: 0.0, duration: 0.2, removeOnCompletion: false)
self.largeCircleShadowLayer.animateAlpha(from: CGFloat(self.largeCircleShadowLayer.opacity), to: 0.0, duration: 0.2, removeOnCompletion: false)
self.smallCircleLayer.animateAlpha(from: CGFloat(self.smallCircleLayer.opacity), to: 0.0, duration: 0.2, removeOnCompletion: false) self.smallCircleLayer.animateAlpha(from: CGFloat(self.smallCircleLayer.opacity), to: 0.0, duration: 0.2, removeOnCompletion: false)
self.smallCircleShadowLayer.animateAlpha(from: CGFloat(self.smallCircleShadowLayer.opacity), to: 0.0, duration: 0.2, removeOnCompletion: false)
} }
} }

View File

@ -17,17 +17,20 @@ public final class ReactionContextItem {
} }
public let reaction: ReactionContextItem.Reaction public let reaction: ReactionContextItem.Reaction
public let appearAnimation: TelegramMediaFile
public let stillAnimation: TelegramMediaFile public let stillAnimation: TelegramMediaFile
public let listAnimation: TelegramMediaFile public let listAnimation: TelegramMediaFile
public let applicationAnimation: TelegramMediaFile public let applicationAnimation: TelegramMediaFile
public init( public init(
reaction: ReactionContextItem.Reaction, reaction: ReactionContextItem.Reaction,
appearAnimation: TelegramMediaFile,
stillAnimation: TelegramMediaFile, stillAnimation: TelegramMediaFile,
listAnimation: TelegramMediaFile, listAnimation: TelegramMediaFile,
applicationAnimation: TelegramMediaFile applicationAnimation: TelegramMediaFile
) { ) {
self.reaction = reaction self.reaction = reaction
self.appearAnimation = appearAnimation
self.stillAnimation = stillAnimation self.stillAnimation = stillAnimation
self.listAnimation = listAnimation self.listAnimation = listAnimation
self.applicationAnimation = applicationAnimation self.applicationAnimation = applicationAnimation

View File

@ -148,6 +148,7 @@ class ReactionChatPreviewItemNode: ListViewItemNode {
standaloneReactionAnimation.animateReactionSelection( standaloneReactionAnimation.animateReactionSelection(
context: item.context, theme: item.theme, reaction: ReactionContextItem( context: item.context, theme: item.theme, reaction: ReactionContextItem(
reaction: ReactionContextItem.Reaction(rawValue: reaction.value), reaction: ReactionContextItem.Reaction(rawValue: reaction.value),
appearAnimation: reaction.appearAnimation,
stillAnimation: reaction.selectAnimation, stillAnimation: reaction.selectAnimation,
listAnimation: reaction.activateAnimation, listAnimation: reaction.activateAnimation,
applicationAnimation: reaction.effectAnimation applicationAnimation: reaction.effectAnimation

View File

@ -2,14 +2,17 @@ import Foundation
import Postbox import Postbox
public final class AdMessageAttribute: MessageAttribute { public final class AdMessageAttribute: MessageAttribute {
public let opaqueId: Data public enum MessageTarget {
public let startParam: String? case peer(id: EnginePeer.Id, message: EngineMessage.Id?, startParam: String?)
public let messageId: MessageId? case join(title: String, joinHash: String)
}
public init(opaqueId: Data, startParam: String?, messageId: MessageId?) { public let opaqueId: Data
public let target: MessageTarget
public init(opaqueId: Data, target: MessageTarget) {
self.opaqueId = opaqueId self.opaqueId = opaqueId
self.startParam = startParam self.target = target
self.messageId = messageId
} }
public init(decoder: PostboxDecoder) { public init(decoder: PostboxDecoder) {

View File

@ -234,6 +234,7 @@ func managedSynchronizeAvailableReactions(postbox: Postbox, network: Network) ->
for reaction in availableReactions.reactions { for reaction in availableReactions.reactions {
resources.append(reaction.staticIcon.resource) resources.append(reaction.staticIcon.resource)
resources.append(reaction.appearAnimation.resource)
resources.append(reaction.selectAnimation.resource) resources.append(reaction.selectAnimation.resource)
resources.append(reaction.activateAnimation.resource) resources.append(reaction.activateAnimation.resource)
resources.append(reaction.effectAnimation.resource) resources.append(reaction.effectAnimation.resource)

View File

@ -22,19 +22,39 @@ private class AdMessagesHistoryContextImpl {
enum CodingKeys: String, CodingKey { enum CodingKeys: String, CodingKey {
case peer case peer
case invite
}
struct Invite: Equatable, Codable {
var title: String
var joinHash: String
} }
case peer(PeerId) case peer(PeerId)
case invite(Invite)
init(from decoder: Decoder) throws { init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self) let container = try decoder.container(keyedBy: CodingKeys.self)
if let peer = try container.decodeIfPresent(Int64.self, forKey: .peer) { if let peer = try container.decodeIfPresent(Int64.self, forKey: .peer) {
self = .peer(PeerId(peer)) self = .peer(PeerId(peer))
} else if let invite = try container.decodeIfPresent(Invite.self, forKey: .invite) {
self = .invite(invite)
} else { } else {
throw DecodingError.generic throw DecodingError.generic
} }
} }
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case let .peer(peerId):
try container.encode(peerId.toInt64(), forKey: .peer)
case let .invite(invite):
try container.encode(invite, forKey: .invite)
}
}
} }
public let opaqueId: Data public let opaqueId: Data
@ -133,7 +153,14 @@ private class AdMessagesHistoryContextImpl {
func toMessage(peerId: PeerId, transaction: Transaction) -> Message? { func toMessage(peerId: PeerId, transaction: Transaction) -> Message? {
var attributes: [MessageAttribute] = [] var attributes: [MessageAttribute] = []
attributes.append(AdMessageAttribute(opaqueId: self.opaqueId, startParam: self.startParam, messageId: self.messageId)) let target: AdMessageAttribute.MessageTarget
switch self.target {
case let .peer(peerId):
target = .peer(id: peerId, message: self.messageId, startParam: self.startParam)
case let .invite(invite):
target = .join(title: invite.title, joinHash: invite.joinHash)
}
attributes.append(AdMessageAttribute(opaqueId: self.opaqueId, target: target))
if !self.textEntities.isEmpty { if !self.textEntities.isEmpty {
let attribute = TextEntitiesMessageAttribute(entities: self.textEntities) let attribute = TextEntitiesMessageAttribute(entities: self.textEntities)
attributes.append(attribute) attributes.append(attribute)
@ -153,6 +180,23 @@ private class AdMessagesHistoryContextImpl {
} else { } else {
return nil return nil
} }
case let .invite(invite):
author = TelegramChannel(
id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(1)),
accessHash: nil,
title: invite.title,
username: nil,
photo: [],
creationDate: 0,
version: 0,
participationStatus: .left,
info: .broadcast(TelegramChannelBroadcastInfo(flags: [])),
flags: [],
restrictionInfo: nil,
adminRights: nil,
bannedRights: nil,
defaultBannedRights: nil
)
} }
messagePeers[author.id] = author messagePeers[author.id] = author
@ -368,6 +412,36 @@ private class AdMessagesHistoryContextImpl {
var target: CachedMessage.Target? var target: CachedMessage.Target?
if let fromId = fromId { if let fromId = fromId {
target = .peer(fromId.peerId) target = .peer(fromId.peerId)
} else if let chatInvite = chatInvite, let chatInviteHash = chatInviteHash {
switch chatInvite {
case let .chatInvite(flags, title, _, photo, participantsCount, participants):
let photo = telegramMediaImageFromApiPhoto(photo).flatMap({ smallestImageRepresentation($0.representations) })
let flags: ExternalJoiningChatState.Invite.Flags = .init(isChannel: (flags & (1 << 0)) != 0, isBroadcast: (flags & (1 << 1)) != 0, isPublic: (flags & (1 << 2)) != 0, isMegagroup: (flags & (1 << 3)) != 0, requestNeeded: (flags & (1 << 6)) != 0)
let _ = photo
let _ = flags
let _ = participantsCount
let _ = participants
target = .invite(CachedMessage.Target.Invite(
title: title,
joinHash: chatInviteHash
))
case let .chatInvitePeek(chat, _):
if let peer = parseTelegramGroupOrChannel(chat: chat) {
target = .invite(CachedMessage.Target.Invite(
title: peer.debugDisplayTitle,
joinHash: chatInviteHash
))
}
case let .chatInviteAlready(chat):
if let peer = parseTelegramGroupOrChannel(chat: chat) {
target = .invite(CachedMessage.Target.Invite(
title: peer.debugDisplayTitle,
joinHash: chatInviteHash
))
}
}
} }
var messageId: MessageId? var messageId: MessageId?

View File

@ -29,7 +29,8 @@ func apiUpdatesGroups(_ updates: Api.Updates) -> [Api.Chat] {
} }
public enum ExternalJoiningChatState { public enum ExternalJoiningChatState {
public struct InviteFlags : Equatable { public struct Invite: Equatable {
public struct Flags: Equatable, Codable {
public let isChannel: Bool public let isChannel: Bool
public let isBroadcast: Bool public let isBroadcast: Bool
public let isPublic: Bool public let isPublic: Bool
@ -37,13 +38,21 @@ public enum ExternalJoiningChatState {
public let requestNeeded: Bool public let requestNeeded: Bool
} }
case invite(flags: InviteFlags, title: String, about: String?, photoRepresentation: TelegramMediaImageRepresentation?, participantsCount: Int32, participants: [Peer]?) public let flags: Flags
public let title: String
public let about: String?
public let photoRepresentation: TelegramMediaImageRepresentation?
public let participantsCount: Int32
public let participants: [EnginePeer]?
}
case invite(Invite)
case alreadyJoined(PeerId) case alreadyJoined(PeerId)
case invalidHash case invalidHash
case peek(PeerId, Int32) case peek(PeerId, Int32)
} }
func _internal_joinChatInteractively(with hash: String, account: Account) -> Signal <PeerId?, JoinLinkError> { func _internal_joinChatInteractively(with hash: String, account: Account) -> Signal<PeerId?, JoinLinkError> {
return account.network.request(Api.functions.messages.importChatInvite(hash: hash), automaticFloodWait: false) return account.network.request(Api.functions.messages.importChatInvite(hash: hash), automaticFloodWait: false)
|> mapError { error -> JoinLinkError in |> mapError { error -> JoinLinkError in
switch error.errorDescription { switch error.errorDescription {
@ -94,8 +103,8 @@ func _internal_joinLinkInformation(_ hash: String, account: Account) -> Signal<E
switch result { switch result {
case let .chatInvite(flags, title, about, invitePhoto, participantsCount, participants): case let .chatInvite(flags, title, about, invitePhoto, participantsCount, participants):
let photo = telegramMediaImageFromApiPhoto(invitePhoto).flatMap({ smallestImageRepresentation($0.representations) }) let photo = telegramMediaImageFromApiPhoto(invitePhoto).flatMap({ smallestImageRepresentation($0.representations) })
let flags:ExternalJoiningChatState.InviteFlags = .init(isChannel: (flags & (1 << 0)) != 0, isBroadcast: (flags & (1 << 1)) != 0, isPublic: (flags & (1 << 2)) != 0, isMegagroup: (flags & (1 << 3)) != 0, requestNeeded: (flags & (1 << 6)) != 0) let flags: ExternalJoiningChatState.Invite.Flags = .init(isChannel: (flags & (1 << 0)) != 0, isBroadcast: (flags & (1 << 1)) != 0, isPublic: (flags & (1 << 2)) != 0, isMegagroup: (flags & (1 << 3)) != 0, requestNeeded: (flags & (1 << 6)) != 0)
return .single(.invite(flags: flags, title: title, about: about, photoRepresentation: photo, participantsCount: participantsCount, participants: participants?.map({TelegramUser(user: $0)}))) return .single(.invite(ExternalJoiningChatState.Invite(flags: flags, title: title, about: about, photoRepresentation: photo, participantsCount: participantsCount, participants: participants?.map({ EnginePeer(TelegramUser(user: $0)) }))))
case let .chatInviteAlready(chat): case let .chatInviteAlready(chat):
if let peer = parseTelegramGroupOrChannel(chat: chat) { if let peer = parseTelegramGroupOrChannel(chat: chat) {
return account.postbox.transaction({ (transaction) -> ExternalJoiningChatState in return account.postbox.transaction({ (transaction) -> ExternalJoiningChatState in

View File

@ -1015,6 +1015,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
actions.reactionItems.append(ReactionContextItem( actions.reactionItems.append(ReactionContextItem(
reaction: ReactionContextItem.Reaction(rawValue: reaction.value), reaction: ReactionContextItem.Reaction(rawValue: reaction.value),
appearAnimation: reaction.appearAnimation,
stillAnimation: reaction.selectAnimation, stillAnimation: reaction.selectAnimation,
listAnimation: reaction.activateAnimation, listAnimation: reaction.activateAnimation,
applicationAnimation: reaction.effectAnimation applicationAnimation: reaction.effectAnimation
@ -1249,6 +1250,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
theme: strongSelf.presentationData.theme, theme: strongSelf.presentationData.theme,
reaction: ReactionContextItem( reaction: ReactionContextItem(
reaction: ReactionContextItem.Reaction(rawValue: reaction.value), reaction: ReactionContextItem.Reaction(rawValue: reaction.value),
appearAnimation: reaction.appearAnimation,
stillAnimation: reaction.selectAnimation, stillAnimation: reaction.selectAnimation,
listAnimation: reaction.activateAnimation, listAnimation: reaction.activateAnimation,
applicationAnimation: reaction.effectAnimation applicationAnimation: reaction.effectAnimation
@ -3193,6 +3195,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
])]) ])])
strongSelf.chatDisplayNode.dismissInput() strongSelf.chatDisplayNode.dismissInput()
strongSelf.present(actionSheet, in: .window(.root)) strongSelf.present(actionSheet, in: .window(.root))
}, openJoinLink: { [weak self] joinHash in
guard let strongSelf = self else {
return
}
strongSelf.openResolved(result: .join(joinHash), sourceMessageId: nil)
}, requestMessageUpdate: { [weak self] id in }, requestMessageUpdate: { [weak self] id in
if let strongSelf = self { if let strongSelf = self {
strongSelf.chatDisplayNode.historyNode.requestMessageUpdate(id) strongSelf.chatDisplayNode.historyNode.requestMessageUpdate(id)

View File

@ -129,6 +129,7 @@ public final class ChatControllerInteraction {
let updateChoosingSticker: (Bool) -> Void let updateChoosingSticker: (Bool) -> Void
let commitEmojiInteraction: (MessageId, String, EmojiInteraction, TelegramMediaFile) -> Void let commitEmojiInteraction: (MessageId, String, EmojiInteraction, TelegramMediaFile) -> Void
let openLargeEmojiInfo: (String, String?, TelegramMediaFile) -> Void let openLargeEmojiInfo: (String, String?, TelegramMediaFile) -> Void
let openJoinLink: (String) -> Void
let requestMessageUpdate: (MessageId) -> Void let requestMessageUpdate: (MessageId) -> Void
let cancelInteractiveKeyboardGestures: () -> Void let cancelInteractiveKeyboardGestures: () -> Void
@ -227,6 +228,7 @@ public final class ChatControllerInteraction {
updateChoosingSticker: @escaping (Bool) -> Void, updateChoosingSticker: @escaping (Bool) -> Void,
commitEmojiInteraction: @escaping (MessageId, String, EmojiInteraction, TelegramMediaFile) -> Void, commitEmojiInteraction: @escaping (MessageId, String, EmojiInteraction, TelegramMediaFile) -> Void,
openLargeEmojiInfo: @escaping (String, String?, TelegramMediaFile) -> Void, openLargeEmojiInfo: @escaping (String, String?, TelegramMediaFile) -> Void,
openJoinLink: @escaping (String) -> Void,
requestMessageUpdate: @escaping (MessageId) -> Void, requestMessageUpdate: @escaping (MessageId) -> Void,
cancelInteractiveKeyboardGestures: @escaping () -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void,
automaticMediaDownloadSettings: MediaAutoDownloadSettings, automaticMediaDownloadSettings: MediaAutoDownloadSettings,
@ -311,6 +313,7 @@ public final class ChatControllerInteraction {
self.updateChoosingSticker = updateChoosingSticker self.updateChoosingSticker = updateChoosingSticker
self.commitEmojiInteraction = commitEmojiInteraction self.commitEmojiInteraction = commitEmojiInteraction
self.openLargeEmojiInfo = openLargeEmojiInfo self.openLargeEmojiInfo = openLargeEmojiInfo
self.openJoinLink = openJoinLink
self.requestMessageUpdate = requestMessageUpdate self.requestMessageUpdate = requestMessageUpdate
self.cancelInteractiveKeyboardGestures = cancelInteractiveKeyboardGestures self.cancelInteractiveKeyboardGestures = cancelInteractiveKeyboardGestures
@ -369,6 +372,7 @@ public final class ChatControllerInteraction {
}, updateChoosingSticker: { _ in }, updateChoosingSticker: { _ in
}, commitEmojiInteraction: { _, _, _, _ in }, commitEmojiInteraction: { _, _, _, _ in
}, openLargeEmojiInfo: { _, _, _ in }, openLargeEmojiInfo: { _, _, _ in
}, openJoinLink: { _ in
}, requestMessageUpdate: { _ in }, requestMessageUpdate: { _ in
}, cancelInteractiveKeyboardGestures: { }, cancelInteractiveKeyboardGestures: {
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,

View File

@ -245,7 +245,9 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
} else { } else {
if result.last?.1 == ChatMessageWebpageBubbleContentNode.self || if result.last?.1 == ChatMessageWebpageBubbleContentNode.self ||
result.last?.1 == ChatMessagePollBubbleContentNode.self || result.last?.1 == ChatMessagePollBubbleContentNode.self ||
result.last?.1 == ChatMessageContactBubbleContentNode.self { result.last?.1 == ChatMessageContactBubbleContentNode.self ||
result.last?.1 == ChatMessageGameBubbleContentNode.self ||
result.last?.1 == ChatMessageInvoiceBubbleContentNode.self {
result.append((firstMessage, ChatMessageReactionsFooterContentNode.self, ChatMessageEntryAttributes(), BubbleItemAttributes(isAttachment: true, neighborType: .freeform, neighborSpacing: .default))) result.append((firstMessage, ChatMessageReactionsFooterContentNode.self, ChatMessageEntryAttributes(), BubbleItemAttributes(isAttachment: true, neighborType: .freeform, neighborSpacing: .default)))
needReactions = false needReactions = false
} else if result.last?.1 == ChatMessageCommentFooterContentNode.self { } else if result.last?.1 == ChatMessageCommentFooterContentNode.self {
@ -254,11 +256,6 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
result[result.count - 2].1 == ChatMessageContactBubbleContentNode.self { result[result.count - 2].1 == ChatMessageContactBubbleContentNode.self {
result.insert((firstMessage, ChatMessageReactionsFooterContentNode.self, ChatMessageEntryAttributes(), BubbleItemAttributes(isAttachment: true, neighborType: .freeform, neighborSpacing: .default)), at: result.count - 1) result.insert((firstMessage, ChatMessageReactionsFooterContentNode.self, ChatMessageEntryAttributes(), BubbleItemAttributes(isAttachment: true, neighborType: .freeform, neighborSpacing: .default)), at: result.count - 1)
} }
/*if result[result.count - 2].1 == ChatMessageTextBubbleContentNode.self {
} else {
result.insert((firstMessage, ChatMessageReactionsFooterContentNode.self, ChatMessageEntryAttributes(), BubbleItemAttributes(isAttachment: true, neighborType: .freeform, neighborSpacing: .default)), at: result.count - 1)
needReactions = false
}*/
} }
} }
} }

View File

@ -301,8 +301,15 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
isReplyThread = true isReplyThread = true
} }
let trailingWidthToMeasure: CGFloat
if textLayout.hasRTL {
trailingWidthToMeasure = 10000.0
} else {
trailingWidthToMeasure = textLayout.trailingLineWidth
}
let dateLayoutInput: ChatMessageDateAndStatusNode.LayoutInput let dateLayoutInput: ChatMessageDateAndStatusNode.LayoutInput
dateLayoutInput = .trailingContent(contentWidth: textLayout.trailingLineWidth, reactionSettings: ChatMessageDateAndStatusNode.TrailingReactionSettings(displayInline: shouldDisplayInlineDateReactions(message: item.message), preferAdditionalInset: false)) dateLayoutInput = .trailingContent(contentWidth: trailingWidthToMeasure, reactionSettings: ChatMessageDateAndStatusNode.TrailingReactionSettings(displayInline: shouldDisplayInlineDateReactions(message: item.message), preferAdditionalInset: false))
statusSuggestedWidthAndContinue = statusLayout(ChatMessageDateAndStatusNode.Arguments( statusSuggestedWidthAndContinue = statusLayout(ChatMessageDateAndStatusNode.Arguments(
context: item.context, context: item.context,

View File

@ -63,18 +63,23 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode {
} }
self.contentNode.activateAction = { [weak self] in self.contentNode.activateAction = { [weak self] in
if let strongSelf = self, let item = strongSelf.item { if let strongSelf = self, let item = strongSelf.item {
if let adAttribute = item.message.adAttribute, let author = item.message.author { if let adAttribute = item.message.adAttribute {
switch adAttribute.target {
case let .peer(id, messageId, startParam):
let navigationData: ChatControllerInteractionNavigateToPeer let navigationData: ChatControllerInteractionNavigateToPeer
if let bot = author as? TelegramUser, bot.botInfo != nil, let startParam = adAttribute.startParam { if let bot = item.message.author as? TelegramUser, bot.botInfo != nil, let startParam = startParam {
navigationData = .withBotStartPayload(ChatControllerInitialBotStart(payload: startParam, behavior: .interactive)) navigationData = .withBotStartPayload(ChatControllerInitialBotStart(payload: startParam, behavior: .interactive))
} else { } else {
var subject: ChatControllerSubject? var subject: ChatControllerSubject?
if let messageId = adAttribute.messageId { if let messageId = messageId {
subject = .message(id: .id(messageId), highlight: true, timecode: nil) subject = .message(id: .id(messageId), highlight: true, timecode: nil)
} }
navigationData = .chat(textInputState: nil, subject: subject, peekData: nil) navigationData = .chat(textInputState: nil, subject: subject, peekData: nil)
} }
item.controllerInteraction.openPeer(author.id, navigationData, nil) item.controllerInteraction.openPeer(id, navigationData, nil)
case let .join(_, joinHash):
item.controllerInteraction.openJoinLink(joinHash)
}
} else { } else {
var webPageContent: TelegramMediaWebpageLoadedContent? var webPageContent: TelegramMediaWebpageLoadedContent?
for media in item.message.media { for media in item.message.media {
@ -342,13 +347,13 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode {
if let author = item.message.author as? TelegramUser, author.botInfo != nil { if let author = item.message.author as? TelegramUser, author.botInfo != nil {
actionTitle = item.presentationData.strings.Conversation_ViewBot actionTitle = item.presentationData.strings.Conversation_ViewBot
} else if let author = item.message.author as? TelegramChannel, case .group = author.info { } else if let author = item.message.author as? TelegramChannel, case .group = author.info {
if adAttribute.messageId != nil { if case let .peer(_, messageId, _) = adAttribute.target, messageId != nil {
actionTitle = item.presentationData.strings.Conversation_ViewPost actionTitle = item.presentationData.strings.Conversation_ViewPost
} else { } else {
actionTitle = item.presentationData.strings.Conversation_ViewGroup actionTitle = item.presentationData.strings.Conversation_ViewGroup
} }
} else { } else {
if adAttribute.messageId != nil { if case let .peer(_, messageId, _) = adAttribute.target, messageId != nil {
actionTitle = item.presentationData.strings.Conversation_ViewMessage actionTitle = item.presentationData.strings.Conversation_ViewMessage
} else { } else {
actionTitle = item.presentationData.strings.Conversation_ViewChannel actionTitle = item.presentationData.strings.Conversation_ViewChannel

View File

@ -531,6 +531,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
}, updateChoosingSticker: { _ in }, updateChoosingSticker: { _ in
}, commitEmojiInteraction: { _, _, _, _ in }, commitEmojiInteraction: { _, _, _, _ in
}, openLargeEmojiInfo: { _, _, _ in }, openLargeEmojiInfo: { _, _, _ in
}, openJoinLink: { _ in
}, requestMessageUpdate: { _ in }, requestMessageUpdate: { _ in
}, cancelInteractiveKeyboardGestures: { }, cancelInteractiveKeyboardGestures: {
}, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings, }, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings,

View File

@ -157,6 +157,7 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode {
}, updateChoosingSticker: { _ in }, updateChoosingSticker: { _ in
}, commitEmojiInteraction: { _, _, _, _ in }, commitEmojiInteraction: { _, _, _, _ in
}, openLargeEmojiInfo: { _, _, _ in }, openLargeEmojiInfo: { _, _, _ in
}, openJoinLink: { _ in
}, requestMessageUpdate: { _ in }, requestMessageUpdate: { _ in
}, cancelInteractiveKeyboardGestures: { }, cancelInteractiveKeyboardGestures: {
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,

View File

@ -149,6 +149,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
}, updateChoosingSticker: { _ in }, updateChoosingSticker: { _ in
}, commitEmojiInteraction: { _, _, _, _ in }, commitEmojiInteraction: { _, _, _, _ in
}, openLargeEmojiInfo: { _, _, _ in }, openLargeEmojiInfo: { _, _, _ in
}, openJoinLink: { _ in
}, requestMessageUpdate: { _ in }, requestMessageUpdate: { _ in
}, cancelInteractiveKeyboardGestures: { }, cancelInteractiveKeyboardGestures: {
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(loopAnimatedStickers: false), presentationContext: ChatPresentationContext(backgroundNode: nil)) }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(loopAnimatedStickers: false), presentationContext: ChatPresentationContext(backgroundNode: nil))

View File

@ -2241,6 +2241,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
}, updateChoosingSticker: { _ in }, updateChoosingSticker: { _ in
}, commitEmojiInteraction: { _, _, _, _ in }, commitEmojiInteraction: { _, _, _, _ in
}, openLargeEmojiInfo: { _, _, _ in }, openLargeEmojiInfo: { _, _, _ in
}, openJoinLink: { _ in
}, requestMessageUpdate: { _ in }, requestMessageUpdate: { _ in
}, cancelInteractiveKeyboardGestures: { }, cancelInteractiveKeyboardGestures: {
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,

View File

@ -1294,6 +1294,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
}, updateChoosingSticker: { _ in }, updateChoosingSticker: { _ in
}, commitEmojiInteraction: { _, _, _, _ in }, commitEmojiInteraction: { _, _, _, _ in
}, openLargeEmojiInfo: { _, _, _ in }, openLargeEmojiInfo: { _, _, _ in
}, openJoinLink: { _ in
}, requestMessageUpdate: { _ in }, requestMessageUpdate: { _ in
}, cancelInteractiveKeyboardGestures: { }, cancelInteractiveKeyboardGestures: {
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,