Initial reactions implementation

This commit is contained in:
Ali 2021-11-30 22:18:02 +04:00
parent 37e869f8f2
commit 3dc4efbfcb
60 changed files with 2808 additions and 1702 deletions

View File

@ -26,8 +26,10 @@ public final class ChatMessageItemAssociatedData: Equatable {
public let forcedResourceStatus: FileMediaResourceStatus?
public let currentlyPlayingMessageId: EngineMessage.Index?
public let isCopyProtectionEnabled: Bool
public let availableReactions: AvailableReactions?
public let defaultReaction: String?
public init(automaticDownloadPeerType: MediaAutoDownloadPeerType, automaticDownloadNetworkType: MediaAutoDownloadNetworkType, isRecentActions: Bool = false, subject: ChatControllerSubject? = nil, contactsPeerIds: Set<EnginePeer.Id> = Set(), channelDiscussionGroup: ChannelDiscussionGroupStatus = .unknown, animatedEmojiStickers: [String: [StickerPackItem]] = [:], additionalAnimatedEmojiStickers: [String: [Int: StickerPackItem]] = [:], forcedResourceStatus: FileMediaResourceStatus? = nil, currentlyPlayingMessageId: EngineMessage.Index? = nil, isCopyProtectionEnabled: Bool = false) {
public init(automaticDownloadPeerType: MediaAutoDownloadPeerType, automaticDownloadNetworkType: MediaAutoDownloadNetworkType, isRecentActions: Bool = false, subject: ChatControllerSubject? = nil, contactsPeerIds: Set<EnginePeer.Id> = Set(), channelDiscussionGroup: ChannelDiscussionGroupStatus = .unknown, animatedEmojiStickers: [String: [StickerPackItem]] = [:], additionalAnimatedEmojiStickers: [String: [Int: StickerPackItem]] = [:], forcedResourceStatus: FileMediaResourceStatus? = nil, currentlyPlayingMessageId: EngineMessage.Index? = nil, isCopyProtectionEnabled: Bool = false, availableReactions: AvailableReactions?) {
self.automaticDownloadPeerType = automaticDownloadPeerType
self.automaticDownloadNetworkType = automaticDownloadNetworkType
self.isRecentActions = isRecentActions
@ -39,6 +41,17 @@ public final class ChatMessageItemAssociatedData: Equatable {
self.forcedResourceStatus = forcedResourceStatus
self.currentlyPlayingMessageId = currentlyPlayingMessageId
self.isCopyProtectionEnabled = isCopyProtectionEnabled
self.availableReactions = availableReactions
var defaultReaction: String?
if let availableReactions = availableReactions {
for reaction in availableReactions.reactions {
if reaction.title.lowercased().contains("heart") {
defaultReaction = reaction.value
}
}
}
self.defaultReaction = defaultReaction
}
public static func == (lhs: ChatMessageItemAssociatedData, rhs: ChatMessageItemAssociatedData) -> Bool {
@ -75,6 +88,9 @@ public final class ChatMessageItemAssociatedData: Equatable {
if lhs.isCopyProtectionEnabled != rhs.isCopyProtectionEnabled {
return false
}
if lhs.availableReactions != rhs.availableReactions {
return false
}
return true
}
}

View File

@ -1,5 +1,19 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
config_setting(
name = "debug_build",
values = {
"compilation_mode": "dbg",
},
)
optimization_flags = select({
":debug_build": [
"-O",
],
"//conditions:default": [],
})
swift_library(
name = "AnimatedStickerNode",
module_name = "AnimatedStickerNode",
@ -8,7 +22,7 @@ swift_library(
]),
copts = [
"-warnings-as-errors",
],
] + optimization_flags,
deps = [
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
"//submodules/AsyncDisplayKit:AsyncDisplayKit",

View File

@ -104,6 +104,10 @@ public protocol _TypeErasedComponent {
func _isEqual(to other: _TypeErasedComponent) -> Bool
}
public protocol ComponentTaggedView: UIView {
func matches(tag: Any) -> Bool
}
public protocol Component: _TypeErasedComponent, Equatable {
associatedtype EnvironmentType = Empty
associatedtype View: UIView = UIView

View File

@ -1,6 +1,22 @@
import Foundation
import UIKit
private func findTaggedViewImpl(view: UIView, tag: Any) -> UIView? {
if let view = view as? ComponentTaggedView {
if view.matches(tag: tag) {
return view
}
}
for subview in view.subviews {
if let result = findTaggedViewImpl(view: subview, tag: tag) {
return result
}
}
return nil
}
public final class ComponentHostView<EnvironmentType>: UIView {
private var currentComponent: AnyComponent<EnvironmentType>?
private var currentContainerSize: CGSize?
@ -78,4 +94,12 @@ public final class ComponentHostView<EnvironmentType>: UIView {
let result = super.hitTest(point, with: event)
return result
}
public func findTaggedView(tag: Any) -> UIView? {
guard let componentView = self.componentView else {
return nil
}
return findTaggedViewImpl(view: componentView, tag: tag)
}
}

View File

@ -0,0 +1,25 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "ReactionButtonListComponent",
module_name = "ReactionButtonListComponent",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/Display:Display",
"//submodules/ComponentFlow:ComponentFlow",
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
"//submodules/Postbox:Postbox",
"//submodules/TelegramCore:TelegramCore",
"//submodules/AccountContext:AccountContext",
"//submodules/TelegramPresentationData:TelegramPresentationData",
"//submodules/WebPBinding:WebPBinding",
],
visibility = [
"//visibility:public",
],
)

View File

@ -0,0 +1,322 @@
import Foundation
import Display
import ComponentFlow
import SwiftSignalKit
import Postbox
import TelegramCore
import AccountContext
import TelegramPresentationData
import UIKit
import WebPBinding
public final class ReactionButtonComponent: Component {
public struct ViewTag: Equatable {
public var value: String
public init(value: String) {
self.value = value
}
}
public struct Reaction: Equatable {
public var value: String
public var iconFile: TelegramMediaFile?
public init(value: String, iconFile: TelegramMediaFile?) {
self.value = value
self.iconFile = iconFile
}
public static func ==(lhs: Reaction, rhs: Reaction) -> Bool {
if lhs.value != rhs.value {
return false
}
if lhs.iconFile?.fileId != rhs.iconFile?.fileId {
return false
}
return true
}
}
public struct Colors: Equatable {
public var background: UInt32
public var foreground: UInt32
public var stroke: UInt32
public init(
background: UInt32,
foreground: UInt32,
stroke: UInt32
) {
self.background = background
self.foreground = foreground
self.stroke = stroke
}
}
public let context: AccountContext
public let colors: Colors
public let reaction: Reaction
public let count: Int
public let isSelected: Bool
public let action: (String) -> Void
public init(
context: AccountContext,
colors: Colors,
reaction: Reaction,
count: Int,
isSelected: Bool,
action: @escaping (String) -> Void
) {
self.context = context
self.colors = colors
self.reaction = reaction
self.count = count
self.isSelected = isSelected
self.action = action
}
public static func ==(lhs: ReactionButtonComponent, rhs: ReactionButtonComponent) -> Bool {
if lhs.context !== rhs.context {
return false
}
if lhs.colors != rhs.colors {
return false
}
if lhs.reaction != rhs.reaction {
return false
}
if lhs.count != rhs.count {
return false
}
if lhs.isSelected != rhs.isSelected {
return false
}
return true
}
public final class View: UIButton, ComponentTaggedView {
public let iconView: UIImageView
private let textView: ComponentHostView<Empty>
private var currentComponent: ReactionButtonComponent?
private let iconImageDisposable = MetaDisposable()
init() {
self.iconView = UIImageView()
self.iconView.isUserInteractionEnabled = false
self.textView = ComponentHostView<Empty>()
self.textView.isUserInteractionEnabled = false
super.init(frame: CGRect())
self.addSubview(self.iconView)
self.addSubview(self.textView)
self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
}
required init?(coder aDecoder: NSCoder) {
preconditionFailure()
}
deinit {
self.iconImageDisposable.dispose()
}
@objc private func pressed() {
guard let currentComponent = self.currentComponent else {
return
}
currentComponent.action(currentComponent.reaction.value)
}
public func matches(tag: Any) -> Bool {
guard let tag = tag as? ViewTag else {
return false
}
guard let currentComponent = self.currentComponent else {
return false
}
if currentComponent.reaction.value == tag.value {
return true
}
return false
}
func update(component: ReactionButtonComponent, availableSize: CGSize, environment: Environment<Empty>, transition: Transition) -> CGSize {
let sideInsets: CGFloat = 10.0
let height: CGFloat = 30.0
let spacing: CGFloat = 2.0
let defaultImageSize = CGSize(width: 20.0, height: 20.0)
let imageSize: CGSize
if self.currentComponent?.reaction != component.reaction {
if let file = component.reaction.iconFile {
self.iconImageDisposable.set((component.context.account.postbox.mediaBox.resourceData(file.resource)
|> deliverOnMainQueue).start(next: { [weak self] data in
guard let strongSelf = self else {
return
}
if data.complete, let dataValue = try? Data(contentsOf: URL(fileURLWithPath: data.path)) {
if let image = WebP.convert(fromWebP: dataValue) {
strongSelf.iconView.image = image
}
}
}))
imageSize = file.dimensions?.cgSize.aspectFitted(defaultImageSize) ?? defaultImageSize
} else {
imageSize = defaultImageSize
}
} else {
imageSize = self.iconView.bounds.size
}
self.iconView.frame = CGRect(origin: CGPoint(x: sideInsets, y: floorToScreenPixels((height - imageSize.height) / 2.0)), size: imageSize)
let textSize: CGSize
if self.currentComponent?.count != component.count || self.currentComponent?.colors != component.colors {
textSize = self.textView.update(
transition: .immediate,
component: AnyComponent(Text(
text: "\(component.count)",
font: Font.regular(13.0),
color: UIColor(argb: component.colors.foreground)
)),
environment: {},
containerSize: CGSize(width: 100.0, height: 100.0)
)
} else {
textSize = self.textView.bounds.size
}
if self.currentComponent?.colors != component.colors {
self.backgroundColor = UIColor(argb: component.colors.background)
}
if self.currentComponent?.colors != component.colors || self.currentComponent?.isSelected != component.isSelected {
if component.isSelected {
self.layer.borderColor = UIColor(argb: component.colors.stroke).cgColor
self.layer.borderWidth = 1.5
} else {
self.layer.borderColor = nil
self.layer.borderWidth = 0.0
}
}
self.layer.cornerRadius = height / 2.0
self.textView.frame = CGRect(origin: CGPoint(x: sideInsets + imageSize.width + spacing, y: floorToScreenPixels((height - textSize.height) / 2.0)), size: textSize)
self.currentComponent = component
return CGSize(width: imageSize.width + spacing + textSize.width + sideInsets * 2.0, height: height)
}
}
public func makeView() -> View {
return View()
}
public func update(view: View, availableSize: CGSize, environment: Environment<Empty>, transition: Transition) -> CGSize {
return view.update(component: self, availableSize: availableSize, environment: environment, transition: transition)
}
}
public final class ReactionButtonsLayoutContainer {
public struct Reaction {
public var reaction: ReactionButtonComponent.Reaction
public var count: Int
public var isSelected: Bool
public init(
reaction: ReactionButtonComponent.Reaction,
count: Int,
isSelected: Bool
) {
self.reaction = reaction
self.count = count
self.isSelected = isSelected
}
}
public struct Result {
public struct Item {
public var view: ComponentHostView<Empty>
public var size: CGSize
}
public var items: [Item]
public var removedViews: [ComponentHostView<Empty>]
}
public private(set) var buttons: [String: ComponentHostView<Empty>] = [:]
public init() {
}
public func update(
context: AccountContext,
action: @escaping (String) -> Void,
reactions: [Reaction],
colors: ReactionButtonComponent.Colors,
constrainedWidth: CGFloat,
transition: Transition
) -> Result {
var items: [Result.Item] = []
var removedViews: [ComponentHostView<Empty>] = []
var validIds = Set<String>()
for reaction in reactions {
validIds.insert(reaction.reaction.value)
let view: ComponentHostView<Empty>
var itemTransition = transition
if let current = self.buttons[reaction.reaction.value] {
itemTransition = .immediate
view = current
} else {
view = ComponentHostView<Empty>()
self.buttons[reaction.reaction.value] = view
}
let itemSize = view.update(
transition: itemTransition,
component: AnyComponent(ReactionButtonComponent(
context: context,
colors: colors,
reaction: reaction.reaction,
count: reaction.count,
isSelected: reaction.isSelected,
action: action
)),
environment: {},
containerSize: CGSize(width: constrainedWidth, height: 1000.0)
)
items.append(Result.Item(
view: view,
size: itemSize
))
}
var removeIds: [String] = []
for (id, view) in self.buttons {
if !validIds.contains(id) {
removeIds.append(id)
removedViews.append(view)
}
}
for id in removeIds {
self.buttons.removeValue(forKey: id)
}
return Result(
items: items,
removedViews: removedViews
)
}
}

View File

@ -1126,7 +1126,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
}
}
func animateOutToReaction(value: String, targetEmptyNode: ASDisplayNode, targetFilledNode: ASDisplayNode, hideNode: Bool, completion: @escaping () -> Void) {
func animateOutToReaction(value: String, targetView: UIView, hideNode: Bool, completion: @escaping () -> Void) {
guard let reactionContextNode = self.reactionContextNode else {
self.animateOut(result: .default, completion: completion)
return
@ -1141,7 +1141,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
self.reactionContextNodeIsAnimatingOut = true
reactionContextNode.willAnimateOutToReaction(value: value)
reactionContextNode.animateOutToReaction(value: value, targetEmptyNode: targetEmptyNode, targetFilledNode: targetFilledNode, hideNode: hideNode, completion: { [weak self] in
reactionContextNode.animateOutToReaction(value: value, targetView: targetView, hideNode: hideNode, completion: { [weak self] in
guard let strongSelf = self else {
return
}
@ -2154,10 +2154,10 @@ public final class ContextController: ViewController, StandalonePresentableContr
self.dismiss(result: .default, completion: completion)
}
public func dismissWithReaction(value: String, targetEmptyNode: ASDisplayNode, targetFilledNode: ASDisplayNode, hideNode: Bool, completion: (() -> Void)?) {
public func dismissWithReaction(value: String, targetView: UIView, hideNode: Bool, completion: (() -> Void)?) {
if !self.wasDismissed {
self.wasDismissed = true
self.controllerNode.animateOutToReaction(value: value, targetEmptyNode: targetEmptyNode, targetFilledNode: targetFilledNode, hideNode: hideNode, completion: { [weak self] in
self.controllerNode.animateOutToReaction(value: value, targetView: targetView, hideNode: hideNode, completion: { [weak self] in
self?.presentingViewController?.dismiss(animated: false, completion: nil)
completion?()
})

View File

@ -1575,7 +1575,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
}
private func async(_ f: @escaping () -> Void) {
DispatchQueue.global().async(execute: f)
DispatchQueue.main.async(execute: f)
}
private func nodeForItem(synchronous: Bool, synchronousLoads: Bool, item: ListViewItem, previousNode: QueueLocalObject<ListViewItemNode>?, index: Int, previousItem: ListViewItem?, nextItem: ListViewItem?, params: ListViewItemLayoutParams, updateAnimation: ListViewItemUpdateAnimation, completion: @escaping (QueueLocalObject<ListViewItemNode>, ListViewItemNodeLayout, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {

View File

@ -166,7 +166,7 @@ public struct PeerId: Hashable, CustomStringConvertible, Comparable, Codable {
let offsetIdHighBits = (data >> (32 + 3)) & 0xffffffff
let idHighBits = offsetIdHighBits << 32
if idHighBits == 0 && namespaceBits != 0 {
if idHighBits == 0 && namespaceBits == 3 {
if let uint32Value = UInt32(exactly: idLowBits) {
self.id = Id(rawValue: Int64(Int32(bitPattern: uint32Value)))
} else {

View File

@ -22,15 +22,18 @@ public final class ReactionContextItem {
}
public let reaction: ReactionContextItem.Reaction
public let stillAnimation: TelegramMediaFile
public let listAnimation: TelegramMediaFile
public let applicationAnimation: TelegramMediaFile
public init(
reaction: ReactionContextItem.Reaction,
stillAnimation: TelegramMediaFile,
listAnimation: TelegramMediaFile,
applicationAnimation: TelegramMediaFile
) {
self.reaction = reaction
self.stillAnimation = stillAnimation
self.listAnimation = listAnimation
self.applicationAnimation = applicationAnimation
}
@ -477,11 +480,11 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
return keyframes
}
private func animateFromItemNodeToReaction(itemNode: ReactionNode, targetFilledNode: ASDisplayNode, targetSnapshotView: UIView, hideNode: Bool, completion: @escaping () -> Void) {
private func animateFromItemNodeToReaction(itemNode: ReactionNode, targetView: UIView, targetSnapshotView: UIView, hideNode: Bool, completion: @escaping () -> Void) {
let itemFrame: CGRect = itemNode.frame
let _ = itemFrame
let targetFrame = self.view.convert(targetFilledNode.view.convert(targetFilledNode.bounds, to: nil), from: nil)
let targetFrame = self.view.convert(targetView.convert(targetView.bounds, to: nil), from: nil)
targetSnapshotView.frame = targetFrame
self.view.insertSubview(targetSnapshotView, belowSubview: itemNode.view)
@ -507,26 +510,21 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
completedTarget = true
intermediateCompletion()
targetSnapshotView?.isHidden = true
if hideNode {
targetFilledNode.isHidden = false
targetFilledNode.layer.animateSpring(from: 0.5 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: duration, initialVelocity: 0.0, damping: 90.0, completion: { _ in
targetView.isHidden = false
targetView.layer.animateSpring(from: 0.5 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: duration, initialVelocity: 0.0, damping: 90.0, completion: { _ in
targetSnapshotView?.isHidden = true
targetScaleCompleted = true
intermediateCompletion()
})
} else {
targetSnapshotView?.isHidden = true
targetScaleCompleted = true
intermediateCompletion()
}
})
//let keyframes = self.generateParabollicMotionKeyframes(from: targetPosition, to: targetPosition, elevation: 30.0)
/*itemNode.layer.animateKeyframes(values: keyframes, duration: duration, keyPath: "position", removeOnCompletion: false, completion: { _ in
})
targetSnapshotView.layer.animateKeyframes(values: keyframes, duration: duration, keyPath: "position", removeOnCompletion: false)*/
itemNode.layer.animateScale(from: 1.0, to: (targetSnapshotView.bounds.width * 0.5) / itemNode.bounds.width, duration: duration, removeOnCompletion: false)
}
@ -539,19 +537,19 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
}
}
public func animateOutToReaction(value: String, targetEmptyNode: ASDisplayNode, targetFilledNode: ASDisplayNode, hideNode: Bool, completion: @escaping () -> Void) {
public func animateOutToReaction(value: String, targetView: UIView, hideNode: Bool, completion: @escaping () -> Void) {
for itemNode in self.itemNodes {
if itemNode.item.reaction.rawValue != value {
continue
}
if let targetSnapshotView = targetFilledNode.view.snapshotContentTree() {
if let targetSnapshotView = targetView.snapshotContentTree() {
if hideNode {
targetFilledNode.isHidden = true
targetView.isHidden = true
}
itemNode.isExtracted = true
let selfSourceRect = itemNode.view.convert(itemNode.view.bounds, to: self.view)
let selfTargetRect = self.view.convert(targetFilledNode.bounds, from: targetFilledNode.view)
let selfTargetRect = self.view.convert(targetView.bounds, from: targetView)
let expandedScale: CGFloat = 3.0
let expandedSize = CGSize(width: floor(selfSourceRect.width * expandedScale), height: floor(selfSourceRect.height * expandedScale))
@ -568,11 +566,11 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
transition.animatePositionWithKeyframes(node: itemNode, keyframes: self.generateParabollicMotionKeyframes(from: selfSourceRect.center, to: expandedFrame.center, elevation: 30.0))
let additionalAnimationNode = AnimatedStickerNode()
let incomingMessage: Bool = self.isLeftAligned
let incomingMessage: Bool = expandedFrame.midX < self.bounds.width / 2.0
let animationFrame = expandedFrame.insetBy(dx: -expandedFrame.width * 0.5, dy: -expandedFrame.height * 0.5)
.offsetBy(dx: incomingMessage ? (expandedFrame.width - 50.0) : (-expandedFrame.width + 50.0), dy: 0.0)
//animationFrame = animationFrame.offsetBy(dx: CGFloat.random(in: -30.0 ... 30.0), dy: CGFloat.random(in: -30.0 ... 30.0))
additionalAnimationNode.setup(source: AnimatedStickerResourceSource(account: itemNode.context.account, resource: itemNode.item.applicationAnimation.resource), width: Int(animationFrame.width * 2.0), height: Int(animationFrame.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: nil))
additionalAnimationNode.setup(source: AnimatedStickerResourceSource(account: itemNode.context.account, resource: itemNode.item.applicationAnimation.resource), width: Int(animationFrame.width * 2.0), height: Int(animationFrame.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: itemNode.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(itemNode.item.applicationAnimation.resource.id)))
additionalAnimationNode.frame = animationFrame
if incomingMessage {
additionalAnimationNode.transform = CATransform3DMakeScale(-1.0, 1.0, 1.0)
@ -598,7 +596,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
})
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2.0 * UIView.animationDurationFactor(), execute: {
self.animateFromItemNodeToReaction(itemNode: itemNode, targetFilledNode: targetFilledNode, targetSnapshotView: targetSnapshotView, hideNode: hideNode, completion: {
self.animateFromItemNodeToReaction(itemNode: itemNode, targetView: targetView, targetSnapshotView: targetSnapshotView, hideNode: hideNode, completion: {
mainAnimationCompleted = true
intermediateCompletion()
})
@ -614,11 +612,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
if self.contentContainer.bounds.contains(contentPoint) {
return self.contentContainer.hitTest(contentPoint, with: event)
}
/*for itemNode in self.itemNodes {
if !itemNode.alpha.isZero && itemNode.frame.contains(contentPoint) {
return self.view
}
}*/
return nil
}
@ -659,3 +653,135 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
}
}
public final class StandaloneReactionAnimation: ASDisplayNode {
private let itemNode: ReactionNode
private let hapticFeedback = HapticFeedback()
public init(context: AccountContext, theme: PresentationTheme, reaction: ReactionContextItem) {
self.itemNode = ReactionNode(context: context, theme: theme, item: reaction)
super.init()
self.isUserInteractionEnabled = false
self.addSubnode(self.itemNode)
}
public func animateReactionSelection(targetView: UIView, hideNode: Bool, completion: @escaping () -> Void) {
guard let targetSnapshotView = targetView.snapshotContentTree() else {
completion()
return
}
if hideNode {
targetView.isHidden = true
}
self.itemNode.isExtracted = true
let sourceItemSize: CGFloat = 40.0
let selfTargetRect = self.view.convert(targetView.bounds, from: targetView)
let expandedScale: CGFloat = 3.0
let expandedSize = CGSize(width: floor(sourceItemSize * expandedScale), height: floor(sourceItemSize * expandedScale))
let expandedFrame = CGRect(origin: CGPoint(x: floor(selfTargetRect.midX - expandedSize.width / 2.0), y: floor(selfTargetRect.midY - expandedSize.height / 2.0)), size: expandedSize)
self.addSubnode(itemNode)
itemNode.frame = expandedFrame
itemNode.updateLayout(size: expandedFrame.size, isExpanded: true, transition: .immediate)
itemNode.layer.animateScale(from: 0.1, to: 1.0, duration: 0.18)
itemNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.18)
let additionalAnimationNode = AnimatedStickerNode()
let incomingMessage: Bool = expandedFrame.midX < self.bounds.width / 2.0
let animationFrame = expandedFrame.insetBy(dx: -expandedFrame.width * 0.5, dy: -expandedFrame.height * 0.5)
.offsetBy(dx: incomingMessage ? (expandedFrame.width - 50.0) : (-expandedFrame.width + 50.0), dy: 0.0)
additionalAnimationNode.setup(source: AnimatedStickerResourceSource(account: self.itemNode.context.account, resource: self.itemNode.item.applicationAnimation.resource), width: Int(animationFrame.width * 2.0), height: Int(animationFrame.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: self.itemNode.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.itemNode.item.applicationAnimation.resource.id)))
additionalAnimationNode.frame = animationFrame
if incomingMessage {
additionalAnimationNode.transform = CATransform3DMakeScale(-1.0, 1.0, 1.0)
}
additionalAnimationNode.updateLayout(size: animationFrame.size)
self.addSubnode(additionalAnimationNode)
additionalAnimationNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.18)
var mainAnimationCompleted = false
var additionalAnimationCompleted = false
let intermediateCompletion: () -> Void = {
if mainAnimationCompleted && additionalAnimationCompleted {
completion()
}
}
additionalAnimationNode.completed = { _ in
additionalAnimationCompleted = true
intermediateCompletion()
}
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1 * UIView.animationDurationFactor(), execute: {
additionalAnimationNode.visibility = true
})
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2.0 * UIView.animationDurationFactor(), execute: {
self.animateFromItemNodeToReaction(itemNode: self.itemNode, targetView: targetView, targetSnapshotView: targetSnapshotView, hideNode: hideNode, completion: {
mainAnimationCompleted = true
intermediateCompletion()
})
})
}
private func animateFromItemNodeToReaction(itemNode: ReactionNode, targetView: UIView, targetSnapshotView: UIView, hideNode: Bool, completion: @escaping () -> Void) {
let itemFrame: CGRect = itemNode.frame
let _ = itemFrame
let targetFrame = self.view.convert(targetView.convert(targetView.bounds, to: nil), from: nil)
targetSnapshotView.frame = targetFrame
self.view.insertSubview(targetSnapshotView, belowSubview: itemNode.view)
var completedTarget = false
var targetScaleCompleted = false
let intermediateCompletion: () -> Void = {
if completedTarget && targetScaleCompleted {
completion()
}
}
let targetPosition = targetFrame.center
let _ = targetPosition
let duration: Double = 0.16
itemNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration * 0.9, removeOnCompletion: false)
targetSnapshotView.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration * 0.8)
targetSnapshotView.layer.animateScale(from: itemNode.bounds.width / targetSnapshotView.bounds.width, to: 0.5, duration: duration, removeOnCompletion: false, completion: { [weak self, weak targetSnapshotView] _ in
if let strongSelf = self {
strongSelf.hapticFeedback.tap()
}
completedTarget = true
intermediateCompletion()
targetSnapshotView?.isHidden = true
if hideNode {
targetView.isHidden = false
targetView.layer.animateSpring(from: 0.5 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: duration, initialVelocity: 0.0, damping: 90.0, completion: { _ in
targetSnapshotView?.isHidden = true
targetScaleCompleted = true
intermediateCompletion()
})
} else {
targetScaleCompleted = true
intermediateCompletion()
}
})
itemNode.layer.animateScale(from: 1.0, to: (targetSnapshotView.bounds.width * 0.5) / itemNode.bounds.width, duration: duration, removeOnCompletion: false)
}
public func addRelativeContentOffset(_ offset: CGPoint, transition: ContainedViewLayoutTransition) {
self.bounds = self.bounds.offsetBy(dx: 0.0, dy: offset.y)
transition.animateOffsetAdditive(node: self, offset: -offset.y)
}
}

View File

@ -39,6 +39,7 @@ final class ReactionNode: ASDisplayNode {
let context: AccountContext
let item: ReactionContextItem
private let staticImageNode: TransformImageNode
private let stillAnimationNode: AnimatedStickerNode
private var animationNode: AnimatedStickerNode?
private var fetchStickerDisposable: Disposable?
@ -48,11 +49,14 @@ final class ReactionNode: ASDisplayNode {
var isExtracted: Bool = false
var didSetupStillAnimation: Bool = false
init(context: AccountContext, theme: PresentationTheme, item: ReactionContextItem) {
self.context = context
self.item = item
self.staticImageNode = TransformImageNode()
self.stillAnimationNode = AnimatedStickerNode()
super.init()
@ -60,14 +64,18 @@ final class ReactionNode: ASDisplayNode {
self.addSubnode(self.staticImageNode)
/*self.addSubnode(self.animationNode)
self.addSubnode(self.stillAnimationNode)
self.animationNode.updateLayout(size: self.intrinsicSize)
self.animationNode.frame = CGRect(origin: CGPoint(), size: self.intrinsicSize)*/
self.stillAnimationNode.started = { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.staticImageNode.isHidden = true
}
self.fetchStickerDisposable = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, reference: .standalone(resource: item.stillAnimation.resource)).start()
self.fetchStickerDisposable = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, reference: .standalone(resource: item.listAnimation.resource)).start()
self.fetchFullAnimationDisposable = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, reference: .standalone(resource: item.applicationAnimation.resource)).start()
//let _ = context.meshAnimationCache.get(resource: item.applicationAnimation.resource)
}
deinit {
@ -81,35 +89,8 @@ final class ReactionNode: ASDisplayNode {
let animationSize = self.item.listAnimation.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0)
var animationDisplaySize = animationSize.aspectFitted(intrinsicSize)
var scalingFactor: CGFloat = 1.0
var offsetFactor: CGFloat = 0.0
switch self.item.reaction.rawValue {
case "💸":
scalingFactor = 1.25
offsetFactor = -0.04
case "👍":
scalingFactor = 1.4
offsetFactor = 0.02
case "👎":
scalingFactor = 1.4
offsetFactor = -0.01
case "😂":
scalingFactor = 1.2
case "🍆":
scalingFactor = 1.1
offsetFactor = -0.01
case "👻":
scalingFactor = 1.2
case "🎃":
scalingFactor = 1.15
offsetFactor = -0.08
case "🎈":
offsetFactor = 0.03
case "🎉":
offsetFactor = -0.01
default:
break
}
let scalingFactor: CGFloat = 1.0
let offsetFactor: CGFloat = 0.0
animationDisplaySize.width = floor(animationDisplaySize.width * scalingFactor)
animationDisplaySize.height = floor(animationDisplaySize.height * scalingFactor)
@ -129,7 +110,7 @@ final class ReactionNode: ASDisplayNode {
strongSelf.staticImageNode.isHidden = true
}
animationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.item.listAnimation.resource), width: Int(size.width * 2.0), height: Int(size.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: nil))
animationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.item.listAnimation.resource), width: Int(animationDisplaySize.width * 2.0), height: Int(animationDisplaySize.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.item.listAnimation.resource.id)))
animationNode.frame = animationFrame
animationNode.updateLayout(size: animationFrame.size)
if transition.isAnimated, !self.staticImageNode.frame.isEmpty {
@ -137,355 +118,38 @@ final class ReactionNode: ASDisplayNode {
transition.animatePositionAdditive(node: animationNode, offset: CGPoint(x: self.staticImageNode.frame.midX - animationFrame.midX, y: self.staticImageNode.frame.midY - animationFrame.midY))
}
animationNode.visibility = true
self.stillAnimationNode.alpha = 0.0
self.stillAnimationNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak self] _ in
self?.stillAnimationNode.visibility = false
})
animationNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
}
if self.validSize != size {
self.validSize = size
self.staticImageNode.setSignal(chatMessageAnimatedSticker(postbox: self.context.account.postbox, file: item.listAnimation, small: false, size: CGSize(width: animationDisplaySize.width * UIScreenScale, height: animationDisplaySize.height * UIScreenScale), fitzModifier: nil, fetched: false, onlyFullSize: false, thumbnail: false, synchronousLoad: false))
self.staticImageNode.setSignal(chatMessageAnimatedSticker(postbox: self.context.account.postbox, file: item.stillAnimation, small: false, size: CGSize(width: animationDisplaySize.width * UIScreenScale, height: animationDisplaySize.height * UIScreenScale), fitzModifier: nil, fetched: false, onlyFullSize: false, thumbnail: false, synchronousLoad: false))
let imageApply = self.staticImageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: animationDisplaySize, boundingSize: animationDisplaySize, intrinsicInsets: UIEdgeInsets()))
imageApply()
transition.updateFrame(node: self.staticImageNode, frame: animationFrame)
}
if !self.didSetupStillAnimation {
self.didSetupStillAnimation = true
self.stillAnimationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.item.stillAnimation.resource), width: Int(animationDisplaySize.width * 2.0), height: Int(animationDisplaySize.height * 2.0), playbackMode: .loop, mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.item.stillAnimation.resource.id)))
self.stillAnimationNode.position = animationFrame.center
self.stillAnimationNode.bounds = CGRect(origin: CGPoint(), size: animationFrame.size)
self.stillAnimationNode.updateLayout(size: animationFrame.size)
self.stillAnimationNode.visibility = true
} else {
transition.updatePosition(node: self.stillAnimationNode, position: animationFrame.center)
transition.updateTransformScale(node: self.stillAnimationNode, scale: animationFrame.size.width / self.stillAnimationNode.bounds.width)
}
}
func didAppear() {
}
}
/*final class ReactionSelectionNode: ASDisplayNode {
private let account: Account
private let theme: PresentationTheme
private let reactions: [ReactionGestureItem]
private let backgroundNode: ASImageNode
private let backgroundShadowNode: ASImageNode
private let bubbleNodes: [(ASImageNode, ASImageNode)]
private var reactionNodes: [ReactionNode] = []
private var hasSelectedNode = false
private let hapticFeedback = HapticFeedback()
private var shadowBlur: CGFloat = 8.0
private var minimizedReactionSize: CGFloat = 28.0
private var smallCircleSize: CGFloat = 14.0
private var isRightAligned: Bool = false
public init(account: Account, theme: PresentationTheme, reactions: [ReactionGestureItem]) {
self.account = account
self.theme = theme
self.reactions = reactions
self.backgroundNode = ASImageNode()
self.backgroundNode.displaysAsynchronously = false
self.backgroundNode.displayWithoutProcessing = true
self.backgroundShadowNode = ASImageNode()
self.backgroundShadowNode.displaysAsynchronously = false
self.backgroundShadowNode.displayWithoutProcessing = true
self.bubbleNodes = (0 ..< 2).map { i -> (ASImageNode, ASImageNode) in
let imageNode = ASImageNode()
imageNode.displaysAsynchronously = false
imageNode.displayWithoutProcessing = true
let shadowNode = ASImageNode()
shadowNode.displaysAsynchronously = false
shadowNode.displayWithoutProcessing = true
return (imageNode, shadowNode)
}
super.init()
self.bubbleNodes.forEach { _, shadow in
//self.addSubnode(shadow)
}
self.addSubnode(self.backgroundShadowNode)
self.bubbleNodes.forEach { foreground, _ in
//self.addSubnode(foreground)
}
self.addSubnode(self.backgroundNode)
}
func updateLayout(constrainedSize: CGSize, startingPoint: CGPoint, offsetFromStart: CGFloat, isInitial: Bool, touchPoint: CGPoint) {
let initialAnchorX = startingPoint.x
var isRightAligned = false
if initialAnchorX > constrainedSize.width / 2.0 {
isRightAligned = true
}
let reactionSideInset: CGFloat = 10.0
let reactionSpacing: CGFloat = 6.0
let minReactionSpacing: CGFloat = 2.0
let minimizedReactionSize = self.minimizedReactionSize
let contentWidth: CGFloat = CGFloat(self.reactions.count) * (minimizedReactionSize) + CGFloat(self.reactions.count - 1) * reactionSpacing + reactionSideInset * 2.0
let spaceForMaximizedReaction = CGFloat(self.reactions.count - 1) * reactionSpacing - CGFloat(self.reactions.count - 1) * minReactionSpacing
let maximizedReactionSize: CGFloat = minimizedReactionSize + spaceForMaximizedReaction
let backgroundHeight: CGFloat = floor(self.minimizedReactionSize * 1.8)
var backgroundFrame = CGRect(origin: CGPoint(x: -shadowBlur, y: -shadowBlur), size: CGSize(width: contentWidth + shadowBlur * 2.0, height: backgroundHeight + shadowBlur * 2.0))
if constrainedSize.width > 500.0 {
backgroundFrame = backgroundFrame.offsetBy(dx: constrainedSize.width - contentWidth - 44.0, dy: startingPoint.y - backgroundHeight - 12.0)
} else {
backgroundFrame = backgroundFrame.offsetBy(dx: floor((constrainedSize.width - contentWidth) / 2.0), dy: startingPoint.y - backgroundHeight - 12.0)
}
backgroundFrame.origin.x = max(0.0, backgroundFrame.minX)
backgroundFrame.origin.x = min(constrainedSize.width - backgroundFrame.width, backgroundFrame.minX)
let anchorMinX = backgroundFrame.minX + shadowBlur + backgroundHeight / 2.0
let anchorMaxX = backgroundFrame.maxX - shadowBlur - backgroundHeight / 2.0
let anchorX = max(anchorMinX, min(anchorMaxX, offsetFromStart))
var maximizedIndex = -1
/*if let reaction = self.reactions.last, case .reply = reaction {
maximizedIndex = self.reactions.count - 1
}*/
if backgroundFrame.insetBy(dx: -10.0, dy: -10.0).offsetBy(dx: 0.0, dy: 10.0).contains(touchPoint) {
maximizedIndex = Int(((touchPoint.x - anchorMinX) / (anchorMaxX - anchorMinX)) * CGFloat(self.reactionNodes.count))
maximizedIndex = max(0, min(self.reactionNodes.count - 1, maximizedIndex))
}
let interReactionSpacing: CGFloat
if maximizedIndex != -1 {
interReactionSpacing = minReactionSpacing
} else {
interReactionSpacing = reactionSpacing
}
if isInitial && self.reactionNodes.isEmpty {
self.shadowBlur = floor(minimizedReactionSize * 0.26)
self.smallCircleSize = 14.0
self.backgroundNode.image = generateBubbleImage(foreground: .white, diameter: backgroundHeight, shadowBlur: self.shadowBlur)
self.backgroundShadowNode.image = generateBubbleShadowImage(shadow: UIColor(white: 0.0, alpha: 0.2), diameter: backgroundHeight, shadowBlur: self.shadowBlur)
for i in 0 ..< self.bubbleNodes.count {
self.bubbleNodes[i].0.image = generateBubbleImage(foreground: .white, diameter: CGFloat(i + 1) * self.smallCircleSize, shadowBlur: self.shadowBlur)
self.bubbleNodes[i].1.image = generateBubbleShadowImage(shadow: UIColor(white: 0.0, alpha: 0.2), diameter: CGFloat(i + 1) * self.smallCircleSize, shadowBlur: self.shadowBlur)
}
self.reactionNodes = self.reactions.map { reaction -> ReactionNode in
return ReactionNode(account: self.account, theme: self.theme, reaction: reaction, maximizedReactionSize: maximizedReactionSize - 12.0, loadFirstFrame: true)
}
self.reactionNodes.forEach(self.addSubnode(_:))
}
let minimizedReactionVerticalInset: CGFloat = floor((backgroundHeight - minimizedReactionSize) / 2.0)
/*if maximizedIndex == -1 {
backgroundFrame.size.width -= maximizedReactionSize - minimizedReactionSize
backgroundFrame.origin.x += maximizedReactionSize - minimizedReactionSize
}*/
self.isRightAligned = isRightAligned
let backgroundTransition: ContainedViewLayoutTransition
if isInitial {
backgroundTransition = .immediate
} else {
backgroundTransition = .animated(duration: 0.18, curve: .easeInOut)
}
backgroundTransition.updateFrame(node: self.backgroundNode, frame: backgroundFrame)
backgroundTransition.updateFrame(node: self.backgroundShadowNode, frame: backgroundFrame)
var reactionX: CGFloat = backgroundFrame.minX + shadowBlur + reactionSideInset
if maximizedIndex != -1 {
self.hasSelectedNode = false
} else {
self.hasSelectedNode = true
}
for iterationIndex in 0 ..< self.reactionNodes.count {
var i = iterationIndex
let isMaximized = i == maximizedIndex
if !isRightAligned {
i = self.reactionNodes.count - 1 - i
}
let reactionSize: CGFloat
if isMaximized {
reactionSize = maximizedReactionSize
} else {
reactionSize = minimizedReactionSize
}
let transition: ContainedViewLayoutTransition
if isInitial {
transition = .immediate
} else {
transition = .animated(duration: 0.18, curve: .easeInOut)
}
if self.reactionNodes[i].isMaximized != isMaximized {
self.reactionNodes[i].isMaximized = isMaximized
self.reactionNodes[i].updateIsAnimating(isMaximized, animated: !isInitial)
if isMaximized && !isInitial {
self.hapticFeedback.tap()
}
}
var reactionFrame = CGRect(origin: CGPoint(x: reactionX, y: backgroundFrame.maxY - shadowBlur - minimizedReactionVerticalInset - reactionSize), size: CGSize(width: reactionSize, height: reactionSize))
if isMaximized {
reactionFrame.origin.x -= 7.0
reactionFrame.size.width += 14.0
}
self.reactionNodes[i].updateLayout(size: reactionFrame.size, scale: reactionFrame.size.width / (maximizedReactionSize + 14.0), transition: transition, displayText: isMaximized)
transition.updateFrame(node: self.reactionNodes[i], frame: reactionFrame, beginWithCurrentState: true)
reactionX += reactionSize + interReactionSpacing
}
let mainBubbleFrame = CGRect(origin: CGPoint(x: anchorX - self.smallCircleSize - shadowBlur, y: backgroundFrame.maxY - shadowBlur - self.smallCircleSize - shadowBlur), size: CGSize(width: self.smallCircleSize * 2.0 + shadowBlur * 2.0, height: self.smallCircleSize * 2.0 + shadowBlur * 2.0))
self.bubbleNodes[1].0.frame = mainBubbleFrame
self.bubbleNodes[1].1.frame = mainBubbleFrame
let secondaryBubbleFrame = CGRect(origin: CGPoint(x: mainBubbleFrame.midX - 10.0 - (self.smallCircleSize + shadowBlur * 2.0) / 2.0, y: mainBubbleFrame.midY + 10.0 - (self.smallCircleSize + shadowBlur * 2.0) / 2.0), size: CGSize(width: self.smallCircleSize + shadowBlur * 2.0, height: self.smallCircleSize + shadowBlur * 2.0))
self.bubbleNodes[0].0.frame = secondaryBubbleFrame
self.bubbleNodes[0].1.frame = secondaryBubbleFrame
}
func animateIn() {
self.bubbleNodes[1].0.layer.animateScale(from: 0.01, to: 1.0, duration: 0.11, delay: 0.0, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
self.bubbleNodes[1].1.layer.animateScale(from: 0.01, to: 1.0, duration: 0.11, delay: 0.0, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
self.bubbleNodes[0].0.layer.animateScale(from: 0.01, to: 1.0, duration: 0.11, delay: 0.05, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
self.bubbleNodes[0].1.layer.animateScale(from: 0.01, to: 1.0, duration: 0.11, delay: 0.05, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
let backgroundOffset: CGPoint
if self.isRightAligned {
backgroundOffset = CGPoint(x: (self.backgroundNode.frame.width - shadowBlur) / 2.0 - 42.0, y: 10.0)
} else {
backgroundOffset = CGPoint(x: -(self.backgroundNode.frame.width - shadowBlur) / 2.0 + 42.0, y: 10.0)
}
let damping: CGFloat = 100.0
for i in 0 ..< self.reactionNodes.count {
let animationOffset: Double = 1.0 - Double(i) / Double(self.reactionNodes.count - 1)
var nodeOffset: CGPoint
if self.isRightAligned {
nodeOffset = CGPoint(x: self.reactionNodes[i].frame.minX - (self.backgroundNode.frame.maxX - shadowBlur) / 2.0 - 42.0, y: 10.0)
} else {
nodeOffset = CGPoint(x: self.reactionNodes[i].frame.minX - (self.backgroundNode.frame.minX + shadowBlur) / 2.0 - 42.0, y: 10.0)
}
nodeOffset.x = 0.0
nodeOffset.y = 30.0
self.reactionNodes[i].layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.04, delay: animationOffset * 0.1)
self.reactionNodes[i].layer.animateSpring(from: 0.01 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5, delay: animationOffset * 0.1, initialVelocity: 0.0, damping: damping)
//self.reactionNodes[i].layer.animateSpring(from: NSValue(cgPoint: nodeOffset), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: 0.5, delay: animationOffset * 0.1, initialVelocity: 0.0, damping: damping, additive: true)
}
self.backgroundNode.layer.animateSpring(from: 0.01 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5, initialVelocity: 0.0, damping: damping)
self.backgroundNode.layer.animateSpring(from: NSValue(cgPoint: backgroundOffset), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: 0.5, initialVelocity: 0.0, damping: damping, additive: true)
self.backgroundShadowNode.layer.animateSpring(from: 0.01 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5, initialVelocity: 0.0, damping: damping)
self.backgroundShadowNode.layer.animateSpring(from: NSValue(cgPoint: backgroundOffset), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: 0.5, initialVelocity: 0.0, damping: damping, additive: true)
}
func animateOut(into targetNode: ASDisplayNode?, hideTarget: Bool, completion: @escaping () -> Void) {
self.hapticFeedback.prepareTap()
var completedContainer = false
var completedTarget = true
let intermediateCompletion: () -> Void = {
if completedContainer && completedTarget {
completion()
}
}
if let targetNode = targetNode {
for i in 0 ..< self.reactionNodes.count {
if let isMaximized = self.reactionNodes[i].isMaximized, isMaximized {
targetNode.recursivelyEnsureDisplaySynchronously(true)
if let snapshotView = self.reactionNodes[i].view.snapshotContentTree(), let targetSnapshotView = targetNode.view.snapshotContentTree() {
targetSnapshotView.frame = self.view.convert(targetNode.bounds, from: targetNode.view)
self.reactionNodes[i].isHidden = true
self.view.addSubview(targetSnapshotView)
self.view.addSubview(snapshotView)
completedTarget = false
let targetPosition = self.view.convert(targetNode.bounds.center, from: targetNode.view)
let duration: Double = 0.3
if hideTarget {
targetNode.isHidden = true
}
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false)
targetSnapshotView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
targetSnapshotView.layer.animateScale(from: snapshotView.bounds.width / targetSnapshotView.bounds.width, to: 0.5, duration: 0.3, removeOnCompletion: false)
let sourcePoint = snapshotView.center
let midPoint = CGPoint(x: (sourcePoint.x + targetPosition.x) / 2.0, y: sourcePoint.y - 30.0)
let x1 = sourcePoint.x
let y1 = sourcePoint.y
let x2 = midPoint.x
let y2 = midPoint.y
let x3 = targetPosition.x
let y3 = targetPosition.y
let a = (x3 * (y2 - y1) + x2 * (y1 - y3) + x1 * (y3 - y2)) / ((x1 - x2) * (x1 - x3) * (x2 - x3))
let b = (x1 * x1 * (y2 - y3) + x3 * x3 * (y1 - y2) + x2 * x2 * (y3 - y1)) / ((x1 - x2) * (x1 - x3) * (x2 - x3))
let c = (x2 * x2 * (x3 * y1 - x1 * y3) + x2 * (x1 * x1 * y3 - x3 * x3 * y1) + x1 * x3 * (x3 - x1) * y2) / ((x1 - x2) * (x1 - x3) * (x2 - x3))
var keyframes: [AnyObject] = []
for i in 0 ..< 10 {
let k = CGFloat(i) / CGFloat(10 - 1)
let x = sourcePoint.x * (1.0 - k) + targetPosition.x * k
let y = a * x * x + b * x + c
keyframes.append(NSValue(cgPoint: CGPoint(x: x, y: y)))
}
snapshotView.layer.animateKeyframes(values: keyframes, duration: 0.3, keyPath: "position", removeOnCompletion: false, completion: { [weak self] _ in
if let strongSelf = self {
strongSelf.hapticFeedback.tap()
}
completedTarget = true
if hideTarget {
targetNode.isHidden = false
targetNode.layer.animateSpring(from: 0.5 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: duration, initialVelocity: 0.0, damping: 90.0)
}
intermediateCompletion()
})
targetSnapshotView.layer.animateKeyframes(values: keyframes, duration: 0.3, keyPath: "position", removeOnCompletion: false)
snapshotView.layer.animateScale(from: 1.0, to: (targetSnapshotView.bounds.width * 0.5) / snapshotView.bounds.width, duration: 0.3, removeOnCompletion: false)
}
break
}
}
}
//self.backgroundNode.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false)
self.backgroundShadowNode.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false)
self.backgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
self.backgroundShadowNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in
completedContainer = true
intermediateCompletion()
})
for (node, shadow) in self.bubbleNodes {
node.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false)
node.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
shadow.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false)
shadow.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
}
for i in 0 ..< self.reactionNodes.count {
self.reactionNodes[i].layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false)
self.reactionNodes[i].layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
}
}
func selectedReaction() -> ReactionGestureItem? {
for i in 0 ..< self.reactionNodes.count {
if let isMaximized = self.reactionNodes[i].isMaximized, isMaximized {
return self.reactionNodes[i].reaction
}
}
return nil
}
}
*/

View File

@ -172,6 +172,12 @@ public func combineLatest<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13
}, initialValues: [:], queue: queue)
}
public func combineLatest<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, E>(queue: Queue? = nil, _ s1: Signal<T1, E>, _ s2: Signal<T2, E>, _ s3: Signal<T3, E>, _ s4: Signal<T4, E>, _ s5: Signal<T5, E>, _ s6: Signal<T6, E>, _ s7: Signal<T7, E>, _ s8: Signal<T8, E>, _ s9: Signal<T9, E>, _ s10: Signal<T10, E>, _ s11: Signal<T11, E>, _ s12: Signal<T12, E>, _ s13: Signal<T13, E>, _ s14: Signal<T14, E>, _ s15: Signal<T15, E>) -> Signal<(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15), E> {
return combineLatestAny([signalOfAny(s1), signalOfAny(s2), signalOfAny(s3), signalOfAny(s4), signalOfAny(s5), signalOfAny(s6), signalOfAny(s7), signalOfAny(s8), signalOfAny(s9), signalOfAny(s10), signalOfAny(s11), signalOfAny(s12), signalOfAny(s13), signalOfAny(s14), signalOfAny(s15)], combine: { values in
return (values[0] as! T1, values[1] as! T2, values[2] as! T3, values[3] as! T4, values[4] as! T5, values[5] as! T6, values[6] as! T7, values[7] as! T8, values[8] as! T9, values[9] as! T10, values[10] as! T11, values[11] as! T12, values[12] as! T13, values[13] as! T14, values[14] as! T15)
}, initialValues: [:], queue: queue)
}
public func combineLatest<T, E>(queue: Queue? = nil, _ signals: [Signal<T, E>]) -> Signal<[T], E> {
if signals.count == 0 {
return single([T](), E.self)

View File

@ -12,8 +12,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-457104426] = { return Api.InputGeoPoint.parse_inputGeoPointEmpty($0) }
dict[1210199983] = { return Api.InputGeoPoint.parse_inputGeoPoint($0) }
dict[-784000893] = { return Api.payments.ValidatedRequestedInfo.parse_validatedRequestedInfo($0) }
dict[1185349556] = { return Api.ChatFull.parse_chatFull($0) }
dict[1449537070] = { return Api.ChatFull.parse_channelFull($0) }
dict[-779165146] = { return Api.ChatFull.parse_chatFull($0) }
dict[-516145888] = { return Api.ChatFull.parse_channelFull($0) }
dict[-591909213] = { return Api.PollResults.parse_pollResults($0) }
dict[-1070776313] = { return Api.ChatParticipant.parse_chatParticipant($0) }
dict[-462696732] = { return Api.ChatParticipant.parse_chatParticipantCreator($0) }
@ -289,6 +289,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[1299263278] = { return Api.Update.parse_updateBotCommands($0) }
dict[1885586395] = { return Api.Update.parse_updatePendingJoinRequests($0) }
dict[299870598] = { return Api.Update.parse_updateBotChatInviteRequester($0) }
dict[357013699] = { return Api.Update.parse_updateMessageReactions($0) }
dict[136574537] = { return Api.messages.VotesList.parse_votesList($0) }
dict[1558266229] = { return Api.PopularContact.parse_popularContact($0) }
dict[-592373577] = { return Api.GroupCallParticipantVideoSourceGroup.parse_groupCallParticipantVideoSourceGroup($0) }
@ -341,6 +342,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-842824308] = { return Api.account.WallPapers.parse_wallPapers($0) }
dict[1012306921] = { return Api.InputTheme.parse_inputTheme($0) }
dict[-175567375] = { return Api.InputTheme.parse_inputThemeSlug($0) }
dict[142306870] = { return Api.MessageReactions.parse_messageReactions($0) }
dict[-2032041631] = { return Api.Poll.parse_poll($0) }
dict[-1195615476] = { return Api.InputNotifyPeer.parse_inputNotifyPeer($0) }
dict[423314455] = { return Api.InputNotifyPeer.parse_inputNotifyUsers($0) }
@ -489,6 +491,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1036572727] = { return Api.account.PasswordInputSettings.parse_passwordInputSettings($0) }
dict[878078826] = { return Api.PageTableCell.parse_pageTableCell($0) }
dict[-1626209256] = { return Api.ChatBannedRights.parse_chatBannedRights($0) }
dict[-1626924713] = { return Api.messages.AvailableReactions.parse_availableReactionsNotModified($0) }
dict[1989032621] = { return Api.messages.AvailableReactions.parse_availableReactions($0) }
dict[1968737087] = { return Api.InputClientProxy.parse_inputClientProxy($0) }
dict[649453030] = { return Api.messages.MessageEditData.parse_messageEditData($0) }
dict[1705297877] = { return Api.messages.SponsoredMessages.parse_sponsoredMessages($0) }
@ -513,6 +517,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-524237339] = { return Api.PageTableRow.parse_pageTableRow($0) }
dict[453805082] = { return Api.DraftMessage.parse_draftMessageEmpty($0) }
dict[-40996577] = { return Api.DraftMessage.parse_draftMessage($0) }
dict[-1553558980] = { return Api.messages.MessageReactionsList.parse_messageReactionsList($0) }
dict[-1014526429] = { return Api.help.Country.parse_country($0) }
dict[-1660637285] = { return Api.StatsGroupTopPoster.parse_statsGroupTopPoster($0) }
dict[-2128640689] = { return Api.account.SentEmailCode.parse_sentEmailCode($0) }
@ -690,7 +695,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-742634630] = { return Api.User.parse_userEmpty($0) }
dict[1073147056] = { return Api.User.parse_user($0) }
dict[-1868117372] = { return Api.Message.parse_messageEmpty($0) }
dict[-2049520670] = { return Api.Message.parse_message($0) }
dict[940666592] = { return Api.Message.parse_message($0) }
dict[721967202] = { return Api.Message.parse_messageService($0) }
dict[1398765469] = { return Api.StatsGroupTopInviter.parse_statsGroupTopInviter($0) }
dict[186120336] = { return Api.messages.RecentStickers.parse_recentStickersNotModified($0) }
@ -760,6 +765,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[1834973166] = { return Api.BaseTheme.parse_baseThemeTinted($0) }
dict[1527845466] = { return Api.BaseTheme.parse_baseThemeArctic($0) }
dict[398898678] = { return Api.help.Support.parse_support($0) }
dict[1873957073] = { return Api.ReactionCount.parse_reactionCount($0) }
dict[1474492012] = { return Api.MessagesFilter.parse_inputMessagesFilterEmpty($0) }
dict[-1777752804] = { return Api.MessagesFilter.parse_inputMessagesFilterPhotos($0) }
dict[-1614803355] = { return Api.MessagesFilter.parse_inputMessagesFilterVideo($0) }
@ -785,7 +791,9 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[594408994] = { return Api.EmojiKeyword.parse_emojiKeywordDeleted($0) }
dict[-290921362] = { return Api.upload.CdnFile.parse_cdnFileReuploadNeeded($0) }
dict[-1449145777] = { return Api.upload.CdnFile.parse_cdnFile($0) }
dict[1679961905] = { return Api.AvailableReaction.parse_availableReaction($0) }
dict[415997816] = { return Api.help.InviteText.parse_inviteText($0) }
dict[-1826077446] = { return Api.MessageUserReaction.parse_messageUserReaction($0) }
dict[1984755728] = { return Api.BotInlineMessage.parse_botInlineMessageMediaAuto($0) }
dict[-1937807902] = { return Api.BotInlineMessage.parse_botInlineMessageText($0) }
dict[85477117] = { return Api.BotInlineMessage.parse_botInlineMessageMediaGeo($0) }
@ -1164,6 +1172,8 @@ public struct Api {
_1.serialize(buffer, boxed)
case let _1 as Api.InputTheme:
_1.serialize(buffer, boxed)
case let _1 as Api.MessageReactions:
_1.serialize(buffer, boxed)
case let _1 as Api.Poll:
_1.serialize(buffer, boxed)
case let _1 as Api.InputNotifyPeer:
@ -1262,6 +1272,8 @@ public struct Api {
_1.serialize(buffer, boxed)
case let _1 as Api.ChatBannedRights:
_1.serialize(buffer, boxed)
case let _1 as Api.messages.AvailableReactions:
_1.serialize(buffer, boxed)
case let _1 as Api.InputClientProxy:
_1.serialize(buffer, boxed)
case let _1 as Api.messages.MessageEditData:
@ -1292,6 +1304,8 @@ public struct Api {
_1.serialize(buffer, boxed)
case let _1 as Api.DraftMessage:
_1.serialize(buffer, boxed)
case let _1 as Api.messages.MessageReactionsList:
_1.serialize(buffer, boxed)
case let _1 as Api.help.Country:
_1.serialize(buffer, boxed)
case let _1 as Api.StatsGroupTopPoster:
@ -1544,6 +1558,8 @@ public struct Api {
_1.serialize(buffer, boxed)
case let _1 as Api.help.Support:
_1.serialize(buffer, boxed)
case let _1 as Api.ReactionCount:
_1.serialize(buffer, boxed)
case let _1 as Api.MessagesFilter:
_1.serialize(buffer, boxed)
case let _1 as Api.messages.Dialogs:
@ -1554,8 +1570,12 @@ public struct Api {
_1.serialize(buffer, boxed)
case let _1 as Api.upload.CdnFile:
_1.serialize(buffer, boxed)
case let _1 as Api.AvailableReaction:
_1.serialize(buffer, boxed)
case let _1 as Api.help.InviteText:
_1.serialize(buffer, boxed)
case let _1 as Api.MessageUserReaction:
_1.serialize(buffer, boxed)
case let _1 as Api.BotInlineMessage:
_1.serialize(buffer, boxed)
case let _1 as Api.InputPeerNotifySettings:

View File

@ -893,6 +893,62 @@ public struct messages {
}
}
}
public enum AvailableReactions: TypeConstructorDescription {
case availableReactionsNotModified
case availableReactions(hash: Int32, reactions: [Api.AvailableReaction])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .availableReactionsNotModified:
if boxed {
buffer.appendInt32(-1626924713)
}
break
case .availableReactions(let hash, let reactions):
if boxed {
buffer.appendInt32(1989032621)
}
serializeInt32(hash, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(reactions.count))
for item in reactions {
item.serialize(buffer, true)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .availableReactionsNotModified:
return ("availableReactionsNotModified", [])
case .availableReactions(let hash, let reactions):
return ("availableReactions", [("hash", hash), ("reactions", reactions)])
}
}
public static func parse_availableReactionsNotModified(_ reader: BufferReader) -> AvailableReactions? {
return Api.messages.AvailableReactions.availableReactionsNotModified
}
public static func parse_availableReactions(_ reader: BufferReader) -> AvailableReactions? {
var _1: Int32?
_1 = reader.readInt32()
var _2: [Api.AvailableReaction]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.AvailableReaction.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.messages.AvailableReactions.availableReactions(hash: _1!, reactions: _2!)
}
else {
return nil
}
}
}
public enum MessageEditData: TypeConstructorDescription {
case messageEditData(flags: Int32)
@ -1161,6 +1217,68 @@ public struct messages {
}
}
}
public enum MessageReactionsList: TypeConstructorDescription {
case messageReactionsList(flags: Int32, count: Int32, reactions: [Api.MessageUserReaction], users: [Api.User], nextOffset: String?)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .messageReactionsList(let flags, let count, let reactions, let users, let nextOffset):
if boxed {
buffer.appendInt32(-1553558980)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(count, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(reactions.count))
for item in reactions {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(users.count))
for item in users {
item.serialize(buffer, true)
}
if Int(flags) & Int(1 << 0) != 0 {serializeString(nextOffset!, buffer: buffer, boxed: false)}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .messageReactionsList(let flags, let count, let reactions, let users, let nextOffset):
return ("messageReactionsList", [("flags", flags), ("count", count), ("reactions", reactions), ("users", users), ("nextOffset", nextOffset)])
}
}
public static func parse_messageReactionsList(_ reader: BufferReader) -> MessageReactionsList? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
var _3: [Api.MessageUserReaction]?
if let _ = reader.readInt32() {
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageUserReaction.self)
}
var _4: [Api.User]?
if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
var _5: String?
if Int(_1!) & Int(1 << 0) != 0 {_5 = parseString(reader) }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 {
return Api.messages.MessageReactionsList.messageReactionsList(flags: _1!, count: _2!, reactions: _3!, users: _4!, nextOffset: _5)
}
else {
return nil
}
}
}
public enum PeerSettings: TypeConstructorDescription {
case peerSettings(settings: Api.PeerSettings, chats: [Api.Chat], users: [Api.User])

View File

@ -160,14 +160,14 @@ public extension Api {
}
public enum ChatFull: TypeConstructorDescription {
case chatFull(flags: Int32, id: Int64, about: String, participants: Api.ChatParticipants, chatPhoto: Api.Photo?, notifySettings: Api.PeerNotifySettings, exportedInvite: Api.ExportedChatInvite?, botInfo: [Api.BotInfo]?, pinnedMsgId: Int32?, folderId: Int32?, call: Api.InputGroupCall?, ttlPeriod: Int32?, groupcallDefaultJoinAs: Api.Peer?, themeEmoticon: String?, requestsPending: Int32?, recentRequesters: [Int64]?)
case channelFull(flags: Int32, id: Int64, about: String, participantsCount: Int32?, adminsCount: Int32?, kickedCount: Int32?, bannedCount: Int32?, onlineCount: Int32?, readInboxMaxId: Int32, readOutboxMaxId: Int32, unreadCount: Int32, chatPhoto: Api.Photo, notifySettings: Api.PeerNotifySettings, exportedInvite: Api.ExportedChatInvite?, botInfo: [Api.BotInfo], migratedFromChatId: Int64?, migratedFromMaxId: Int32?, pinnedMsgId: Int32?, stickerset: Api.StickerSet?, availableMinId: Int32?, folderId: Int32?, linkedChatId: Int64?, location: Api.ChannelLocation?, slowmodeSeconds: Int32?, slowmodeNextSendDate: Int32?, statsDc: Int32?, pts: Int32, call: Api.InputGroupCall?, ttlPeriod: Int32?, pendingSuggestions: [String]?, groupcallDefaultJoinAs: Api.Peer?, themeEmoticon: String?, requestsPending: Int32?, recentRequesters: [Int64]?, defaultSendAs: Api.Peer?)
case chatFull(flags: Int32, id: Int64, about: String, participants: Api.ChatParticipants, chatPhoto: Api.Photo?, notifySettings: Api.PeerNotifySettings, exportedInvite: Api.ExportedChatInvite?, botInfo: [Api.BotInfo]?, pinnedMsgId: Int32?, folderId: Int32?, call: Api.InputGroupCall?, ttlPeriod: Int32?, groupcallDefaultJoinAs: Api.Peer?, themeEmoticon: String?, requestsPending: Int32?, recentRequesters: [Int64]?, availableReactions: [String]?)
case channelFull(flags: Int32, id: Int64, about: String, participantsCount: Int32?, adminsCount: Int32?, kickedCount: Int32?, bannedCount: Int32?, onlineCount: Int32?, readInboxMaxId: Int32, readOutboxMaxId: Int32, unreadCount: Int32, chatPhoto: Api.Photo, notifySettings: Api.PeerNotifySettings, exportedInvite: Api.ExportedChatInvite?, botInfo: [Api.BotInfo], migratedFromChatId: Int64?, migratedFromMaxId: Int32?, pinnedMsgId: Int32?, stickerset: Api.StickerSet?, availableMinId: Int32?, folderId: Int32?, linkedChatId: Int64?, location: Api.ChannelLocation?, slowmodeSeconds: Int32?, slowmodeNextSendDate: Int32?, statsDc: Int32?, pts: Int32, call: Api.InputGroupCall?, ttlPeriod: Int32?, pendingSuggestions: [String]?, groupcallDefaultJoinAs: Api.Peer?, themeEmoticon: String?, requestsPending: Int32?, recentRequesters: [Int64]?, defaultSendAs: Api.Peer?, availableReactions: [String]?)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .chatFull(let flags, let id, let about, let participants, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let pinnedMsgId, let folderId, let call, let ttlPeriod, let groupcallDefaultJoinAs, let themeEmoticon, let requestsPending, let recentRequesters):
case .chatFull(let flags, let id, let about, let participants, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let pinnedMsgId, let folderId, let call, let ttlPeriod, let groupcallDefaultJoinAs, let themeEmoticon, let requestsPending, let recentRequesters, let availableReactions):
if boxed {
buffer.appendInt32(1185349556)
buffer.appendInt32(-779165146)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt64(id, buffer: buffer, boxed: false)
@ -193,10 +193,15 @@ public extension Api {
for item in recentRequesters! {
serializeInt64(item, buffer: buffer, boxed: false)
}}
if Int(flags) & Int(1 << 18) != 0 {buffer.appendInt32(481674261)
buffer.appendInt32(Int32(availableReactions!.count))
for item in availableReactions! {
serializeString(item, buffer: buffer, boxed: false)
}}
break
case .channelFull(let flags, let id, let about, let participantsCount, let adminsCount, let kickedCount, let bannedCount, let onlineCount, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let migratedFromChatId, let migratedFromMaxId, let pinnedMsgId, let stickerset, let availableMinId, let folderId, let linkedChatId, let location, let slowmodeSeconds, let slowmodeNextSendDate, let statsDc, let pts, let call, let ttlPeriod, let pendingSuggestions, let groupcallDefaultJoinAs, let themeEmoticon, let requestsPending, let recentRequesters, let defaultSendAs):
case .channelFull(let flags, let id, let about, let participantsCount, let adminsCount, let kickedCount, let bannedCount, let onlineCount, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let migratedFromChatId, let migratedFromMaxId, let pinnedMsgId, let stickerset, let availableMinId, let folderId, let linkedChatId, let location, let slowmodeSeconds, let slowmodeNextSendDate, let statsDc, let pts, let call, let ttlPeriod, let pendingSuggestions, let groupcallDefaultJoinAs, let themeEmoticon, let requestsPending, let recentRequesters, let defaultSendAs, let availableReactions):
if boxed {
buffer.appendInt32(1449537070)
buffer.appendInt32(-516145888)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt64(id, buffer: buffer, boxed: false)
@ -245,16 +250,21 @@ public extension Api {
serializeInt64(item, buffer: buffer, boxed: false)
}}
if Int(flags) & Int(1 << 29) != 0 {defaultSendAs!.serialize(buffer, true)}
if Int(flags) & Int(1 << 30) != 0 {buffer.appendInt32(481674261)
buffer.appendInt32(Int32(availableReactions!.count))
for item in availableReactions! {
serializeString(item, buffer: buffer, boxed: false)
}}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .chatFull(let flags, let id, let about, let participants, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let pinnedMsgId, let folderId, let call, let ttlPeriod, let groupcallDefaultJoinAs, let themeEmoticon, let requestsPending, let recentRequesters):
return ("chatFull", [("flags", flags), ("id", id), ("about", about), ("participants", participants), ("chatPhoto", chatPhoto), ("notifySettings", notifySettings), ("exportedInvite", exportedInvite), ("botInfo", botInfo), ("pinnedMsgId", pinnedMsgId), ("folderId", folderId), ("call", call), ("ttlPeriod", ttlPeriod), ("groupcallDefaultJoinAs", groupcallDefaultJoinAs), ("themeEmoticon", themeEmoticon), ("requestsPending", requestsPending), ("recentRequesters", recentRequesters)])
case .channelFull(let flags, let id, let about, let participantsCount, let adminsCount, let kickedCount, let bannedCount, let onlineCount, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let migratedFromChatId, let migratedFromMaxId, let pinnedMsgId, let stickerset, let availableMinId, let folderId, let linkedChatId, let location, let slowmodeSeconds, let slowmodeNextSendDate, let statsDc, let pts, let call, let ttlPeriod, let pendingSuggestions, let groupcallDefaultJoinAs, let themeEmoticon, let requestsPending, let recentRequesters, let defaultSendAs):
return ("channelFull", [("flags", flags), ("id", id), ("about", about), ("participantsCount", participantsCount), ("adminsCount", adminsCount), ("kickedCount", kickedCount), ("bannedCount", bannedCount), ("onlineCount", onlineCount), ("readInboxMaxId", readInboxMaxId), ("readOutboxMaxId", readOutboxMaxId), ("unreadCount", unreadCount), ("chatPhoto", chatPhoto), ("notifySettings", notifySettings), ("exportedInvite", exportedInvite), ("botInfo", botInfo), ("migratedFromChatId", migratedFromChatId), ("migratedFromMaxId", migratedFromMaxId), ("pinnedMsgId", pinnedMsgId), ("stickerset", stickerset), ("availableMinId", availableMinId), ("folderId", folderId), ("linkedChatId", linkedChatId), ("location", location), ("slowmodeSeconds", slowmodeSeconds), ("slowmodeNextSendDate", slowmodeNextSendDate), ("statsDc", statsDc), ("pts", pts), ("call", call), ("ttlPeriod", ttlPeriod), ("pendingSuggestions", pendingSuggestions), ("groupcallDefaultJoinAs", groupcallDefaultJoinAs), ("themeEmoticon", themeEmoticon), ("requestsPending", requestsPending), ("recentRequesters", recentRequesters), ("defaultSendAs", defaultSendAs)])
case .chatFull(let flags, let id, let about, let participants, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let pinnedMsgId, let folderId, let call, let ttlPeriod, let groupcallDefaultJoinAs, let themeEmoticon, let requestsPending, let recentRequesters, let availableReactions):
return ("chatFull", [("flags", flags), ("id", id), ("about", about), ("participants", participants), ("chatPhoto", chatPhoto), ("notifySettings", notifySettings), ("exportedInvite", exportedInvite), ("botInfo", botInfo), ("pinnedMsgId", pinnedMsgId), ("folderId", folderId), ("call", call), ("ttlPeriod", ttlPeriod), ("groupcallDefaultJoinAs", groupcallDefaultJoinAs), ("themeEmoticon", themeEmoticon), ("requestsPending", requestsPending), ("recentRequesters", recentRequesters), ("availableReactions", availableReactions)])
case .channelFull(let flags, let id, let about, let participantsCount, let adminsCount, let kickedCount, let bannedCount, let onlineCount, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let migratedFromChatId, let migratedFromMaxId, let pinnedMsgId, let stickerset, let availableMinId, let folderId, let linkedChatId, let location, let slowmodeSeconds, let slowmodeNextSendDate, let statsDc, let pts, let call, let ttlPeriod, let pendingSuggestions, let groupcallDefaultJoinAs, let themeEmoticon, let requestsPending, let recentRequesters, let defaultSendAs, let availableReactions):
return ("channelFull", [("flags", flags), ("id", id), ("about", about), ("participantsCount", participantsCount), ("adminsCount", adminsCount), ("kickedCount", kickedCount), ("bannedCount", bannedCount), ("onlineCount", onlineCount), ("readInboxMaxId", readInboxMaxId), ("readOutboxMaxId", readOutboxMaxId), ("unreadCount", unreadCount), ("chatPhoto", chatPhoto), ("notifySettings", notifySettings), ("exportedInvite", exportedInvite), ("botInfo", botInfo), ("migratedFromChatId", migratedFromChatId), ("migratedFromMaxId", migratedFromMaxId), ("pinnedMsgId", pinnedMsgId), ("stickerset", stickerset), ("availableMinId", availableMinId), ("folderId", folderId), ("linkedChatId", linkedChatId), ("location", location), ("slowmodeSeconds", slowmodeSeconds), ("slowmodeNextSendDate", slowmodeNextSendDate), ("statsDc", statsDc), ("pts", pts), ("call", call), ("ttlPeriod", ttlPeriod), ("pendingSuggestions", pendingSuggestions), ("groupcallDefaultJoinAs", groupcallDefaultJoinAs), ("themeEmoticon", themeEmoticon), ("requestsPending", requestsPending), ("recentRequesters", recentRequesters), ("defaultSendAs", defaultSendAs), ("availableReactions", availableReactions)])
}
}
@ -307,6 +317,10 @@ public extension Api {
if Int(_1!) & Int(1 << 17) != 0 {if let _ = reader.readInt32() {
_16 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self)
} }
var _17: [String]?
if Int(_1!) & Int(1 << 18) != 0 {if let _ = reader.readInt32() {
_17 = Api.parseVector(reader, elementSignature: -1255641564, elementType: String.self)
} }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
@ -323,8 +337,9 @@ public extension Api {
let _c14 = (Int(_1!) & Int(1 << 16) == 0) || _14 != nil
let _c15 = (Int(_1!) & Int(1 << 17) == 0) || _15 != nil
let _c16 = (Int(_1!) & Int(1 << 17) == 0) || _16 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 {
return Api.ChatFull.chatFull(flags: _1!, id: _2!, about: _3!, participants: _4!, chatPhoto: _5, notifySettings: _6!, exportedInvite: _7, botInfo: _8, pinnedMsgId: _9, folderId: _10, call: _11, ttlPeriod: _12, groupcallDefaultJoinAs: _13, themeEmoticon: _14, requestsPending: _15, recentRequesters: _16)
let _c17 = (Int(_1!) & Int(1 << 18) == 0) || _17 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 {
return Api.ChatFull.chatFull(flags: _1!, id: _2!, about: _3!, participants: _4!, chatPhoto: _5, notifySettings: _6!, exportedInvite: _7, botInfo: _8, pinnedMsgId: _9, folderId: _10, call: _11, ttlPeriod: _12, groupcallDefaultJoinAs: _13, themeEmoticon: _14, requestsPending: _15, recentRequesters: _16, availableReactions: _17)
}
else {
return nil
@ -423,6 +438,10 @@ public extension Api {
if Int(_1!) & Int(1 << 29) != 0 {if let signature = reader.readInt32() {
_35 = Api.parse(reader, signature: signature) as? Api.Peer
} }
var _36: [String]?
if Int(_1!) & Int(1 << 30) != 0 {if let _ = reader.readInt32() {
_36 = Api.parseVector(reader, elementSignature: -1255641564, elementType: String.self)
} }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
@ -458,8 +477,9 @@ public extension Api {
let _c33 = (Int(_1!) & Int(1 << 28) == 0) || _33 != nil
let _c34 = (Int(_1!) & Int(1 << 28) == 0) || _34 != nil
let _c35 = (Int(_1!) & Int(1 << 29) == 0) || _35 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 && _c25 && _c26 && _c27 && _c28 && _c29 && _c30 && _c31 && _c32 && _c33 && _c34 && _c35 {
return Api.ChatFull.channelFull(flags: _1!, id: _2!, about: _3!, participantsCount: _4, adminsCount: _5, kickedCount: _6, bannedCount: _7, onlineCount: _8, readInboxMaxId: _9!, readOutboxMaxId: _10!, unreadCount: _11!, chatPhoto: _12!, notifySettings: _13!, exportedInvite: _14, botInfo: _15!, migratedFromChatId: _16, migratedFromMaxId: _17, pinnedMsgId: _18, stickerset: _19, availableMinId: _20, folderId: _21, linkedChatId: _22, location: _23, slowmodeSeconds: _24, slowmodeNextSendDate: _25, statsDc: _26, pts: _27!, call: _28, ttlPeriod: _29, pendingSuggestions: _30, groupcallDefaultJoinAs: _31, themeEmoticon: _32, requestsPending: _33, recentRequesters: _34, defaultSendAs: _35)
let _c36 = (Int(_1!) & Int(1 << 30) == 0) || _36 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 && _c25 && _c26 && _c27 && _c28 && _c29 && _c30 && _c31 && _c32 && _c33 && _c34 && _c35 && _c36 {
return Api.ChatFull.channelFull(flags: _1!, id: _2!, about: _3!, participantsCount: _4, adminsCount: _5, kickedCount: _6, bannedCount: _7, onlineCount: _8, readInboxMaxId: _9!, readOutboxMaxId: _10!, unreadCount: _11!, chatPhoto: _12!, notifySettings: _13!, exportedInvite: _14, botInfo: _15!, migratedFromChatId: _16, migratedFromMaxId: _17, pinnedMsgId: _18, stickerset: _19, availableMinId: _20, folderId: _21, linkedChatId: _22, location: _23, slowmodeSeconds: _24, slowmodeNextSendDate: _25, statsDc: _26, pts: _27!, call: _28, ttlPeriod: _29, pendingSuggestions: _30, groupcallDefaultJoinAs: _31, themeEmoticon: _32, requestsPending: _33, recentRequesters: _34, defaultSendAs: _35, availableReactions: _36)
}
else {
return nil
@ -4857,6 +4877,7 @@ public extension Api {
case updateBotCommands(peer: Api.Peer, botId: Int64, commands: [Api.BotCommand])
case updatePendingJoinRequests(peer: Api.Peer, requestsPending: Int32, recentRequesters: [Int64])
case updateBotChatInviteRequester(peer: Api.Peer, date: Int32, userId: Int64, about: String, invite: Api.ExportedChatInvite, qts: Int32)
case updateMessageReactions(peer: Api.Peer, msgId: Int32, reactions: Api.MessageReactions)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
@ -5694,6 +5715,14 @@ public extension Api {
invite.serialize(buffer, true)
serializeInt32(qts, buffer: buffer, boxed: false)
break
case .updateMessageReactions(let peer, let msgId, let reactions):
if boxed {
buffer.appendInt32(357013699)
}
peer.serialize(buffer, true)
serializeInt32(msgId, buffer: buffer, boxed: false)
reactions.serialize(buffer, true)
break
}
}
@ -5889,6 +5918,8 @@ public extension Api {
return ("updatePendingJoinRequests", [("peer", peer), ("requestsPending", requestsPending), ("recentRequesters", recentRequesters)])
case .updateBotChatInviteRequester(let peer, let date, let userId, let about, let invite, let qts):
return ("updateBotChatInviteRequester", [("peer", peer), ("date", date), ("userId", userId), ("about", about), ("invite", invite), ("qts", qts)])
case .updateMessageReactions(let peer, let msgId, let reactions):
return ("updateMessageReactions", [("peer", peer), ("msgId", msgId), ("reactions", reactions)])
}
}
@ -7599,6 +7630,27 @@ public extension Api {
return nil
}
}
public static func parse_updateMessageReactions(_ reader: BufferReader) -> Update? {
var _1: Api.Peer?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.Peer
}
var _2: Int32?
_2 = reader.readInt32()
var _3: Api.MessageReactions?
if let signature = reader.readInt32() {
_3 = Api.parse(reader, signature: signature) as? Api.MessageReactions
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.Update.updateMessageReactions(peer: _1!, msgId: _2!, reactions: _3!)
}
else {
return nil
}
}
}
public enum PopularContact: TypeConstructorDescription {
@ -8868,6 +8920,60 @@ public extension Api {
}
}
}
public enum MessageReactions: TypeConstructorDescription {
case messageReactions(flags: Int32, results: [Api.ReactionCount], recentReactons: [Api.MessageUserReaction]?)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .messageReactions(let flags, let results, let recentReactons):
if boxed {
buffer.appendInt32(142306870)
}
serializeInt32(flags, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(results.count))
for item in results {
item.serialize(buffer, true)
}
if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261)
buffer.appendInt32(Int32(recentReactons!.count))
for item in recentReactons! {
item.serialize(buffer, true)
}}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .messageReactions(let flags, let results, let recentReactons):
return ("messageReactions", [("flags", flags), ("results", results), ("recentReactons", recentReactons)])
}
}
public static func parse_messageReactions(_ reader: BufferReader) -> MessageReactions? {
var _1: Int32?
_1 = reader.readInt32()
var _2: [Api.ReactionCount]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ReactionCount.self)
}
var _3: [Api.MessageUserReaction]?
if Int(_1!) & Int(1 << 1) != 0 {if let _ = reader.readInt32() {
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageUserReaction.self)
} }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil
if _c1 && _c2 && _c3 {
return Api.MessageReactions.messageReactions(flags: _1!, results: _2!, recentReactons: _3)
}
else {
return nil
}
}
}
public enum Poll: TypeConstructorDescription {
case poll(id: Int64, flags: Int32, question: String, answers: [Api.PollAnswer], closePeriod: Int32?, closeDate: Int32?)
@ -17551,7 +17657,7 @@ public extension Api {
}
public enum Message: TypeConstructorDescription {
case messageEmpty(flags: Int32, id: Int32, peerId: Api.Peer?)
case message(flags: Int32, id: Int32, fromId: Api.Peer?, peerId: Api.Peer, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int64?, replyTo: Api.MessageReplyHeader?, date: Int32, message: String, media: Api.MessageMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, views: Int32?, forwards: Int32?, replies: Api.MessageReplies?, editDate: Int32?, postAuthor: String?, groupedId: Int64?, restrictionReason: [Api.RestrictionReason]?, ttlPeriod: Int32?)
case message(flags: Int32, id: Int32, fromId: Api.Peer?, peerId: Api.Peer, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int64?, replyTo: Api.MessageReplyHeader?, date: Int32, message: String, media: Api.MessageMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, views: Int32?, forwards: Int32?, replies: Api.MessageReplies?, editDate: Int32?, postAuthor: String?, groupedId: Int64?, reactions: Api.MessageReactions?, restrictionReason: [Api.RestrictionReason]?, ttlPeriod: Int32?)
case messageService(flags: Int32, id: Int32, fromId: Api.Peer?, peerId: Api.Peer, replyTo: Api.MessageReplyHeader?, date: Int32, action: Api.MessageAction, ttlPeriod: Int32?)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
@ -17564,9 +17670,9 @@ public extension Api {
serializeInt32(id, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {peerId!.serialize(buffer, true)}
break
case .message(let flags, let id, let fromId, let peerId, let fwdFrom, let viaBotId, let replyTo, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let editDate, let postAuthor, let groupedId, let restrictionReason, let ttlPeriod):
case .message(let flags, let id, let fromId, let peerId, let fwdFrom, let viaBotId, let replyTo, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let editDate, let postAuthor, let groupedId, let reactions, let restrictionReason, let ttlPeriod):
if boxed {
buffer.appendInt32(-2049520670)
buffer.appendInt32(940666592)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(id, buffer: buffer, boxed: false)
@ -17590,6 +17696,7 @@ public extension Api {
if Int(flags) & Int(1 << 15) != 0 {serializeInt32(editDate!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 16) != 0 {serializeString(postAuthor!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 17) != 0 {serializeInt64(groupedId!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 20) != 0 {reactions!.serialize(buffer, true)}
if Int(flags) & Int(1 << 22) != 0 {buffer.appendInt32(481674261)
buffer.appendInt32(Int32(restrictionReason!.count))
for item in restrictionReason! {
@ -17617,8 +17724,8 @@ public extension Api {
switch self {
case .messageEmpty(let flags, let id, let peerId):
return ("messageEmpty", [("flags", flags), ("id", id), ("peerId", peerId)])
case .message(let flags, let id, let fromId, let peerId, let fwdFrom, let viaBotId, let replyTo, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let editDate, let postAuthor, let groupedId, let restrictionReason, let ttlPeriod):
return ("message", [("flags", flags), ("id", id), ("fromId", fromId), ("peerId", peerId), ("fwdFrom", fwdFrom), ("viaBotId", viaBotId), ("replyTo", replyTo), ("date", date), ("message", message), ("media", media), ("replyMarkup", replyMarkup), ("entities", entities), ("views", views), ("forwards", forwards), ("replies", replies), ("editDate", editDate), ("postAuthor", postAuthor), ("groupedId", groupedId), ("restrictionReason", restrictionReason), ("ttlPeriod", ttlPeriod)])
case .message(let flags, let id, let fromId, let peerId, let fwdFrom, let viaBotId, let replyTo, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let editDate, let postAuthor, let groupedId, let reactions, let restrictionReason, let ttlPeriod):
return ("message", [("flags", flags), ("id", id), ("fromId", fromId), ("peerId", peerId), ("fwdFrom", fwdFrom), ("viaBotId", viaBotId), ("replyTo", replyTo), ("date", date), ("message", message), ("media", media), ("replyMarkup", replyMarkup), ("entities", entities), ("views", views), ("forwards", forwards), ("replies", replies), ("editDate", editDate), ("postAuthor", postAuthor), ("groupedId", groupedId), ("reactions", reactions), ("restrictionReason", restrictionReason), ("ttlPeriod", ttlPeriod)])
case .messageService(let flags, let id, let fromId, let peerId, let replyTo, let date, let action, let ttlPeriod):
return ("messageService", [("flags", flags), ("id", id), ("fromId", fromId), ("peerId", peerId), ("replyTo", replyTo), ("date", date), ("action", action), ("ttlPeriod", ttlPeriod)])
}
@ -17696,12 +17803,16 @@ public extension Api {
if Int(_1!) & Int(1 << 16) != 0 {_17 = parseString(reader) }
var _18: Int64?
if Int(_1!) & Int(1 << 17) != 0 {_18 = reader.readInt64() }
var _19: [Api.RestrictionReason]?
if Int(_1!) & Int(1 << 22) != 0 {if let _ = reader.readInt32() {
_19 = Api.parseVector(reader, elementSignature: 0, elementType: Api.RestrictionReason.self)
var _19: Api.MessageReactions?
if Int(_1!) & Int(1 << 20) != 0 {if let signature = reader.readInt32() {
_19 = Api.parse(reader, signature: signature) as? Api.MessageReactions
} }
var _20: Int32?
if Int(_1!) & Int(1 << 25) != 0 {_20 = reader.readInt32() }
var _20: [Api.RestrictionReason]?
if Int(_1!) & Int(1 << 22) != 0 {if let _ = reader.readInt32() {
_20 = Api.parseVector(reader, elementSignature: 0, elementType: Api.RestrictionReason.self)
} }
var _21: Int32?
if Int(_1!) & Int(1 << 25) != 0 {_21 = reader.readInt32() }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = (Int(_1!) & Int(1 << 8) == 0) || _3 != nil
@ -17720,10 +17831,11 @@ public extension Api {
let _c16 = (Int(_1!) & Int(1 << 15) == 0) || _16 != nil
let _c17 = (Int(_1!) & Int(1 << 16) == 0) || _17 != nil
let _c18 = (Int(_1!) & Int(1 << 17) == 0) || _18 != nil
let _c19 = (Int(_1!) & Int(1 << 22) == 0) || _19 != nil
let _c20 = (Int(_1!) & Int(1 << 25) == 0) || _20 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 {
return Api.Message.message(flags: _1!, id: _2!, fromId: _3, peerId: _4!, fwdFrom: _5, viaBotId: _6, replyTo: _7, date: _8!, message: _9!, media: _10, replyMarkup: _11, entities: _12, views: _13, forwards: _14, replies: _15, editDate: _16, postAuthor: _17, groupedId: _18, restrictionReason: _19, ttlPeriod: _20)
let _c19 = (Int(_1!) & Int(1 << 20) == 0) || _19 != nil
let _c20 = (Int(_1!) & Int(1 << 22) == 0) || _20 != nil
let _c21 = (Int(_1!) & Int(1 << 25) == 0) || _21 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 {
return Api.Message.message(flags: _1!, id: _2!, fromId: _3, peerId: _4!, fwdFrom: _5, viaBotId: _6, replyTo: _7, date: _8!, message: _9!, media: _10, replyMarkup: _11, entities: _12, views: _13, forwards: _14, replies: _15, editDate: _16, postAuthor: _17, groupedId: _18, reactions: _19, restrictionReason: _20, ttlPeriod: _21)
}
else {
return nil
@ -19734,6 +19846,48 @@ public extension Api {
return Api.BaseTheme.baseThemeArctic
}
}
public enum ReactionCount: TypeConstructorDescription {
case reactionCount(flags: Int32, reaction: String, count: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .reactionCount(let flags, let reaction, let count):
if boxed {
buffer.appendInt32(1873957073)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeString(reaction, buffer: buffer, boxed: false)
serializeInt32(count, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .reactionCount(let flags, let reaction, let count):
return ("reactionCount", [("flags", flags), ("reaction", reaction), ("count", count)])
}
}
public static func parse_reactionCount(_ reader: BufferReader) -> ReactionCount? {
var _1: Int32?
_1 = reader.readInt32()
var _2: String?
_2 = parseString(reader)
var _3: Int32?
_3 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.ReactionCount.reactionCount(flags: _1!, reaction: _2!, count: _3!)
}
else {
return nil
}
}
}
public enum MessagesFilter: TypeConstructorDescription {
case inputMessagesFilterEmpty
@ -20034,6 +20188,106 @@ public extension Api {
}
}
}
public enum AvailableReaction: TypeConstructorDescription {
case availableReaction(reaction: String, title: String, staticIcon: Api.Document, selectAnimation: Api.Document, activateAnimation: Api.Document, effectAnimation: Api.Document)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .availableReaction(let reaction, let title, let staticIcon, let selectAnimation, let activateAnimation, let effectAnimation):
if boxed {
buffer.appendInt32(1679961905)
}
serializeString(reaction, buffer: buffer, boxed: false)
serializeString(title, buffer: buffer, boxed: false)
staticIcon.serialize(buffer, true)
selectAnimation.serialize(buffer, true)
activateAnimation.serialize(buffer, true)
effectAnimation.serialize(buffer, true)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .availableReaction(let reaction, let title, let staticIcon, let selectAnimation, let activateAnimation, let effectAnimation):
return ("availableReaction", [("reaction", reaction), ("title", title), ("staticIcon", staticIcon), ("selectAnimation", selectAnimation), ("activateAnimation", activateAnimation), ("effectAnimation", effectAnimation)])
}
}
public static func parse_availableReaction(_ reader: BufferReader) -> AvailableReaction? {
var _1: String?
_1 = parseString(reader)
var _2: String?
_2 = parseString(reader)
var _3: Api.Document?
if let signature = reader.readInt32() {
_3 = Api.parse(reader, signature: signature) as? Api.Document
}
var _4: Api.Document?
if let signature = reader.readInt32() {
_4 = Api.parse(reader, signature: signature) as? Api.Document
}
var _5: Api.Document?
if let signature = reader.readInt32() {
_5 = Api.parse(reader, signature: signature) as? Api.Document
}
var _6: Api.Document?
if let signature = reader.readInt32() {
_6 = Api.parse(reader, signature: signature) as? Api.Document
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = _5 != nil
let _c6 = _6 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 {
return Api.AvailableReaction.availableReaction(reaction: _1!, title: _2!, staticIcon: _3!, selectAnimation: _4!, activateAnimation: _5!, effectAnimation: _6!)
}
else {
return nil
}
}
}
public enum MessageUserReaction: TypeConstructorDescription {
case messageUserReaction(userId: Int64, reaction: String)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .messageUserReaction(let userId, let reaction):
if boxed {
buffer.appendInt32(-1826077446)
}
serializeInt64(userId, buffer: buffer, boxed: false)
serializeString(reaction, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .messageUserReaction(let userId, let reaction):
return ("messageUserReaction", [("userId", userId), ("reaction", reaction)])
}
}
public static func parse_messageUserReaction(_ reader: BufferReader) -> MessageUserReaction? {
var _1: Int64?
_1 = reader.readInt64()
var _2: String?
_2 = parseString(reader)
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.MessageUserReaction.messageUserReaction(userId: _1!, reaction: _2!)
}
else {
return nil
}
}
}
public enum BotInlineMessage: TypeConstructorDescription {
case botInlineMessageMediaAuto(flags: Int32, message: String, entities: [Api.MessageEntity]?, replyMarkup: Api.ReplyMarkup?)

View File

@ -4503,6 +4503,94 @@ public extension Api {
return result
})
}
public static func sendReaction(flags: Int32, peer: Api.InputPeer, msgId: Int32, reaction: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()
buffer.appendInt32(627641572)
serializeInt32(flags, buffer: buffer, boxed: false)
peer.serialize(buffer, true)
serializeInt32(msgId, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {serializeString(reaction!, buffer: buffer, boxed: false)}
return (FunctionDescription(name: "messages.sendReaction", parameters: [("flags", flags), ("peer", peer), ("msgId", msgId), ("reaction", reaction)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
let reader = BufferReader(buffer)
var result: Api.Updates?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Updates
}
return result
})
}
public static func getMessagesReactions(peer: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()
buffer.appendInt32(-1950707482)
peer.serialize(buffer, true)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(id.count))
for item in id {
serializeInt32(item, buffer: buffer, boxed: false)
}
return (FunctionDescription(name: "messages.getMessagesReactions", parameters: [("peer", peer), ("id", id)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
let reader = BufferReader(buffer)
var result: Api.Updates?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Updates
}
return result
})
}
public static func getMessageReactionsList(flags: Int32, peer: Api.InputPeer, id: Int32, reaction: String?, offset: String?, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.MessageReactionsList>) {
let buffer = Buffer()
buffer.appendInt32(-521245833)
serializeInt32(flags, buffer: buffer, boxed: false)
peer.serialize(buffer, true)
serializeInt32(id, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {serializeString(reaction!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 1) != 0 {serializeString(offset!, buffer: buffer, boxed: false)}
serializeInt32(limit, buffer: buffer, boxed: false)
return (FunctionDescription(name: "messages.getMessageReactionsList", parameters: [("flags", flags), ("peer", peer), ("id", id), ("reaction", reaction), ("offset", offset), ("limit", limit)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.MessageReactionsList? in
let reader = BufferReader(buffer)
var result: Api.messages.MessageReactionsList?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.messages.MessageReactionsList
}
return result
})
}
public static func setChatAvailableReactions(peer: Api.InputPeer, availableReactions: [String]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()
buffer.appendInt32(335875750)
peer.serialize(buffer, true)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(availableReactions.count))
for item in availableReactions {
serializeString(item, buffer: buffer, boxed: false)
}
return (FunctionDescription(name: "messages.setChatAvailableReactions", parameters: [("peer", peer), ("availableReactions", availableReactions)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
let reader = BufferReader(buffer)
var result: Api.Updates?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Updates
}
return result
})
}
public static func getAvailableReactions(hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.AvailableReactions>) {
let buffer = Buffer()
buffer.appendInt32(417243308)
serializeInt32(hash, buffer: buffer, boxed: false)
return (FunctionDescription(name: "messages.getAvailableReactions", parameters: [("hash", hash)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AvailableReactions? in
let reader = BufferReader(buffer)
var result: Api.messages.AvailableReactions?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.messages.AvailableReactions
}
return result
})
}
}
public struct channels {
public static func readHistory(channel: Api.InputChannel, maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {

View File

@ -1048,6 +1048,7 @@ public class Account {
self.managedOperationsDisposable.add(managedApplyPendingMessageReactionsActions(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start())
self.managedOperationsDisposable.add(managedSynchronizeEmojiKeywordsOperations(postbox: self.postbox, network: self.network).start())
self.managedOperationsDisposable.add(managedApplyPendingScheduledMessagesActions(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start())
self.managedOperationsDisposable.add(managedSynchronizeAvailableReactions(postbox: self.postbox, network: self.network).start())
if !supplementary {
self.managedOperationsDisposable.add(managedChatListFilters(postbox: self.postbox, network: self.network, accountPeerId: self.peerId).start())

View File

@ -2,7 +2,7 @@ import Foundation
import Postbox
import TelegramApi
/*extension ReactionsMessageAttribute {
extension ReactionsMessageAttribute {
func withUpdatedResults(_ reactions: Api.MessageReactions) -> ReactionsMessageAttribute {
switch reactions {
case let .messageReactions(flags, results, _):
@ -32,7 +32,7 @@ import TelegramApi
return ReactionsMessageAttribute(reactions: reactions)
}
}
}*/
}
public func mergedMessageReactions(attributes: [MessageAttribute]) -> ReactionsMessageAttribute? {
var current: ReactionsMessageAttribute?
@ -84,7 +84,7 @@ public func mergedMessageReactions(attributes: [MessageAttribute]) -> ReactionsM
}
}
/*extension ReactionsMessageAttribute {
extension ReactionsMessageAttribute {
convenience init(apiReactions: Api.MessageReactions) {
switch apiReactions {
case let .messageReactions(_, results, _):
@ -96,4 +96,4 @@ public func mergedMessageReactions(attributes: [MessageAttribute]) -> ReactionsM
})
}
}
}*/
}

View File

@ -116,7 +116,7 @@ public func tagsForStoreMessage(incoming: Bool, attributes: [MessageAttribute],
func apiMessagePeerId(_ messsage: Api.Message) -> PeerId? {
switch messsage {
case let .message(_, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
case let .message(_, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
let chatPeerId = messagePeerId
return chatPeerId.peerId
case let .messageEmpty(_, _, peerId):
@ -132,7 +132,7 @@ func apiMessagePeerId(_ messsage: Api.Message) -> PeerId? {
func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] {
switch message {
case let .message(_, _, fromId, chatPeerId, fwdHeader, viaBotId, _, _, _, media, _, entities, _, _, _, _, _, _, _, _):
case let .message(_, _, fromId, chatPeerId, fwdHeader, viaBotId, _, _, _, media, _, entities, _, _, _, _, _, _, _, _, _):
let peerId: PeerId = chatPeerId.peerId
var result = [peerId]
@ -228,7 +228,7 @@ func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] {
func apiMessageAssociatedMessageIds(_ message: Api.Message) -> [MessageId]? {
switch message {
case let .message(_, _, _, chatPeerId, _, _, replyTo, _, _, _, _, _, _, _, _, _, _, _, _, _):
case let .message(_, _, _, chatPeerId, _, _, replyTo, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
if let replyTo = replyTo {
let peerId: PeerId = chatPeerId.peerId
@ -374,7 +374,7 @@ func messageTextEntitiesFromApiEntities(_ entities: [Api.MessageEntity]) -> [Mes
extension StoreMessage {
convenience init?(apiMessage: Api.Message, namespace: MessageId.Namespace = Namespaces.Message.Cloud) {
switch apiMessage {
case let .message(flags, id, fromId, chatPeerId, fwdFrom, viaBotId, replyTo, date, message, media, replyMarkup, entities, views, forwards, replies, editDate, postAuthor, groupingId/*, reactions*/, restrictionReason, ttlPeriod):
case let .message(flags, id, fromId, chatPeerId, fwdFrom, viaBotId, replyTo, date, message, media, replyMarkup, entities, views, forwards, replies, editDate, postAuthor, groupingId, reactions, restrictionReason, ttlPeriod):
let resolvedFromId = fromId?.peerId ?? chatPeerId.peerId
let peerId: PeerId
@ -546,9 +546,9 @@ extension StoreMessage {
attributes.append(ContentRequiresValidationMessageAttribute())
}
/*if let reactions = reactions {
if let reactions = reactions {
attributes.append(ReactionsMessageAttribute(apiReactions: reactions))
}*/
}
if let replies = replies {
let recentRepliersPeerIds: [PeerId]?

View File

@ -813,7 +813,7 @@ public final class AccountViewTracker {
}
public func updateReactionsForMessageIds(messageIds: Set<MessageId>) {
/*self.queue.async {
self.queue.async {
var addedMessageIds: [MessageId] = []
let timestamp = Int32(CFAbsoluteTimeGetCurrent())
for messageId in messageIds {
@ -870,7 +870,7 @@ public final class AccountViewTracker {
break loop
}
}
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
})
default:
break
@ -892,7 +892,7 @@ public final class AccountViewTracker {
}
}
}
}*/
}
}
public func updateSeenLiveLocationForMessageIds(messageIds: Set<MessageId>) {

View File

@ -64,7 +64,7 @@ func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, mes
var updatedTimestamp: Int32?
if let apiMessage = apiMessage {
switch apiMessage {
case let .message(_, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _):
case let .message(_, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _):
updatedTimestamp = date
case .messageEmpty:
break

View File

@ -0,0 +1,258 @@
import Foundation
import TelegramApi
import Postbox
import SwiftSignalKit
public final class AvailableReactions: Equatable, Codable {
public final class Reaction: Equatable, Codable {
private enum CodingKeys: String, CodingKey {
case value
case title
case staticIcon
case selectAnimation
case activateAnimation
case effectAnimation
}
public let value: String
public let title: String
public let staticIcon: TelegramMediaFile
public let selectAnimation: TelegramMediaFile
public let activateAnimation: TelegramMediaFile
public let effectAnimation: TelegramMediaFile
public init(
value: String,
title: String,
staticIcon: TelegramMediaFile,
selectAnimation: TelegramMediaFile,
activateAnimation: TelegramMediaFile,
effectAnimation: TelegramMediaFile
) {
self.value = value
self.title = title
self.staticIcon = staticIcon
self.selectAnimation = selectAnimation
self.activateAnimation = activateAnimation
self.effectAnimation = effectAnimation
}
public static func ==(lhs: Reaction, rhs: Reaction) -> Bool {
if lhs.value != rhs.value {
return false
}
if lhs.title != rhs.title {
return false
}
if lhs.staticIcon != rhs.staticIcon {
return false
}
if lhs.selectAnimation != rhs.selectAnimation {
return false
}
if lhs.activateAnimation != rhs.activateAnimation {
return false
}
if lhs.effectAnimation != rhs.effectAnimation {
return false
}
return true
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.value = try container.decode(String.self, forKey: .value)
self.title = try container.decode(String.self, forKey: .title)
let staticIconData = try container.decode(AdaptedPostboxDecoder.RawObjectData.self, forKey: .staticIcon)
self.staticIcon = TelegramMediaFile(decoder: PostboxDecoder(buffer: MemoryBuffer(data: staticIconData.data)))
let selectAnimationData = try container.decode(AdaptedPostboxDecoder.RawObjectData.self, forKey: .selectAnimation)
self.selectAnimation = TelegramMediaFile(decoder: PostboxDecoder(buffer: MemoryBuffer(data: selectAnimationData.data)))
let activateAnimationData = try container.decode(AdaptedPostboxDecoder.RawObjectData.self, forKey: .activateAnimation)
self.activateAnimation = TelegramMediaFile(decoder: PostboxDecoder(buffer: MemoryBuffer(data: activateAnimationData.data)))
let effectAnimationData = try container.decode(AdaptedPostboxDecoder.RawObjectData.self, forKey: .effectAnimation)
self.effectAnimation = TelegramMediaFile(decoder: PostboxDecoder(buffer: MemoryBuffer(data: effectAnimationData.data)))
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.value, forKey: .value)
try container.encode(self.title, forKey: .title)
try container.encode(PostboxEncoder().encodeObjectToRawData(self.staticIcon), forKey: .staticIcon)
try container.encode(PostboxEncoder().encodeObjectToRawData(self.selectAnimation), forKey: .selectAnimation)
try container.encode(PostboxEncoder().encodeObjectToRawData(self.activateAnimation), forKey: .activateAnimation)
try container.encode(PostboxEncoder().encodeObjectToRawData(self.effectAnimation), forKey: .effectAnimation)
}
}
private enum CodingKeys: String, CodingKey {
case hash
case reactions
}
public let hash: Int32
public let reactions: [Reaction]
public init(
hash: Int32,
reactions: [Reaction]
) {
self.hash = hash
self.reactions = reactions
}
public static func ==(lhs: AvailableReactions, rhs: AvailableReactions) -> Bool {
if lhs.hash != rhs.hash {
return false
}
if lhs.reactions != rhs.reactions {
return false
}
return true
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.hash = try container.decode(Int32.self, forKey: .hash)
self.reactions = try container.decode([Reaction].self, forKey: .reactions)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.hash, forKey: .hash)
try container.encode(self.reactions, forKey: .reactions)
}
}
private extension AvailableReactions.Reaction {
convenience init?(apiReaction: Api.AvailableReaction) {
switch apiReaction {
case let .availableReaction(reaction, title, staticIcon, selectAnimation, activateAnimation, effectAnimation):
guard let staticIconFile = telegramMediaFileFromApiDocument(staticIcon) else {
return nil
}
guard let selectAnimationFile = telegramMediaFileFromApiDocument(selectAnimation) else {
return nil
}
guard let activateAnimationFile = telegramMediaFileFromApiDocument(activateAnimation) else {
return nil
}
guard let effectAnimationFile = telegramMediaFileFromApiDocument(effectAnimation) else {
return nil
}
self.init(
value: reaction,
title: title,
staticIcon: staticIconFile,
selectAnimation: selectAnimationFile,
activateAnimation: activateAnimationFile,
effectAnimation: effectAnimationFile
)
}
}
}
func _internal_cachedAvailableReactions(postbox: Postbox) -> Signal<AvailableReactions?, NoError> {
return postbox.transaction { transaction -> AvailableReactions? in
return _internal_cachedAvailableReactions(transaction: transaction)
}
}
func _internal_cachedAvailableReactions(transaction: Transaction) -> AvailableReactions? {
let key = ValueBoxKey(length: 8)
key.setInt64(0, value: 0)
let cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.availableReactions, key: key))?.get(AvailableReactions.self)
if let cached = cached {
return cached
} else {
return nil
}
}
func _internal_setCachedAvailableReactions(transaction: Transaction, availableReactions: AvailableReactions) {
let key = ValueBoxKey(length: 8)
key.setInt64(0, value: 0)
if let entry = CodableEntry(availableReactions) {
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.availableReactions, key: key), entry: entry, collectionSpec: ItemCacheCollectionSpec(lowWaterItemCount: 10, highWaterItemCount: 10))
}
}
func managedSynchronizeAvailableReactions(postbox: Postbox, network: Network) -> Signal<Never, NoError> {
let poll = Signal<Never, NoError> { subscriber in
let signal: Signal<Never, NoError> = _internal_cachedAvailableReactions(postbox: postbox)
|> mapToSignal { current in
return (network.request(Api.functions.messages.getAvailableReactions(hash: current?.hash ?? 0))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.messages.AvailableReactions?, NoError> in
return .single(nil)
}
|> mapToSignal { result -> Signal<Never, NoError> in
return postbox.transaction { transaction -> Signal<Never, NoError> in
guard let result = result else {
return .complete()
}
switch result {
case let .availableReactions(hash, reactions):
let availableReactions = AvailableReactions(
hash: hash,
reactions: reactions.compactMap(AvailableReactions.Reaction.init(apiReaction:))
)
_internal_setCachedAvailableReactions(transaction: transaction, availableReactions: availableReactions)
case .availableReactionsNotModified:
break
}
var signals: [Signal<Never, NoError>] = []
if let availableReactions = _internal_cachedAvailableReactions(transaction: transaction) {
var resources: [MediaResource] = []
for reaction in availableReactions.reactions {
resources.append(reaction.staticIcon.resource)
resources.append(reaction.selectAnimation.resource)
resources.append(reaction.activateAnimation.resource)
resources.append(reaction.effectAnimation.resource)
}
for resource in resources {
signals.append(
fetchedMediaResource(mediaBox: postbox.mediaBox, reference: .standalone(resource: resource))
|> ignoreValues
|> `catch` { _ -> Signal<Never, NoError> in
return .complete()
}
)
}
}
return combineLatest(signals)
|> ignoreValues
}
|> switchToLatest
})
}
return signal.start(completed: {
subscriber.putCompletion()
})
}
return (
poll
|> then(
.complete()
|> suspendAwareDelay(1.0 * 60.0 * 60.0, queue: Queue.concurrentDefaultQueue())
)
)
|> restart
}

View File

@ -32,8 +32,7 @@ private enum RequestUpdateMessageReactionError {
}
private func requestUpdateMessageReaction(postbox: Postbox, network: Network, stateManager: AccountStateManager, messageId: MessageId) -> Signal<Never, RequestUpdateMessageReactionError> {
return .never()
/*return postbox.transaction { transaction -> (Peer, String?)? in
return postbox.transaction { transaction -> (Peer, String?)? in
guard let peer = transaction.getPeer(messageId.peerId) else {
return nil
}
@ -89,7 +88,7 @@ private func requestUpdateMessageReaction(postbox: Postbox, network: Network, st
|> castError(RequestUpdateMessageReactionError.self)
|> ignoreValues
}
}*/
}
}
private final class ManagedApplyPendingMessageReactionsActionsHelper {

View File

@ -210,7 +210,7 @@ public class BoxedMessage: NSObject {
public class Serialization: NSObject, MTSerialization {
public func currentLayer() -> UInt {
return 135
return 136
}
public func parseMessage(_ data: Data!) -> Any! {

View File

@ -58,7 +58,7 @@ class UpdateMessageService: NSObject, MTMessageService {
self.putNext(groups)
}
case let .updateShortChatMessage(flags, id, fromId, chatId, message, pts, ptsCount, date, fwdFrom, viaBotId, replyHeader, entities, ttlPeriod):
let generatedMessage = Api.Message.message(flags: flags, id: id, fromId: .peerUser(userId: fromId), peerId: Api.Peer.peerChat(chatId: chatId), fwdFrom: fwdFrom, viaBotId: viaBotId, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil/*, reactions: nil*/, restrictionReason: nil, ttlPeriod: ttlPeriod)
let generatedMessage = Api.Message.message(flags: flags, id: id, fromId: .peerUser(userId: fromId), peerId: Api.Peer.peerChat(chatId: chatId), fwdFrom: fwdFrom, viaBotId: viaBotId, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, reactions: nil, restrictionReason: nil, ttlPeriod: ttlPeriod)
let update = Api.Update.updateNewMessage(message: generatedMessage, pts: pts, ptsCount: ptsCount)
let groups = groupUpdates([update], users: [], chats: [], date: date, seqRange: nil)
if groups.count != 0 {
@ -74,7 +74,7 @@ class UpdateMessageService: NSObject, MTMessageService {
let generatedPeerId = Api.Peer.peerUser(userId: userId)
let generatedMessage = Api.Message.message(flags: flags, id: id, fromId: generatedFromId, peerId: generatedPeerId, fwdFrom: fwdFrom, viaBotId: viaBotId, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil/*, reactions: nil*/, restrictionReason: nil, ttlPeriod: ttlPeriod)
let generatedMessage = Api.Message.message(flags: flags, id: id, fromId: generatedFromId, peerId: generatedPeerId, fwdFrom: fwdFrom, viaBotId: viaBotId, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, reactions: nil, restrictionReason: nil, ttlPeriod: ttlPeriod)
let update = Api.Update.updateNewMessage(message: generatedMessage, pts: pts, ptsCount: ptsCount)
let groups = groupUpdates([update], users: [], chats: [], date: date, seqRange: nil)
if groups.count != 0 {

View File

@ -90,7 +90,7 @@ extension Api.MessageMedia {
extension Api.Message {
var rawId: Int32 {
switch self {
case let .message(_, id, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
case let .message(_, id, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
return id
case let .messageEmpty(_, id, _):
return id
@ -101,7 +101,7 @@ extension Api.Message {
func id(namespace: MessageId.Namespace = Namespaces.Message.Cloud) -> MessageId? {
switch self {
case let .message(_, id, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
case let .message(_, id, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
let peerId: PeerId = messagePeerId.peerId
return MessageId(peerId: peerId, namespace: namespace, id: id)
case let .messageEmpty(_, id, peerId):
@ -118,7 +118,7 @@ extension Api.Message {
var timestamp: Int32? {
switch self {
case let .message(_, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _):
case let .message(_, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _):
return date
case let .messageService(_, _, _, _, _, date, _, _):
return date
@ -129,7 +129,7 @@ extension Api.Message {
var preCachedResources: [(MediaResource, Data)]? {
switch self {
case let .message(_, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _):
case let .message(_, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _):
return media?.preCachedResources
default:
return nil

View File

@ -80,6 +80,7 @@ public struct Namespaces {
public static let cachedPeerInvitationImporters: Int8 = 16
public static let cachedPeerExportedInvitations: Int8 = 17
public static let cachedSendAsPeers: Int8 = 18
public static let availableReactions: Int8 = 19
}
public struct UnorderedItemList {

View File

@ -267,14 +267,14 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee
switch result {
case let .chatFull(fullChat, chats, users):
switch fullChat {
case let .chatFull(_, _, _, _, _, notifySettings, _, _, _, _, _, _, _, _, _, _):
case let .chatFull(_, _, _, _, _, notifySettings, _, _, _, _, _, _, _, _, _, _, _):
transaction.updateCurrentPeerNotificationSettings([peerId: TelegramPeerNotificationSettings(apiSettings: notifySettings)])
case .channelFull:
break
}
switch fullChat {
case let .chatFull(chatFullFlags, _, chatFullAbout, chatFullParticipants, chatFullChatPhoto, _, chatFullExportedInvite, chatFullBotInfo, chatFullPinnedMsgId, _, chatFullCall, _, chatFullGroupcallDefaultJoinAs, chatFullThemeEmoticon, chatFullRequestsPending, _):
case let .chatFull(chatFullFlags, _, chatFullAbout, chatFullParticipants, chatFullChatPhoto, _, chatFullExportedInvite, chatFullBotInfo, chatFullPinnedMsgId, _, chatFullCall, _, chatFullGroupcallDefaultJoinAs, chatFullThemeEmoticon, chatFullRequestsPending, _, _):
var botInfos: [CachedPeerBotInfo] = []
for botInfo in chatFullBotInfo ?? [] {
switch botInfo {
@ -396,14 +396,14 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee
switch result {
case let .chatFull(fullChat, chats, users):
switch fullChat {
case let .channelFull(_, _, _, _, _, _, _, _, _, _, _, _, notifySettings, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
case let .channelFull(_, _, _, _, _, _, _, _, _, _, _, _, notifySettings, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
transaction.updateCurrentPeerNotificationSettings([peerId: TelegramPeerNotificationSettings(apiSettings: notifySettings)])
case .chatFull:
break
}
switch fullChat {
case let .channelFull(flags, _, about, participantsCount, adminsCount, kickedCount, bannedCount, _, _, _, _, chatPhoto, _, apiExportedInvite, apiBotInfos, migratedFromChatId, migratedFromMaxId, pinnedMsgId, stickerSet, minAvailableMsgId, _, linkedChatId, location, slowmodeSeconds, slowmodeNextSendDate, statsDc, _, inputCall, ttl, pendingSuggestions, groupcallDefaultJoinAs, themeEmoticon, requestsPending, _, defaultSendAs):
case let .channelFull(flags, _, about, participantsCount, adminsCount, kickedCount, bannedCount, _, _, _, _, chatPhoto, _, apiExportedInvite, apiBotInfos, migratedFromChatId, migratedFromMaxId, pinnedMsgId, stickerSet, minAvailableMsgId, _, linkedChatId, location, slowmodeSeconds, slowmodeNextSendDate, statsDc, _, inputCall, ttl, pendingSuggestions, groupcallDefaultJoinAs, themeEmoticon, requestsPending, _, defaultSendAs, _):
var channelFlags = CachedChannelFlags()
if (flags & (1 << 3)) != 0 {
channelFlags.insert(.canDisplayParticipants)

View File

@ -95,5 +95,9 @@ public extension TelegramEngine {
)
}
}
public func availableReactions() -> Signal<AvailableReactions?, NoError> {
return _internal_cachedAvailableReactions(postbox: self.account.postbox)
}
}
}

View File

@ -247,6 +247,7 @@ swift_library(
"//submodules/MeshAnimationCache:MeshAnimationCache",
"//submodules/DirectMediaImageCache:DirectMediaImageCache",
"//submodules/CodeInputView:CodeInputView",
"//submodules/Components/ReactionButtonListComponent:ReactionButtonListComponent",
] + select({
"@build_bazel_rules_apple//apple:ios_armv7": [],
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,

View File

@ -430,6 +430,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
private weak var currentReactionContextController: ContextController?
private weak var currentReactionContextItemNode: ListViewItemNode?
private weak var currentStandaloneReactionAnimation: StandaloneReactionAnimation?
private weak var currentStandaloneReactionItemNode: ListViewItemNode?
private var screenCaptureManager: ScreenCaptureDetectionManager?
private let chatAdditionalDataDisposable = MetaDisposable()
@ -942,46 +945,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
}
let additionalAnimatedEmojiStickers = strongSelf.context.engine.stickers.loadedStickerPack(reference: .animatedEmojiAnimations, forceActualized: false)
|> map { animatedEmoji -> [String: [Int: StickerPackItem]] in
let sequence = "0⃣1⃣2⃣3⃣4⃣5⃣6⃣7⃣8⃣9".strippedEmoji
var animatedEmojiStickers: [String: [Int: StickerPackItem]] = [:]
switch animatedEmoji {
case let .result(_, items, _):
for item in items {
let indexKeys = item.getStringRepresentationsOfIndexKeys()
if indexKeys.count > 1, let first = indexKeys.first, let last = indexKeys.last {
let emoji: String?
let indexEmoji: String?
if sequence.contains(first.strippedEmoji) {
emoji = last
indexEmoji = first
} else if sequence.contains(last.strippedEmoji) {
emoji = first
indexEmoji = last
} else {
emoji = nil
indexEmoji = nil
}
if let emoji = emoji?.strippedEmoji, let indexEmoji = indexEmoji?.strippedEmoji.first, let strIndex = sequence.firstIndex(of: indexEmoji) {
let index = sequence.distance(from: sequence.startIndex, to: strIndex)
if animatedEmojiStickers[emoji] != nil {
animatedEmojiStickers[emoji]![index] = item
} else {
animatedEmojiStickers[emoji] = [index: item]
}
}
}
}
default:
break
}
return animatedEmojiStickers
}
let _ = combineLatest(queue: .mainQueue(), contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState: strongSelf.presentationInterfaceState, context: strongSelf.context, messages: updatedMessages, controllerInteraction: strongSelf.controllerInteraction, selectAll: selectAll, interfaceInteraction: strongSelf.interfaceInteraction), strongSelf.context.engine.stickers.loadedStickerPack(reference: .animatedEmoji, forceActualized: false), additionalAnimatedEmojiStickers, ApplicationSpecificNotice.getChatTextSelectionTips(accountManager: strongSelf.context.sharedContext.accountManager)
).start(next: { actions, animatedEmojiStickers, additionalAnimatedEmojiStickers, chatTextSelectionTips in
let _ = combineLatest(queue: .mainQueue(),
contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState: strongSelf.presentationInterfaceState, context: strongSelf.context, messages: updatedMessages, controllerInteraction: strongSelf.controllerInteraction, selectAll: selectAll, interfaceInteraction: strongSelf.interfaceInteraction),
strongSelf.context.engine.stickers.availableReactions(),
ApplicationSpecificNotice.getChatTextSelectionTips(accountManager: strongSelf.context.sharedContext.accountManager)
).start(next: { actions, availableReactions, chatTextSelectionTips in
var actions = actions
guard let strongSelf = self, !actions.items.isEmpty else {
@ -1023,32 +991,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
actions.context = strongSelf.context
if canAddMessageReactions(message: message) {
if case let .result(_, stickers, _) = animatedEmojiStickers {
for fullSticker in additionalAnimatedEmojiStickers {
guard let fullStickerItem = fullSticker.value.first?.value else {
continue
}
inner: for sticker in stickers {
for attribute in sticker.file.attributes {
if case let .Sticker(displayText, _, _) = attribute {
if displayText == fullSticker.key {
actions.reactionItems.append(ReactionContextItem(
reaction: ReactionContextItem.Reaction(rawValue: displayText),
listAnimation: sticker.file,
applicationAnimation: fullStickerItem.file
))
break inner
}
}
}
}
}
if canAddMessageReactions(message: message), let availableReactions = availableReactions {
for reaction in availableReactions.reactions {
actions.reactionItems.append(ReactionContextItem(
reaction: ReactionContextItem.Reaction(rawValue: reaction.value),
stillAnimation: reaction.selectAnimation,
listAnimation: reaction.activateAnimation,
applicationAnimation: reaction.effectAnimation
))
}
actions.reactionItems.sort(by: { lhs, rhs in
return lhs.reaction.rawValue < rhs.reaction.rawValue
})
}
let controller = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .extracted(ChatMessageContextExtractedContentSource(chatNode: strongSelf.chatDisplayNode, postbox: strongSelf.context.account.postbox, message: message, selectAll: selectAll)), items: .single(actions), recognizer: recognizer, gesture: gesture)
@ -1084,18 +1035,18 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
guard let controller = controller else {
return
}
if let itemNode = itemNode, let (targetEmptyNode, targetFilledNode) = itemNode.targetReactionNode(value: updatedReaction) {
if let itemNode = itemNode, let targetView = itemNode.targetReactionView(value: updatedReaction) {
strongSelf.currentReactionContextController = controller
strongSelf.currentReactionContextItemNode = itemNode
controller.dismissWithReaction(value: updatedReaction, targetEmptyNode: targetEmptyNode, targetFilledNode: targetFilledNode, hideNode: true, completion: { [weak itemNode, weak targetFilledNode] in
guard let strongSelf = self, let itemNode = itemNode, let targetFilledNode = targetFilledNode else {
controller.dismissWithReaction(value: updatedReaction, targetView: targetView, hideNode: true, completion: { [weak itemNode, weak targetView] in
guard let strongSelf = self, let itemNode = itemNode, let targetView = targetView else {
return
}
let _ = strongSelf
let _ = itemNode
let _ = targetFilledNode
let _ = targetView
/*let targetFrame = targetFilledNode.view.convert(targetFilledNode.bounds, to: itemNode.view).offsetBy(dx: 0.0, dy: itemNode.insets.top)
@ -1145,7 +1096,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
strongSelf.window?.presentInGlobalOverlay(controller)
})
}
}, updateMessageReaction: { [weak self] message in
}, updateMessageReaction: { [weak self] message, value in
guard let strongSelf = self else {
return
}
@ -1154,7 +1105,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return
}
var updatedReaction: String? = ""
var updatedReaction: String? = value
for attribute in message.attributes {
if let attribute = attribute as? ReactionsMessageAttribute {
for reaction in attribute.reactions {
@ -1171,21 +1122,48 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
}
strongSelf.chatDisplayNode.historyNode.forEachItemNode { itemNode in
if let itemNode = itemNode as? ChatMessageItemView, let item = itemNode.item {
if item.message.id == message.id {
itemNode.awaitingAppliedReaction = (updatedReaction, { [weak itemNode] in
if let updatedReaction = updatedReaction, let itemNode = itemNode, let (_, targetFilledNode) = itemNode.targetReactionNode(value: updatedReaction) {
targetFilledNode.layer.animateSpring(from: 0.01 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5, initialVelocity: 0.0, damping: 90.0)
if let strongSelf = self {
if strongSelf.selectPollOptionFeedback == nil {
strongSelf.selectPollOptionFeedback = HapticFeedback()
}
strongSelf.selectPollOptionFeedback?.tap()
if updatedReaction != nil {
strongSelf.chatDisplayNode.historyNode.forEachItemNode { itemNode in
if let itemNode = itemNode as? ChatMessageItemView, let item = itemNode.item {
if item.message.id == message.id {
itemNode.awaitingAppliedReaction = (updatedReaction, { [weak itemNode] in
guard let strongSelf = self else {
return
}
}
})
if let updatedReaction = updatedReaction, let itemNode = itemNode, let item = itemNode.item, let availableReactions = item.associatedData.availableReactions, let targetView = itemNode.targetReactionView(value: updatedReaction) {
for reaction in availableReactions.reactions {
if reaction.value == updatedReaction {
let standaloneReactionAnimation = StandaloneReactionAnimation(context: strongSelf.context, theme: strongSelf.presentationData.theme, reaction: ReactionContextItem(
reaction: ReactionContextItem.Reaction(rawValue: reaction.value),
stillAnimation: reaction.selectAnimation,
listAnimation: reaction.activateAnimation,
applicationAnimation: reaction.effectAnimation
))
strongSelf.currentStandaloneReactionAnimation = standaloneReactionAnimation
strongSelf.currentStandaloneReactionItemNode = itemNode
strongSelf.chatDisplayNode.addSubnode(standaloneReactionAnimation)
standaloneReactionAnimation.frame = strongSelf.chatDisplayNode.bounds
standaloneReactionAnimation.animateReactionSelection(targetView: targetView, hideNode: true, completion: { [weak standaloneReactionAnimation] in
standaloneReactionAnimation?.removeFromSupernode()
})
break
}
}
/*targetView.layer.animateSpring(from: 0.01 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5, initialVelocity: 0.0, damping: 90.0)
if let strongSelf = self {
if strongSelf.selectPollOptionFeedback == nil {
strongSelf.selectPollOptionFeedback = HapticFeedback()
}
strongSelf.selectPollOptionFeedback?.tap()
}*/
}
})
}
}
}
}
@ -4781,6 +4759,16 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
}
if let standaloneReactionItemNode = strongSelf.currentStandaloneReactionItemNode, let currentStandaloneReactionAnimation = strongSelf.currentStandaloneReactionAnimation {
if let itemNode = itemNode {
if itemNode === standaloneReactionItemNode {
currentStandaloneReactionAnimation.addRelativeContentOffset(CGPoint(x: 0.0, y: -offset), transition: transition)
}
} else {
currentStandaloneReactionAnimation.addRelativeContentOffset(CGPoint(x: 0.0, y: -offset), transition: transition)
}
}
strongSelf.chatDisplayNode.messageTransitionNode.addExternalOffset(offset: offset, transition: transition, itemNode: itemNode)
}
@ -14319,15 +14307,8 @@ extension Peer {
}
func canAddMessageReactions(message: Message) -> Bool {
return false
/*if let peer = message.peers[message.id.peerId] {
if let channel = peer as? TelegramChannel {
if case .group = channel.info {
} else {
return false
}
} else if let _ = peer as? TelegramGroup {
} else {
if let peer = message.peers[message.id.peerId] {
if let _ = peer as? TelegramSecretChat {
return false
}
} else {
@ -14338,5 +14319,5 @@ func canAddMessageReactions(message: Message) -> Bool {
return false
}
}
return true*/
return true
}

View File

@ -51,7 +51,7 @@ public final class ChatControllerInteraction {
let openPeer: (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void
let openPeerMention: (String) -> Void
let openMessageContextMenu: (Message, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?) -> Void
let updateMessageReaction: (Message) -> Void
let updateMessageReaction: (Message, String) -> Void
let activateMessagePinch: (PinchSourceContainerNode) -> Void
let openMessageContextActions: (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void
let navigateToMessage: (MessageId, MessageId) -> Void
@ -147,7 +147,7 @@ public final class ChatControllerInteraction {
openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void,
openPeerMention: @escaping (String) -> Void,
openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?) -> Void,
updateMessageReaction: @escaping (Message) -> Void,
updateMessageReaction: @escaping (Message, String) -> Void,
activateMessagePinch: @escaping (PinchSourceContainerNode) -> Void,
openMessageContextActions: @escaping (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void,
navigateToMessage: @escaping (MessageId, MessageId) -> Void,
@ -313,7 +313,7 @@ public final class ChatControllerInteraction {
static var `default`: ChatControllerInteraction {
return ChatControllerInteraction(openMessage: { _, _ in
return false }, openPeer: { _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _ in }, updateMessageReaction: { _ in }, activateMessagePinch: { _ in }, openMessageContextActions: { _, _, _, _ in }, navigateToMessage: { _, _ in }, navigateToMessageStandalone: { _ in }, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _, _, _ in return false }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _ in return false }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openTheme: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in
return false }, openPeer: { _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _ in }, updateMessageReaction: { _, _ in }, activateMessagePinch: { _ in }, openMessageContextActions: { _, _, _, _ in }, navigateToMessage: { _, _ in }, navigateToMessageStandalone: { _ in }, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _, _, _ in return false }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _ in return false }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openTheme: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in
}, presentController: { _, _ in }, presentControllerInCurrent: { _, _ in }, navigationController: {
return nil
}, chatControllerNode: {

View File

@ -312,7 +312,7 @@ private final class ChatHistoryTransactionOpaqueState {
}
}
private func extractAssociatedData(chatLocation: ChatLocation, view: MessageHistoryView, automaticDownloadNetworkType: MediaAutoDownloadNetworkType, animatedEmojiStickers: [String: [StickerPackItem]], additionalAnimatedEmojiStickers: [String: [Int: StickerPackItem]], subject: ChatControllerSubject?, currentlyPlayingMessageId: MessageIndex?, isCopyProtectionEnabled: Bool) -> ChatMessageItemAssociatedData {
private func extractAssociatedData(chatLocation: ChatLocation, view: MessageHistoryView, automaticDownloadNetworkType: MediaAutoDownloadNetworkType, animatedEmojiStickers: [String: [StickerPackItem]], additionalAnimatedEmojiStickers: [String: [Int: StickerPackItem]], subject: ChatControllerSubject?, currentlyPlayingMessageId: MessageIndex?, isCopyProtectionEnabled: Bool, availableReactions: AvailableReactions?) -> ChatMessageItemAssociatedData {
var automaticMediaDownloadPeerType: MediaAutoDownloadPeerType = .channel
var contactsPeerIds: Set<PeerId> = Set()
var channelDiscussionGroup: ChatMessageItemAssociatedData.ChannelDiscussionGroupStatus = .unknown
@ -361,7 +361,7 @@ private func extractAssociatedData(chatLocation: ChatLocation, view: MessageHist
}
}
return ChatMessageItemAssociatedData(automaticDownloadPeerType: automaticMediaDownloadPeerType, automaticDownloadNetworkType: automaticDownloadNetworkType, isRecentActions: false, subject: subject, contactsPeerIds: contactsPeerIds, channelDiscussionGroup: channelDiscussionGroup, animatedEmojiStickers: animatedEmojiStickers, additionalAnimatedEmojiStickers: additionalAnimatedEmojiStickers, currentlyPlayingMessageId: currentlyPlayingMessageId, isCopyProtectionEnabled: isCopyProtectionEnabled)
return ChatMessageItemAssociatedData(automaticDownloadPeerType: automaticMediaDownloadPeerType, automaticDownloadNetworkType: automaticDownloadNetworkType, isRecentActions: false, subject: subject, contactsPeerIds: contactsPeerIds, channelDiscussionGroup: channelDiscussionGroup, animatedEmojiStickers: animatedEmojiStickers, additionalAnimatedEmojiStickers: additionalAnimatedEmojiStickers, currentlyPlayingMessageId: currentlyPlayingMessageId, isCopyProtectionEnabled: isCopyProtectionEnabled, availableReactions: availableReactions)
}
private extension ChatHistoryLocationInput {
@ -479,6 +479,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
private let galleryHiddenMesageAndMediaDisposable = MetaDisposable()
private let messageProcessingManager = ChatMessageThrottledProcessingManager()
private let messageWithReactionsProcessingManager = ChatMessageThrottledProcessingManager()
let adSeenProcessingManager = ChatMessageThrottledProcessingManager()
private let seenLiveLocationProcessingManager = ChatMessageThrottledProcessingManager()
private let unsupportedMessageProcessingManager = ChatMessageThrottledProcessingManager()
@ -689,6 +690,9 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
self.messageProcessingManager.process = { [weak context] messageIds in
context?.account.viewTracker.updateViewCountForMessageIds(messageIds: messageIds, clientId: clientId.with { $0 })
}
self.messageWithReactionsProcessingManager.process = { [weak context] messageIds in
context?.account.viewTracker.updateReactionsForMessageIds(messageIds: messageIds)
}
self.adSeenProcessingManager.process = { [weak self] messageIds in
guard let strongSelf = self, let adMessagesContext = strongSelf.adMessagesContext else {
return
@ -977,6 +981,8 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
customThreadOutgoingReadState = .single(nil)
}
let availableReactions = context.engine.stickers.availableReactions()
let historyViewTransitionDisposable = combineLatest(queue: messageViewQueue,
historyViewUpdate,
self.chatPresentationDataPromise.get(),
@ -991,8 +997,9 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
customChannelDiscussionReadState,
customThreadOutgoingReadState,
self.currentlyPlayingMessageIdPromise.get(),
adMessages
).start(next: { [weak self] update, chatPresentationData, selectedMessages, updatingMedia, networkType, historyAppearsCleared, pendingUnpinnedAllMessages, pendingRemovedMessages, animatedEmojiStickers, additionalAnimatedEmojiStickers, customChannelDiscussionReadState, customThreadOutgoingReadState, currentlyPlayingMessageId, adMessages in
adMessages,
availableReactions
).start(next: { [weak self] update, chatPresentationData, selectedMessages, updatingMedia, networkType, historyAppearsCleared, pendingUnpinnedAllMessages, pendingRemovedMessages, animatedEmojiStickers, additionalAnimatedEmojiStickers, customChannelDiscussionReadState, customThreadOutgoingReadState, currentlyPlayingMessageId, adMessages, availableReactions in
func applyHole() {
Queue.mainQueue().async {
if let strongSelf = self {
@ -1113,7 +1120,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
isCopyProtectionEnabled = peer.isCopyProtectionEnabled
}
}
let associatedData = extractAssociatedData(chatLocation: chatLocation, view: view, automaticDownloadNetworkType: networkType, animatedEmojiStickers: animatedEmojiStickers, additionalAnimatedEmojiStickers: additionalAnimatedEmojiStickers, subject: subject, currentlyPlayingMessageId: currentlyPlayingMessageId, isCopyProtectionEnabled: isCopyProtectionEnabled)
let associatedData = extractAssociatedData(chatLocation: chatLocation, view: view, automaticDownloadNetworkType: networkType, animatedEmojiStickers: animatedEmojiStickers, additionalAnimatedEmojiStickers: additionalAnimatedEmojiStickers, subject: subject, currentlyPlayingMessageId: currentlyPlayingMessageId, isCopyProtectionEnabled: isCopyProtectionEnabled, availableReactions: availableReactions)
let filteredEntries = chatHistoryEntriesForView(
location: chatLocation,
@ -1767,6 +1774,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
let toLaterRange = (historyView.filteredEntries.count - 1 - (visible.firstIndex - 1), historyView.filteredEntries.count - 1)
var messageIdsWithViewCount: [MessageId] = []
var messageIdsWithPossibleReactions: [MessageId] = []
var messageIdsWithLiveLocation: [MessageId] = []
var messageIdsWithUnsupportedMedia: [MessageId] = []
var messageIdsWithRefreshMedia: [MessageId] = []
@ -1802,8 +1810,11 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
contentRequiredValidation = true
}
}
var hasAction = false
for media in message.media {
if let _ = media as? TelegramMediaUnsupported {
if let _ = media as? TelegramMediaAction {
hasAction = true
} else if let _ = media as? TelegramMediaUnsupported {
contentRequiredValidation = true
} else if message.flags.contains(.Incoming), let media = media as? TelegramMediaMap, let liveBroadcastingTimeout = media.liveBroadcastingTimeout {
let timestamp = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
@ -1830,6 +1841,9 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
}
}
}
if !hasAction {
messageIdsWithPossibleReactions.append(message.id)
}
if contentRequiredValidation {
messageIdsWithUnsupportedMedia.append(message.id)
}
@ -1967,6 +1981,9 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
if !messageIdsWithUnseenPersonalMention.isEmpty {
self.messageMentionProcessingManager.add(messageIdsWithUnseenPersonalMention)
}
if !messageIdsWithPossibleReactions.isEmpty {
self.messageWithReactionsProcessingManager.add(messageIdsWithPossibleReactions)
}
self.currentEarlierPrefetchMessages = toEarlierMediaMessages
self.currentLaterPrefetchMessages = toLaterMediaMessages

View File

@ -889,7 +889,23 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
isReplyThread = true
}
let (dateAndStatusSize, dateAndStatusApply) = makeDateAndStatusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), dateReactions, dateReplies, item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, item.message.isSelfExpiring)
let statusSuggestedWidthAndContinue = makeDateAndStatusLayout(ChatMessageDateAndStatusNode.Arguments(
context: item.context,
presentationData: item.presentationData,
edited: edited,
impressionCount: viewCount,
dateText: dateText,
type: statusType,
layoutInput: .standalone,
constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude),
availableReactions: item.associatedData.availableReactions,
reactions: dateReactions,
replyCount: dateReplies,
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
hasAutoremove: item.message.isSelfExpiring
))
let (dateAndStatusSize, dateAndStatusApply) = statusSuggestedWidthAndContinue.1(statusSuggestedWidthAndContinue.0)
var viaBotApply: (TextNodeLayout, () -> TextNode)?
var replyInfoApply: (CGSize, () -> ChatMessageReplyInfoNode)?
@ -1301,41 +1317,8 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
}
if let (_, f) = strongSelf.awaitingAppliedReaction {
/*var bounds = strongSelf.bounds
let offset = bounds.origin.x
bounds.origin.x = 0.0
strongSelf.bounds = bounds
var shadowBounds = strongSelf.shadowNode.bounds
let shadowOffset = shadowBounds.origin.x
shadowBounds.origin.x = 0.0
strongSelf.shadowNode.bounds = shadowBounds
if !offset.isZero {
strongSelf.layer.animateBoundsOriginXAdditive(from: offset, to: 0.0, duration: 0.2, timingFunction: kCAMediaTimingFunctionSpring)
}
if !shadowOffset.isZero {
strongSelf.shadowNode.layer.animateBoundsOriginXAdditive(from: shadowOffset, to: 0.0, duration: 0.2, timingFunction: kCAMediaTimingFunctionSpring)
}
if let swipeToReplyNode = strongSelf.swipeToReplyNode {
strongSelf.swipeToReplyNode = nil
swipeToReplyNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak swipeToReplyNode] _ in
swipeToReplyNode?.removeFromSupernode()
})
swipeToReplyNode.layer.animateScale(from: 1.0, to: 0.2, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
}
*/
strongSelf.awaitingAppliedReaction = nil
/*var targetNode: ASDisplayNode?
var hideTarget = false
if let awaitingAppliedReaction = awaitingAppliedReaction {
for contentNode in strongSelf.contentNodes {
if let (reactionNode, count) = contentNode.reactionTargetNode(value: awaitingAppliedReaction) {
targetNode = reactionNode
hideTarget = count == 1
break
}
}
}
strongSelf.reactionRecognizer?.complete(into: targetNode, hideTarget: hideTarget)*/
f()
}
}
@ -2241,9 +2224,9 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
}
}
override func targetReactionNode(value: String) -> (ASDisplayNode, ASDisplayNode)? {
override func targetReactionView(value: String) -> UIView? {
if !self.dateAndStatusNode.isHidden {
return self.dateAndStatusNode.reactionNode(value: value)
return self.dateAndStatusNode.reactionView(value: value)
}
return nil
}

View File

@ -508,7 +508,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
if let (media, flags) = mediaAndFlags {
if let file = media as? TelegramMediaFile {
if file.mimeType == "application/x-tgtheme-ios", let size = file.size, size < 16 * 1024 {
let (_, initialImageWidth, refineLayout) = contentImageLayout(context, presentationData, presentationData.dateTimeFormat, message, attributes, file, imageDateAndStatus, .full, associatedData.automaticDownloadPeerType, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants, contentMode)
let (_, initialImageWidth, refineLayout) = contentImageLayout(context, presentationData, presentationData.dateTimeFormat, message, associatedData, attributes, file, imageDateAndStatus, .full, associatedData.automaticDownloadPeerType, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants, contentMode)
initialWidth = initialImageWidth + horizontalInsets.left + horizontalInsets.right
refineContentImageLayout = refineLayout
} else if file.isInstantVideo {
@ -540,12 +540,12 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
}
}
let (_, initialImageWidth, refineLayout) = contentImageLayout(context, presentationData, presentationData.dateTimeFormat, message, attributes, file, imageDateAndStatus, automaticDownload, associatedData.automaticDownloadPeerType, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants, contentMode)
let (_, initialImageWidth, refineLayout) = contentImageLayout(context, presentationData, presentationData.dateTimeFormat, message, associatedData, attributes, file, imageDateAndStatus, automaticDownload, associatedData.automaticDownloadPeerType, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants, contentMode)
initialWidth = initialImageWidth + horizontalInsets.left + horizontalInsets.right
refineContentImageLayout = refineLayout
} else if file.isSticker || file.isAnimatedSticker {
let automaticDownload = shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, authorPeerId: message.author?.id, contactsPeerIds: associatedData.contactsPeerIds, media: file)
let (_, initialImageWidth, refineLayout) = contentImageLayout(context, presentationData, presentationData.dateTimeFormat, message, attributes, file, imageDateAndStatus, automaticDownload ? .full : .none, associatedData.automaticDownloadPeerType, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants, contentMode)
let (_, initialImageWidth, refineLayout) = contentImageLayout(context, presentationData, presentationData.dateTimeFormat, message, associatedData, attributes, file, imageDateAndStatus, automaticDownload ? .full : .none, associatedData.automaticDownloadPeerType, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants, contentMode)
initialWidth = initialImageWidth + horizontalInsets.left + horizontalInsets.right
refineContentImageLayout = refineLayout
} else {
@ -570,7 +570,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
} else if let image = media as? TelegramMediaImage {
if !flags.contains(.preferMediaInline) {
let automaticDownload = shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, authorPeerId: message.author?.id, contactsPeerIds: associatedData.contactsPeerIds, media: image)
let (_, initialImageWidth, refineLayout) = contentImageLayout(context, presentationData, presentationData.dateTimeFormat, message, attributes, image, imageDateAndStatus, automaticDownload ? .full : .none, associatedData.automaticDownloadPeerType, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants, contentMode)
let (_, initialImageWidth, refineLayout) = contentImageLayout(context, presentationData, presentationData.dateTimeFormat, message, associatedData, attributes, image, imageDateAndStatus, automaticDownload ? .full : .none, associatedData.automaticDownloadPeerType, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants, contentMode)
initialWidth = initialImageWidth + horizontalInsets.left + horizontalInsets.right
refineContentImageLayout = refineLayout
} else if let dimensions = largestImageRepresentation(image.representations)?.dimensions {
@ -582,11 +582,11 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
}
} else if let image = media as? TelegramMediaWebFile {
let automaticDownload = shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, authorPeerId: message.author?.id, contactsPeerIds: associatedData.contactsPeerIds, media: image)
let (_, initialImageWidth, refineLayout) = contentImageLayout(context, presentationData, presentationData.dateTimeFormat, message, attributes, image, imageDateAndStatus, automaticDownload ? .full : .none, associatedData.automaticDownloadPeerType, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants, contentMode)
let (_, initialImageWidth, refineLayout) = contentImageLayout(context, presentationData, presentationData.dateTimeFormat, message, associatedData, attributes, image, imageDateAndStatus, automaticDownload ? .full : .none, associatedData.automaticDownloadPeerType, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants, contentMode)
initialWidth = initialImageWidth + horizontalInsets.left + horizontalInsets.right
refineContentImageLayout = refineLayout
} else if let wallpaper = media as? WallpaperPreviewMedia {
let (_, initialImageWidth, refineLayout) = contentImageLayout(context, presentationData, presentationData.dateTimeFormat, message, attributes, wallpaper, imageDateAndStatus, .full, associatedData.automaticDownloadPeerType, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants, contentMode)
let (_, initialImageWidth, refineLayout) = contentImageLayout(context, presentationData, presentationData.dateTimeFormat, message, associatedData, attributes, wallpaper, imageDateAndStatus, .full, associatedData.automaticDownloadPeerType, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants, contentMode)
initialWidth = initialImageWidth + horizontalInsets.left + horizontalInsets.right
refineContentImageLayout = refineLayout
if case let .file(_, _, _, _, isTheme, _) = wallpaper.content, isTheme {
@ -613,52 +613,39 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
default:
break
}
var statusSizeAndApply: (CGSize, (Bool) -> Void)?
let textConstrainedSize = CGSize(width: constrainedSize.width - insets.left - insets.right, height: constrainedSize.height - insets.top - insets.bottom)
if let textStatusType = textStatusType {
statusSizeAndApply = statusLayout(context, presentationData, edited, viewCount, dateText, textStatusType, textConstrainedSize, dateReactions, dateReplies, message.tags.contains(.pinned) && !associatedData.isInPinnedListMode && !isReplyThread, message.isSelfExpiring)
}
var updatedAdditionalImageBadge: ChatMessageInteractiveMediaBadge?
if let _ = additionalImageBadgeContent {
updatedAdditionalImageBadge = currentAdditionalImageBadgeNode ?? ChatMessageInteractiveMediaBadge()
}
var upatedTextCutout = textCutout
if statusInText, let (statusSize, _) = statusSizeAndApply {
upatedTextCutout.bottomRight = statusSize
}
let upatedTextCutout = textCutout
let (textLayout, textApply) = textAsyncLayout(TextNodeLayoutArguments(attributedString: textString, backgroundColor: nil, maximumNumberOfLines: 12, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: upatedTextCutout, insets: UIEdgeInsets()))
var textFrame = CGRect(origin: CGPoint(), size: textLayout.size)
var statusFrame: CGRect?
if statusInText, let (statusSize, _) = statusSizeAndApply {
var frame = CGRect(origin: CGPoint(x: textFrame.maxX - statusSize.width, y: textFrame.maxY - statusSize.height), size: statusSize)
/*let trailingLineWidth = textLayout.trailingLineWidth
if textLayout.size.width - trailingLineWidth >= statusSize.width {
frame.origin = CGPoint(x: textFrame.maxX - statusSize.width, y: textFrame.maxY - statusSize.height)
} else if trailingLineWidth + statusSize.width < textConstrainedSize.width {
frame.origin = CGPoint(x: textFrame.minX + trailingLineWidth, y: textFrame.maxY - statusSize.height)
} else {
frame.origin = CGPoint(x: textFrame.maxX - statusSize.width, y: textFrame.maxY)
}*/
if let inlineImageSize = inlineImageSize {
if frame.origin.y < inlineImageSize.height + 4.0 {
frame.origin.y = inlineImageSize.height + 4.0
}
}
frame = frame.offsetBy(dx: insets.left, dy: insets.top)
statusFrame = frame
var statusSuggestedWidthAndContinue: (CGFloat, (CGFloat) -> (CGSize, (Bool) -> Void))?
if statusInText, let textStatusType = textStatusType {
statusSuggestedWidthAndContinue = statusLayout(ChatMessageDateAndStatusNode.Arguments(
context: context,
presentationData: presentationData,
edited: edited,
impressionCount: viewCount,
dateText: dateText,
type: textStatusType,
layoutInput: .trailingContent(contentWidth: textLayout.trailingLineWidth, preferAdditionalInset: false),
constrainedSize: textConstrainedSize,
availableReactions: associatedData.availableReactions,
reactions: dateReactions,
replyCount: dateReplies,
isPinned: message.tags.contains(.pinned) && !associatedData.isInPinnedListMode && !isReplyThread,
hasAutoremove: message.isSelfExpiring
))
}
let _ = statusSuggestedWidthAndContinue
var textFrame = CGRect(origin: CGPoint(), size: textLayout.size)
textFrame = textFrame.offsetBy(dx: insets.left, dy: insets.top)
@ -666,12 +653,6 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
var boundingSize = textFrame.size
var lineHeight = textLayout.rawTextSize.height
if let statusFrame = statusFrame {
boundingSize = textFrame.union(statusFrame).size
if let _ = actionTitle {
lineHeight = boundingSize.height
}
}
if let inlineImageSize = inlineImageSize {
if boundingSize.height < inlineImageSize.height {
boundingSize.height = inlineImageSize.height
@ -681,6 +662,10 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
}
}
if let statusSuggestedWidthAndContinue = statusSuggestedWidthAndContinue {
boundingSize.width = max(boundingSize.width, statusSuggestedWidthAndContinue.0)
}
var finalizeContentImageLayout: ((CGFloat) -> (CGSize, (ContainedViewLayoutTransition, Bool) -> ChatMessageInteractiveMediaNode))?
if let refineContentImageLayout = refineContentImageLayout {
let (refinedWidth, finalizeImageLayout) = refineContentImageLayout(textConstrainedSize, automaticPlayback, true, ImageCorners(radius: 4.0))
@ -767,10 +752,6 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
adjustedBoundingSize.height += imageHeightAddition + 5.0
adjustedLineHeight += imageHeightAddition + 4.0
if !statusInText, let (statusSize, _) = statusSizeAndApply {
statusFrame = CGRect(origin: CGPoint(), size: statusSize)
}
}
var contentFileSizeAndApply: (CGSize, (Bool) -> ChatMessageInteractiveFileNode)?
@ -807,10 +788,18 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
adjustedBoundingSize.height += 7.0 + size.height
}
var adjustedStatusFrame: CGRect?
var statusSizeAndApply: ((CGSize), (Bool) -> Void)?
if let statusSuggestedWidthAndContinue = statusSuggestedWidthAndContinue {
statusSizeAndApply = statusSuggestedWidthAndContinue.1(boundingWidth - insets.left - insets.right)
}
if let statusSizeAndApply = statusSizeAndApply {
adjustedBoundingSize.height += statusSizeAndApply.0.height
}
/*var adjustedStatusFrame: CGRect?
if statusInText, let statusFrame = statusFrame {
adjustedStatusFrame = CGRect(origin: CGPoint(x: boundingWidth - statusFrame.size.width - insets.right, y: statusFrame.origin.y), size: statusFrame.size)
}
}*/
adjustedBoundingSize.width = max(boundingWidth, adjustedBoundingSize.width)
@ -830,6 +819,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
hasAnimation = true
transition = .animated(duration: duration, curve: .easeInOut)
}
let _ = hasAnimation
strongSelf.lineNode.image = lineImage
strongSelf.lineNode.frame = CGRect(origin: CGPoint(x: 13.0, y: insets.top), size: CGSize(width: 2.0, height: adjustedLineHeight - insets.top - insets.bottom - 2.0))
@ -884,10 +874,6 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
}
contentImageNode.frame = contentImageFrame
if !statusInText, let statusFrame = statusFrame {
adjustedStatusFrame = CGRect(origin: CGPoint(x: contentImageFrame.width - statusFrame.size.width - 2.0, y: contentImageFrame.height - statusFrame.size.height - 2.0), size: statusFrame.size)
}
} else if let contentImageNode = strongSelf.contentImageNode {
contentImageNode.visibility = false
contentImageNode.removeFromSupernode()
@ -958,6 +944,15 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
}
strongSelf.textNode.frame = textFrame.offsetBy(dx: 0.0, dy: textVerticalOffset)
if let statusSizeAndApply = statusSizeAndApply {
if strongSelf.statusNode.supernode == nil {
strongSelf.addSubnode(strongSelf.statusNode)
}
strongSelf.statusNode.frame = CGRect(origin: CGPoint(x: strongSelf.textNode.frame.minX, y: strongSelf.textNode.frame.maxY), size: statusSizeAndApply.0)
statusSizeAndApply.1(animation.isAnimated)
} else if strongSelf.statusNode.supernode != nil {
strongSelf.statusNode.removeFromSupernode()
}
if let (size, apply) = actionButtonSizeAndApply {
let buttonNode = apply()
@ -976,22 +971,6 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
buttonNode.removeFromSupernode()
strongSelf.buttonNode = nil
}
if let (_, statusApply) = statusSizeAndApply, let adjustedStatusFrame = adjustedStatusFrame {
strongSelf.statusNode.frame = adjustedStatusFrame.offsetBy(dx: 0.0, dy: textVerticalOffset)
if statusInText {
if strongSelf.statusNode.supernode != strongSelf {
strongSelf.addSubnode(strongSelf.statusNode)
}
} else if let contentImageNode = strongSelf.contentImageNode {
if strongSelf.statusNode.supernode != contentImageNode {
contentImageNode.addSubnode(strongSelf.statusNode)
}
}
statusApply(hasAnimation)
} else if strongSelf.statusNode.supernode != nil {
strongSelf.statusNode.removeFromSupernode()
}
}
})
})

View File

@ -210,7 +210,7 @@ class ChatMessageBubbleContentNode: ASDisplayNode {
func applyAbsoluteOffsetSpring(value: CGFloat, duration: Double, damping: CGFloat) {
}
func reactionTargetNode(value: String) -> (ASDisplayNode, ASDisplayNode)? {
func reactionTargetView(value: String) -> UIView? {
return nil
}

View File

@ -460,7 +460,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
self.shadowNode = ChatMessageShadowNode()
self.clippingNode = ChatMessageBubbleClippingNode()
self.clippingNode.clipsToBounds = true
self.clippingNode.clipsToBounds = false
self.messageAccessibilityArea = AccessibilityAreaNode()
@ -689,7 +689,18 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
let widthDifference = self.backgroundNode.frame.width - textInput.backgroundView.frame.width
let heightDifference = self.backgroundNode.frame.height - textInput.backgroundView.frame.height
transition.animateFrame(layer: self.clippingNode.layer, from: CGRect(origin: CGPoint(x: self.clippingNode.frame.minX, y: textInput.backgroundView.frame.minY), size: textInput.backgroundView.frame.size))
if let type = self.backgroundNode.type {
if case .none = type {
} else {
self.clippingNode.clipsToBounds = true
}
}
transition.animateFrame(layer: self.clippingNode.layer, from: CGRect(origin: CGPoint(x: self.clippingNode.frame.minX, y: textInput.backgroundView.frame.minY), size: textInput.backgroundView.frame.size), completion: { [weak self] _ in
guard let strongSelf = self else {
return
}
strongSelf.clippingNode.clipsToBounds = false
})
transition.vertical.animateOffsetAdditive(layer: self.clippingNode.layer, offset: textInput.backgroundView.frame.minY - self.clippingNode.frame.minY)
@ -909,7 +920,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
forwardInfoLayout: (ChatPresentationData, PresentationStrings, ChatMessageForwardInfoType, Peer?, String?, String?, CGSize) -> (CGSize, (CGFloat) -> ChatMessageForwardInfoNode),
replyInfoLayout: (ChatPresentationData, PresentationStrings, AccountContext, ChatMessageReplyInfoType, Message, CGSize) -> (CGSize, () -> ChatMessageReplyInfoNode),
actionButtonsLayout: (AccountContext, ChatPresentationThemeData, PresentationChatBubbleCorners, PresentationStrings, ReplyMarkupMessageAttribute, Message, CGFloat) -> (minWidth: CGFloat, layout: (CGFloat) -> (CGSize, (Bool) -> ChatMessageActionButtonsNode)),
mosaicStatusLayout: (AccountContext, ChatPresentationData, Bool, Int?, String, ChatMessageDateAndStatusType, CGSize, [MessageReaction], Int, Bool, Bool) -> (CGSize, (Bool) -> ChatMessageDateAndStatusNode),
mosaicStatusLayout: (ChatMessageDateAndStatusNode.Arguments) -> (CGFloat, (CGFloat) -> (CGSize, (Bool) -> ChatMessageDateAndStatusNode)),
layoutConstants: ChatMessageItemLayoutConstants,
currentItem: ChatMessageItem?,
currentForwardInfo: (Peer?, String?)?,
@ -1513,7 +1524,23 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
isReplyThread = true
}
mosaicStatusSizeAndApply = mosaicStatusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: 200.0, height: CGFloat.greatestFiniteMagnitude), dateReactions, dateReplies, message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, message.isSelfExpiring)
let statusSuggestedWidthAndContinue = mosaicStatusLayout(ChatMessageDateAndStatusNode.Arguments(
context: item.context,
presentationData: item.presentationData,
edited: edited,
impressionCount: viewCount,
dateText: dateText,
type: statusType,
layoutInput: .standalone,
constrainedSize: CGSize(width: 200.0, height: CGFloat.greatestFiniteMagnitude),
availableReactions: item.associatedData.availableReactions,
reactions: dateReactions,
replyCount: dateReplies,
isPinned: message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
hasAutoremove: message.isSelfExpiring
))
mosaicStatusSizeAndApply = statusSuggestedWidthAndContinue.1(statusSuggestedWidthAndContinue.0)
}
}
@ -2128,11 +2155,6 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
strongSelf.backgroundNode.setType(type: backgroundType, highlighted: strongSelf.highlightedState, graphics: graphics, maskMode: strongSelf.backgroundMaskMode, hasWallpaper: hasWallpaper, transition: transition, backgroundNode: presentationContext.backgroundNode)
strongSelf.backgroundWallpaperNode.setType(type: backgroundType, theme: item.presentationData.theme, essentialGraphics: graphics, maskMode: strongSelf.backgroundMaskMode, backgroundNode: presentationContext.backgroundNode)
strongSelf.shadowNode.setType(type: backgroundType, hasWallpaper: hasWallpaper, graphics: graphics)
if case .none = backgroundType {
strongSelf.clippingNode.clipsToBounds = false
} else {
strongSelf.clippingNode.clipsToBounds = true
}
strongSelf.backgroundType = backgroundType
@ -2606,6 +2628,12 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
if case .System = animation, !strongSelf.mainContextSourceNode.isExtractedToContextPreview {
if !strongSelf.backgroundNode.frame.equalTo(backgroundFrame) {
strongSelf.backgroundFrameTransition = (strongSelf.backgroundNode.frame, backgroundFrame)
if let type = strongSelf.backgroundNode.type {
if case .none = type {
} else {
strongSelf.clippingNode.clipsToBounds = true
}
}
}
if let shareButtonNode = strongSelf.shareButtonNode {
let currentBackgroundFrame = strongSelf.backgroundNode.frame
@ -2694,41 +2722,8 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
strongSelf.updateSearchTextHighlightState()
if let (_, f) = strongSelf.awaitingAppliedReaction {
/*var bounds = strongSelf.bounds
let offset = bounds.origin.x
bounds.origin.x = 0.0
strongSelf.bounds = bounds
var shadowBounds = strongSelf.shadowNode.bounds
let shadowOffset = shadowBounds.origin.x
shadowBounds.origin.x = 0.0
strongSelf.shadowNode.bounds = shadowBounds
if !offset.isZero {
strongSelf.layer.animateBoundsOriginXAdditive(from: offset, to: 0.0, duration: 0.2, timingFunction: kCAMediaTimingFunctionSpring)
}
if !shadowOffset.isZero {
strongSelf.shadowNode.layer.animateBoundsOriginXAdditive(from: shadowOffset, to: 0.0, duration: 0.2, timingFunction: kCAMediaTimingFunctionSpring)
}
if let swipeToReplyNode = strongSelf.swipeToReplyNode {
strongSelf.swipeToReplyNode = nil
swipeToReplyNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak swipeToReplyNode] _ in
swipeToReplyNode?.removeFromSupernode()
})
swipeToReplyNode.layer.animateScale(from: 1.0, to: 0.2, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
}
*/
strongSelf.awaitingAppliedReaction = nil
/*var targetNode: ASDisplayNode?
var hideTarget = false
if let awaitingAppliedReaction = awaitingAppliedReaction {
for contentNode in strongSelf.contentNodes {
if let (reactionNode, count) = contentNode.reactionTargetNode(value: awaitingAppliedReaction) {
targetNode = reactionNode
hideTarget = count == 1
break
}
}
}
strongSelf.reactionRecognizer?.complete(into: targetNode, hideTarget: hideTarget)*/
f()
}
@ -2821,6 +2816,8 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
if CGFloat(1.0).isLessThanOrEqualTo(progress) {
self.backgroundFrameTransition = nil
self.clippingNode.clipsToBounds = false
}
}
}
@ -2828,7 +2825,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
@objc func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
switch recognizer.state {
case .ended:
if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation {
if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation, let item = self.item {
if let action = self.gestureRecognized(gesture: gesture, location: location, recognizer: nil) {
if case .doubleTap = gesture {
self.mainContainerNode.cancelGesture()
@ -2839,14 +2836,14 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
case let .optionalAction(f):
f()
case let .openContextMenu(tapMessage, selectAll, subFrame):
if canAddMessageReactions(message: tapMessage) {
self.item?.controllerInteraction.updateMessageReaction(tapMessage)
if canAddMessageReactions(message: tapMessage), let defaultReaction = item.associatedData.defaultReaction {
item.controllerInteraction.updateMessageReaction(tapMessage, defaultReaction)
} else {
self.item?.controllerInteraction.openMessageContextMenu(tapMessage, selectAll, self, subFrame, nil)
item.controllerInteraction.openMessageContextMenu(tapMessage, selectAll, self, subFrame, nil)
}
}
} else if case .tap = gesture {
self.item?.controllerInteraction.clickThroughMessage()
item.controllerInteraction.clickThroughMessage()
}
}
default:
@ -3667,10 +3664,10 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
return self.mainContextSourceNode.isExtractedToContextPreview || hasWallpaper || isPreview
}
override func targetReactionNode(value: String) -> (ASDisplayNode, ASDisplayNode)? {
override func targetReactionView(value: String) -> UIView? {
for contentNode in self.contentNodes {
if let (emptyNode, filledNode) = contentNode.reactionTargetNode(value: value) {
return (emptyNode, filledNode)
if let result = contentNode.reactionTargetView(value: value) {
return result
}
}
return nil

View File

@ -41,6 +41,13 @@ class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode {
self.addSubnode(self.buttonNode)
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
self.dateAndStatusNode.reactionSelected = { [weak self] value in
guard let strongSelf = self, let item = strongSelf.item else {
return
}
item.controllerInteraction.updateMessageReaction(item.message, value)
}
}
override func accessibilityActivate() -> Bool {
@ -144,7 +151,9 @@ class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode {
return (contentProperties, nil, CGFloat.greatestFiniteMagnitude, { constrainedSize, position in
let avatarSize = CGSize(width: 40.0, height: 40.0)
let maxTextWidth = max(1.0, constrainedSize.width - avatarSize.width - layoutConstants.text.bubbleInsets.left - layoutConstants.text.bubbleInsets.right)
let sideInsets = layoutConstants.text.bubbleInsets.right * 2.0
let maxTextWidth = max(1.0, constrainedSize.width - avatarSize.width - 7.0 - sideInsets)
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: maxTextWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: textString, backgroundColor: nil, maximumNumberOfLines: 5, truncationType: .end, constrainedSize: CGSize(width: maxTextWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
@ -187,18 +196,28 @@ class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode {
statusType = nil
}
var statusSize = CGSize()
var statusApply: ((Bool) -> Void)?
var statusSuggestedWidthAndContinue: (CGFloat, (CGFloat) -> (CGSize, (Bool) -> Void))?
if let statusType = statusType {
var isReplyThread = false
if case .replyThread = item.chatLocation {
isReplyThread = true
}
let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: constrainedSize.width, height: CGFloat.greatestFiniteMagnitude), dateReactions, dateReplies, item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, item.message.isSelfExpiring)
statusSize = size
statusApply = apply
statusSuggestedWidthAndContinue = statusLayout(ChatMessageDateAndStatusNode.Arguments(
context: item.context,
presentationData: item.presentationData,
edited: edited,
impressionCount: viewCount,
dateText: dateText,
type: statusType,
layoutInput: .trailingContent(contentWidth: 1000.0, preferAdditionalInset: true),
constrainedSize: CGSize(width: constrainedSize.width - sideInsets, height: .greatestFiniteMagnitude),
availableReactions: item.associatedData.availableReactions,
reactions: dateReactions,
replyCount: dateReplies,
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && isReplyThread,
hasAutoremove: item.message.isSelfExpiring
))
}
let buttonImage: UIImage
@ -226,27 +245,30 @@ class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode {
let (buttonWidth, continueLayout) = makeButtonLayout(constrainedSize.width, buttonImage, buttonHighlightedImage, nil, nil, item.presentationData.strings.Conversation_ViewContactDetails, titleColor, titleHighlightedColor)
var maxContentWidth: CGFloat = 0.0
maxContentWidth = max(maxContentWidth, statusSize.width)
maxContentWidth = max(maxContentWidth, titleLayout.size.width)
maxContentWidth = max(maxContentWidth, textLayout.size.width)
var maxContentWidth: CGFloat = avatarSize.width + 7.0
if let statusSuggestedWidthAndContinue = statusSuggestedWidthAndContinue {
maxContentWidth = max(maxContentWidth, statusSuggestedWidthAndContinue.0)
}
maxContentWidth = max(maxContentWidth, avatarSize.width + 7.0 + titleLayout.size.width)
maxContentWidth = max(maxContentWidth, avatarSize.width + 7.0 + textLayout.size.width)
maxContentWidth = max(maxContentWidth, buttonWidth)
let contentWidth = avatarSize.width + maxContentWidth + layoutConstants.text.bubbleInsets.right + 8.0
let contentWidth = maxContentWidth + layoutConstants.text.bubbleInsets.right * 2.0
return (contentWidth, { boundingWidth in
let layoutSize: CGSize
let statusFrame: CGRect
let baseAvatarFrame = CGRect(origin: CGPoint(), size: avatarSize)
let baseAvatarFrame = CGRect(origin: CGPoint(x: layoutConstants.text.bubbleInsets.right, y: layoutConstants.text.bubbleInsets.top), size: avatarSize)
let (buttonSize, buttonApply) = continueLayout(boundingWidth - layoutConstants.text.bubbleInsets.right * 2.0)
let buttonSpacing: CGFloat = 4.0
layoutSize = CGSize(width: contentWidth, height: 49.0 + textLayout.size.height + buttonSize.height + buttonSpacing)
statusFrame = CGRect(origin: CGPoint(x: boundingWidth - statusSize.width - layoutConstants.text.bubbleInsets.right, y: layoutSize.height - statusSize.height - 9.0 - buttonSpacing - buttonSize.height), size: statusSize)
let statusSizeAndApply = statusSuggestedWidthAndContinue?.1(boundingWidth - sideInsets)
var layoutSize = CGSize(width: contentWidth, height: 49.0 + textLayout.size.height + buttonSize.height + buttonSpacing)
if let statusSizeAndApply = statusSizeAndApply {
layoutSize.height += statusSizeAndApply.0.height - 4.0
}
let buttonFrame = CGRect(origin: CGPoint(x: layoutConstants.text.bubbleInsets.right, y: layoutSize.height - 9.0 - buttonSize.height), size: buttonSize)
let avatarFrame = baseAvatarFrame.offsetBy(dx: 5.0, dy: 5.0)
let avatarFrame = baseAvatarFrame.offsetBy(dx: 0.0, dy: 5.0)
var customLetters: [String] = []
if let selectedContact = selectedContact, selectedContact.peerId == nil {
@ -279,16 +301,14 @@ class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode {
strongSelf.textNode.frame = CGRect(origin: CGPoint(x: avatarFrame.maxX + 7.0, y: avatarFrame.minY + 20.0), size: textLayout.size)
strongSelf.buttonNode.frame = buttonFrame
if let statusApply = statusApply {
if let statusSizeAndApply = statusSizeAndApply {
strongSelf.dateAndStatusNode.frame = CGRect(origin: CGPoint(x: layoutConstants.text.bubbleInsets.left, y: strongSelf.textNode.frame.maxY + 2.0), size: statusSizeAndApply.0)
if strongSelf.dateAndStatusNode.supernode == nil {
strongSelf.addSubnode(strongSelf.dateAndStatusNode)
statusSizeAndApply.1(false)
} else {
statusSizeAndApply.1(animation.isAnimated)
}
var hasAnimation = true
if case .None = animation {
hasAnimation = false
}
statusApply(hasAnimation)
strongSelf.dateAndStatusNode.frame = statusFrame
} else if strongSelf.dateAndStatusNode.supernode != nil {
strongSelf.dateAndStatusNode.removeFromSupernode()
}
@ -348,6 +368,9 @@ class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode {
if self.buttonNode.frame.contains(point) {
return .openMessage
}
if self.dateAndStatusNode.supernode != nil, let _ = self.dateAndStatusNode.hitTest(self.view.convert(point, to: self.dateAndStatusNode.view), with: nil) {
return .ignore
}
return .none
}
@ -365,10 +388,17 @@ class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode {
}
}
override func reactionTargetNode(value: String) -> (ASDisplayNode, ASDisplayNode)? {
override func reactionTargetView(value: String) -> UIView? {
if !self.dateAndStatusNode.isHidden {
return self.dateAndStatusNode.reactionNode(value: value)
return self.dateAndStatusNode.reactionView(value: value)
}
return nil
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if self.dateAndStatusNode.supernode != nil, let result = self.dateAndStatusNode.hitTest(self.view.convert(point, to: self.dateAndStatusNode.view), with: event) {
return result
}
return super.hitTest(point, with: event)
}
}

View File

@ -56,6 +56,13 @@ class ChatMessageFileBubbleContentNode: ChatMessageBubbleContentNode {
let _ = item.controllerInteraction.displayImportedMessageTooltip(sourceNode)
}
}
self.interactiveFileNode.dateAndStatusNode.reactionSelected = { [weak self] value in
guard let strongSelf = self, let item = strongSelf.item else {
return
}
item.controllerInteraction.updateMessageReaction(item.message, value)
}
}
override func accessibilityActivate() -> Bool {
@ -161,9 +168,16 @@ class ChatMessageFileBubbleContentNode: ChatMessageBubbleContentNode {
self.interactiveFileNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
}
override func reactionTargetNode(value: String) -> (ASDisplayNode, ASDisplayNode)? {
override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction {
if self.interactiveFileNode.dateAndStatusNode.supernode != nil, let _ = self.interactiveFileNode.dateAndStatusNode.hitTest(self.view.convert(point, to: self.interactiveFileNode.dateAndStatusNode.view), with: nil) {
return .ignore
}
return super.tapActionAtPoint(point, gesture: gesture, isEstimating: isEstimating)
}
override func reactionTargetView(value: String) -> UIView? {
if !self.interactiveFileNode.dateAndStatusNode.isHidden {
return self.interactiveFileNode.dateAndStatusNode.reactionNode(value: value)
return self.interactiveFileNode.dateAndStatusNode.reactionView(value: value)
}
return nil
}

View File

@ -140,9 +140,9 @@ final class ChatMessageGameBubbleContentNode: ChatMessageBubbleContentNode {
return self.contentNode.transitionNode(media: media)
}
override func reactionTargetNode(value: String) -> (ASDisplayNode, ASDisplayNode)? {
override func reactionTargetView(value: String) -> UIView? {
if !self.contentNode.statusNode.isHidden {
return self.contentNode.statusNode.reactionNode(value: value)
return self.contentNode.statusNode.reactionView(value: value)
}
return nil
}

View File

@ -1248,9 +1248,9 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
}
}
override func targetReactionNode(value: String) -> (ASDisplayNode, ASDisplayNode)? {
override func targetReactionView(value: String) -> UIView? {
if !self.interactiveVideoNode.dateAndStatusNode.isHidden {
return self.interactiveVideoNode.dateAndStatusNode.reactionNode(value: value)
return self.interactiveVideoNode.dateAndStatusNode.reactionView(value: value)
}
return nil
}

View File

@ -280,9 +280,6 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
updatedPlaybackStatusSignal = messageFileMediaPlaybackStatus(context: context, file: file, message: message, isRecentActions: isRecentActions, isGlobalSearch: false)
}
var statusSize: CGSize?
var statusApply: ((Bool) -> Void)?
var consumableContentIcon: UIImage?
for attribute in message.attributes {
if let attribute = attribute as? ConsumableContentMessageAttribute {
@ -298,36 +295,6 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
}
}
if let statusType = dateAndStatusType {
var edited = false
if attributes.updatingMedia != nil {
edited = true
}
var viewCount: Int?
var dateReplies = 0
let dateReactions: [MessageReaction] = mergedMessageReactions(attributes: message.attributes)?.reactions ?? []
for attribute in message.attributes {
if let attribute = attribute as? EditedMessageAttribute {
edited = !attribute.isHidden
} else if let attribute = attribute as? ViewCountMessageAttribute {
viewCount = attribute.count
} else if let attribute = attribute as? ReplyThreadMessageAttribute, case .peer = chatLocation {
if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .group = channel.info {
dateReplies = Int(attribute.count)
}
}
}
if forcedIsEdited {
edited = true
}
let dateText = stringForMessageTimestampStatus(accountPeerId: context.account.peerId, message: message, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, strings: presentationData.strings)
let (size, apply) = statusLayout(context, presentationData, edited, viewCount, dateText, statusType, constrainedSize, dateReactions, dateReplies, isPinned && !associatedData.isInPinnedListMode, message.isSelfExpiring)
statusSize = size
statusApply = apply
}
var candidateTitleString: NSAttributedString?
var candidateDescriptionString: NSAttributedString?
@ -430,14 +397,80 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
let maxVoiceLength: CGFloat = 30.0
let minVoiceLength: CGFloat = 2.0
let progressDiameter: CGFloat = 44.0
var iconFrame: CGRect?
let progressFrame: CGRect
let controlAreaWidth: CGFloat
if hasThumbnail {
let currentIconFrame = CGRect(origin: CGPoint(x: -1.0, y: -7.0), size: CGSize(width: 74.0, height: 74.0))
iconFrame = currentIconFrame
progressFrame = CGRect(
origin: CGPoint(
x: currentIconFrame.minX + floor((currentIconFrame.size.width - progressDiameter) / 2.0),
y: currentIconFrame.minY + floor((currentIconFrame.size.height - progressDiameter) / 2.0)
),
size: CGSize(width: progressDiameter, height: progressDiameter)
)
controlAreaWidth = 86.0
} else {
progressFrame = CGRect(
origin: CGPoint(x: 3.0, y: isVoice ? -3.0 : 0.0),
size: CGSize(width: progressDiameter, height: progressDiameter)
)
controlAreaWidth = progressFrame.maxX + 8.0
}
var statusSuggestedWidthAndContinue: (CGFloat, (CGFloat) -> (CGSize, (Bool) -> Void))?
if let statusType = dateAndStatusType {
var edited = false
if attributes.updatingMedia != nil {
edited = true
}
var viewCount: Int?
var dateReplies = 0
let dateReactions: [MessageReaction] = mergedMessageReactions(attributes: message.attributes)?.reactions ?? []
for attribute in message.attributes {
if let attribute = attribute as? EditedMessageAttribute {
edited = !attribute.isHidden
} else if let attribute = attribute as? ViewCountMessageAttribute {
viewCount = attribute.count
} else if let attribute = attribute as? ReplyThreadMessageAttribute, case .peer = chatLocation {
if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .group = channel.info {
dateReplies = Int(attribute.count)
}
}
}
if forcedIsEdited {
edited = true
}
let dateText = stringForMessageTimestampStatus(accountPeerId: context.account.peerId, message: message, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, strings: presentationData.strings)
statusSuggestedWidthAndContinue = statusLayout(ChatMessageDateAndStatusNode.Arguments(
context: context,
presentationData: presentationData,
edited: edited,
impressionCount: viewCount,
dateText: dateText,
type: statusType,
layoutInput: .trailingContent(contentWidth: iconFrame == nil ? 1000.0 : controlAreaWidth, preferAdditionalInset: true),
constrainedSize: constrainedSize,
availableReactions: associatedData.availableReactions,
reactions: dateReactions,
replyCount: dateReplies,
isPinned: isPinned && !associatedData.isInPinnedListMode,
hasAutoremove: message.isSelfExpiring
))
}
var minLayoutWidth: CGFloat
if hasThumbnail {
minLayoutWidth = max(titleLayout.size.width, descriptionMaxWidth) + 86.0
} else if isVoice {
var descriptionAndStatusWidth = descriptionLayout.size.width
if let statusSize = statusSize {
descriptionAndStatusWidth += 6 + statusSize.width
}
let descriptionAndStatusWidth = descriptionLayout.size.width
let calcDuration = max(minVoiceLength, min(maxVoiceLength, CGFloat(audioDuration)))
minLayoutWidth = minVoiceWidth + (maxVoiceWidth - minVoiceWidth) * (calcDuration - minVoiceLength) / (maxVoiceLength - minVoiceLength)
minLayoutWidth = max(descriptionAndStatusWidth + 56, minLayoutWidth)
@ -445,8 +478,8 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
minLayoutWidth = max(titleLayout.size.width, descriptionMaxWidth) + 44.0 + 8.0
}
if let statusSize = statusSize {
minLayoutWidth = max(minLayoutWidth, statusSize.width)
if let statusSuggestedWidthAndContinue = statusSuggestedWidthAndContinue {
minLayoutWidth = max(minLayoutWidth, statusSuggestedWidthAndContinue.0)
}
let fileIconImage: UIImage?
@ -459,32 +492,6 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
}
return (minLayoutWidth, { boundingWidth in
let progressDiameter: CGFloat = 44.0
var iconFrame: CGRect?
let progressFrame: CGRect
let streamingCacheStatusFrame: CGRect
let controlAreaWidth: CGFloat
if hasThumbnail {
let currentIconFrame = CGRect(origin: CGPoint(x: -1.0, y: -7.0), size: CGSize(width: 74.0, height: 74.0))
iconFrame = currentIconFrame
progressFrame = CGRect(
origin: CGPoint(
x: currentIconFrame.minX + floor((currentIconFrame.size.width - progressDiameter) / 2.0),
y: currentIconFrame.minY + floor((currentIconFrame.size.height - progressDiameter) / 2.0)
),
size: CGSize(width: progressDiameter, height: progressDiameter)
)
controlAreaWidth = 86.0
} else {
progressFrame = CGRect(
origin: CGPoint(x: 3.0, y: isVoice ? -3.0 : 0.0),
size: CGSize(width: progressDiameter, height: progressDiameter)
)
controlAreaWidth = progressFrame.maxX + 8.0
}
let titleAndDescriptionHeight = titleLayout.size.height - 1.0 + descriptionLayout.size.height
let normHeight: CGFloat
@ -513,29 +520,21 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
fittedLayoutSize = CGSize(width: unionSize.width, height: unionSize.height)
}
var statusFrame: CGRect?
if let statusSize = statusSize {
fittedLayoutSize.width = max(fittedLayoutSize.width, statusSize.width)
statusFrame = CGRect(origin: CGPoint(x: boundingWidth - statusSize.width, y: fittedLayoutSize.height - statusSize.height + 10.0), size: statusSize)
var statusSizeAndApply: (CGSize, (Bool) -> Void)?
if let statusSuggestedWidthAndContinue = statusSuggestedWidthAndContinue {
statusSizeAndApply = statusSuggestedWidthAndContinue.1(boundingWidth)
}
if let statusFrameValue = statusFrame, descriptionFrame.intersects(statusFrameValue) {
let intersection = descriptionFrame.intersection(statusFrameValue)
let addedWidth = intersection.width + 20
fittedLayoutSize.width += addedWidth
}
if let statusFrameValue = statusFrame, let iconFrame = iconFrame {
if iconFrame.intersects(statusFrameValue) {
fittedLayoutSize.height += 15.0
statusFrame = statusFrameValue.offsetBy(dx: 0.0, dy: 15.0)
}
} else if let statusFrameValue = statusFrame {
if progressFrame.intersects(statusFrameValue) {
fittedLayoutSize.height += 10.0
statusFrame = statusFrameValue.offsetBy(dx: 0.0, dy: 10.0)
var statusOffset: CGFloat = 0.0
if let statusSizeAndApply = statusSizeAndApply {
fittedLayoutSize.width = max(fittedLayoutSize.width, statusSizeAndApply.0.width)
fittedLayoutSize.height += statusSizeAndApply.0.height
if !statusSizeAndApply.0.height.isZero && iconFrame == nil {
statusOffset = -10.0
fittedLayoutSize.height += statusOffset
}
}
let streamingCacheStatusFrame: CGRect
if (isAudio && !isVoice) || file.previewRepresentations.isEmpty {
streamingCacheStatusFrame = CGRect(origin: CGPoint(x: progressFrame.maxX - streamingProgressDiameter + 2.0, y: progressFrame.maxY - streamingProgressDiameter + 2.0), size: CGSize(width: streamingProgressDiameter, height: streamingProgressDiameter))
} else {
@ -569,13 +568,18 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
strongSelf.consumableContentNode.removeFromSupernode()
}
if let statusApply = statusApply, let statusFrame = statusFrame {
let statusReferenceFrame: CGRect
if let iconFrame = iconFrame {
statusReferenceFrame = iconFrame
} else {
statusReferenceFrame = progressFrame.offsetBy(dx: 0.0, dy: 8.0)
}
if let statusSizeAndApply = statusSizeAndApply {
if strongSelf.dateAndStatusNode.supernode == nil {
strongSelf.addSubnode(strongSelf.dateAndStatusNode)
}
strongSelf.dateAndStatusNode.frame = statusFrame
statusApply(false)
strongSelf.dateAndStatusNode.frame = CGRect(origin: CGPoint(x: statusReferenceFrame.minX, y: statusReferenceFrame.maxY + statusOffset), size: statusSizeAndApply.0)
statusSizeAndApply.1(false)
} else if strongSelf.dateAndStatusNode.supernode != nil {
strongSelf.dateAndStatusNode.removeFromSupernode()
}
@ -1123,6 +1127,13 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
self.playerUpdateTimer?.invalidate()
self.playerUpdateTimer = nil
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if self.dateAndStatusNode.supernode != nil, let result = self.dateAndStatusNode.hitTest(self.view.convert(point, to: self.dateAndStatusNode.view), with: event) {
return result
}
return super.hitTest(point, with: event)
}
}

View File

@ -289,7 +289,23 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
isReplyThread = true
}
let (dateAndStatusSize, dateAndStatusApply) = makeDateAndStatusLayout(item.context, item.presentationData, edited && !sentViaBot, viewCount, dateText, statusType, CGSize(width: max(1.0, maxDateAndStatusWidth), height: CGFloat.greatestFiniteMagnitude), dateReactions, dateReplies, item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, item.message.isSelfExpiring)
let statusSuggestedWidthAndContinue = makeDateAndStatusLayout(ChatMessageDateAndStatusNode.Arguments(
context: item.context,
presentationData: item.presentationData,
edited: edited && !sentViaBot,
impressionCount: viewCount,
dateText: dateText,
type: statusType,
layoutInput: .standalone,
constrainedSize: CGSize(width: max(1.0, maxDateAndStatusWidth), height: CGFloat.greatestFiniteMagnitude),
availableReactions: item.associatedData.availableReactions,
reactions: dateReactions,
replyCount: dateReplies,
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
hasAutoremove: item.message.isSelfExpiring
))
let (dateAndStatusSize, dateAndStatusApply) = statusSuggestedWidthAndContinue.1(statusSuggestedWidthAndContinue.0)
var displayVideoFrame = videoFrame
displayVideoFrame.size.width *= imageScale

View File

@ -345,7 +345,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
}
}
func asyncLayout() -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ dateTimeFormat: PresentationDateTimeFormat, _ message: Message, _ attributes: ChatMessageEntryAttributes, _ media: Media, _ dateAndStatus: ChatMessageDateAndStatus?, _ automaticDownload: InteractiveMediaNodeAutodownloadMode, _ peerType: MediaAutoDownloadPeerType, _ sizeCalculation: InteractiveMediaNodeSizeCalculation, _ layoutConstants: ChatMessageItemLayoutConstants, _ contentMode: InteractiveMediaNodeContentMode) -> (CGSize, CGFloat, (CGSize, Bool, Bool, ImageCorners) -> (CGFloat, (CGFloat) -> (CGSize, (ContainedViewLayoutTransition, Bool) -> Void))) {
func asyncLayout() -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ dateTimeFormat: PresentationDateTimeFormat, _ message: Message, _ associatedData: ChatMessageItemAssociatedData, _ attributes: ChatMessageEntryAttributes, _ media: Media, _ dateAndStatus: ChatMessageDateAndStatus?, _ automaticDownload: InteractiveMediaNodeAutodownloadMode, _ peerType: MediaAutoDownloadPeerType, _ sizeCalculation: InteractiveMediaNodeSizeCalculation, _ layoutConstants: ChatMessageItemLayoutConstants, _ contentMode: InteractiveMediaNodeContentMode) -> (CGSize, CGFloat, (CGSize, Bool, Bool, ImageCorners) -> (CGFloat, (CGFloat) -> (CGSize, (ContainedViewLayoutTransition, Bool) -> Void))) {
let currentMessage = self.message
let currentMedia = self.media
let imageLayout = self.imageNode.asyncLayout()
@ -359,7 +359,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
let currentAutomaticDownload = self.automaticDownload
let currentAutomaticPlayback = self.automaticPlayback
return { [weak self] context, presentationData, dateTimeFormat, message, attributes, media, dateAndStatus, automaticDownload, peerType, sizeCalculation, layoutConstants, contentMode in
return { [weak self] context, presentationData, dateTimeFormat, message, associatedData, attributes, media, dateAndStatus, automaticDownload, peerType, sizeCalculation, layoutConstants, contentMode in
var nativeSize: CGSize
let isSecretMedia = message.containsSecretMedia
@ -468,7 +468,24 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
var statusApply: ((Bool) -> Void)?
if let dateAndStatus = dateAndStatus {
let (size, apply) = statusLayout(context, presentationData, dateAndStatus.edited, dateAndStatus.viewCount, dateAndStatus.dateText, dateAndStatus.type, CGSize(width: nativeSize.width - 30.0, height: CGFloat.greatestFiniteMagnitude), dateAndStatus.dateReactions, dateAndStatus.dateReplies, dateAndStatus.isPinned, message.isSelfExpiring)
let statusSuggestedWidthAndContinue = statusLayout(ChatMessageDateAndStatusNode.Arguments(
context: context,
presentationData: presentationData,
edited: dateAndStatus.edited,
impressionCount: dateAndStatus.viewCount,
dateText: dateAndStatus.dateText,
type: dateAndStatus.type,
layoutInput: .standalone,
constrainedSize: CGSize(width: nativeSize.width - 30.0, height: CGFloat.greatestFiniteMagnitude),
availableReactions: associatedData.availableReactions,
reactions: dateAndStatus.dateReactions,
replyCount: dateAndStatus.dateReplies,
isPinned: dateAndStatus.isPinned,
hasAutoremove: message.isSelfExpiring
))
let (size, apply) = statusSuggestedWidthAndContinue.1(statusSuggestedWidthAndContinue.0)
statusSize = size
statusApply = apply
}
@ -1484,12 +1501,12 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
}
}
static func asyncLayout(_ node: ChatMessageInteractiveMediaNode?) -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ dateTimeFormat: PresentationDateTimeFormat, _ message: Message, _ attributes: ChatMessageEntryAttributes, _ media: Media, _ dateAndStatus: ChatMessageDateAndStatus?, _ automaticDownload: InteractiveMediaNodeAutodownloadMode, _ peerType: MediaAutoDownloadPeerType, _ sizeCalculation: InteractiveMediaNodeSizeCalculation, _ layoutConstants: ChatMessageItemLayoutConstants, _ contentMode: InteractiveMediaNodeContentMode) -> (CGSize, CGFloat, (CGSize, Bool, Bool, ImageCorners) -> (CGFloat, (CGFloat) -> (CGSize, (ContainedViewLayoutTransition, Bool) -> ChatMessageInteractiveMediaNode))) {
static func asyncLayout(_ node: ChatMessageInteractiveMediaNode?) -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ dateTimeFormat: PresentationDateTimeFormat, _ message: Message, _ associatedData: ChatMessageItemAssociatedData, _ attributes: ChatMessageEntryAttributes, _ media: Media, _ dateAndStatus: ChatMessageDateAndStatus?, _ automaticDownload: InteractiveMediaNodeAutodownloadMode, _ peerType: MediaAutoDownloadPeerType, _ sizeCalculation: InteractiveMediaNodeSizeCalculation, _ layoutConstants: ChatMessageItemLayoutConstants, _ contentMode: InteractiveMediaNodeContentMode) -> (CGSize, CGFloat, (CGSize, Bool, Bool, ImageCorners) -> (CGFloat, (CGFloat) -> (CGSize, (ContainedViewLayoutTransition, Bool) -> ChatMessageInteractiveMediaNode))) {
let currentAsyncLayout = node?.asyncLayout()
return { context, presentationData, dateTimeFormat, message, attributes, media, dateAndStatus, automaticDownload, peerType, sizeCalculation, layoutConstants, contentMode in
return { context, presentationData, dateTimeFormat, message, associatedData, attributes, media, dateAndStatus, automaticDownload, peerType, sizeCalculation, layoutConstants, contentMode in
var imageNode: ChatMessageInteractiveMediaNode
var imageLayout: (_ context: AccountContext, _ presentationData: ChatPresentationData, _ dateTimeFormat: PresentationDateTimeFormat, _ message: Message, _ attributes: ChatMessageEntryAttributes, _ media: Media, _ dateAndStatus: ChatMessageDateAndStatus?, _ automaticDownload: InteractiveMediaNodeAutodownloadMode, _ peerType: MediaAutoDownloadPeerType, _ sizeCalculation: InteractiveMediaNodeSizeCalculation, _ layoutConstants: ChatMessageItemLayoutConstants, _ contentMode: InteractiveMediaNodeContentMode) -> (CGSize, CGFloat, (CGSize, Bool, Bool, ImageCorners) -> (CGFloat, (CGFloat) -> (CGSize, (ContainedViewLayoutTransition, Bool) -> Void)))
var imageLayout: (_ context: AccountContext, _ presentationData: ChatPresentationData, _ dateTimeFormat: PresentationDateTimeFormat, _ message: Message, _ associatedData: ChatMessageItemAssociatedData, _ attributes: ChatMessageEntryAttributes, _ media: Media, _ dateAndStatus: ChatMessageDateAndStatus?, _ automaticDownload: InteractiveMediaNodeAutodownloadMode, _ peerType: MediaAutoDownloadPeerType, _ sizeCalculation: InteractiveMediaNodeSizeCalculation, _ layoutConstants: ChatMessageItemLayoutConstants, _ contentMode: InteractiveMediaNodeContentMode) -> (CGSize, CGFloat, (CGSize, Bool, Bool, ImageCorners) -> (CGFloat, (CGFloat) -> (CGSize, (ContainedViewLayoutTransition, Bool) -> Void)))
if let node = node, let currentAsyncLayout = currentAsyncLayout {
imageNode = node
@ -1499,7 +1516,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
imageLayout = imageNode.asyncLayout()
}
let (unboundSize, initialWidth, continueLayout) = imageLayout(context, presentationData, dateTimeFormat, message, attributes, media, dateAndStatus, automaticDownload, peerType, sizeCalculation, layoutConstants, contentMode)
let (unboundSize, initialWidth, continueLayout) = imageLayout(context, presentationData, dateTimeFormat, message, associatedData, attributes, media, dateAndStatus, automaticDownload, peerType, sizeCalculation, layoutConstants, contentMode)
return (unboundSize, initialWidth, { constrainedSize, automaticPlayback, wideLayout, corners in
let (finalWidth, finalLayout) = continueLayout(constrainedSize, automaticPlayback, wideLayout, corners)

View File

@ -136,9 +136,9 @@ final class ChatMessageInvoiceBubbleContentNode: ChatMessageBubbleContentNode {
return self.contentNode.transitionNode(media: media)
}
override func reactionTargetNode(value: String) -> (ASDisplayNode, ASDisplayNode)? {
override func reactionTargetView(value: String) -> UIView? {
if !self.contentNode.statusNode.isHidden {
return self.contentNode.statusNode.reactionNode(value: value)
return self.contentNode.statusNode.reactionView(value: value)
}
return nil
}

View File

@ -869,7 +869,7 @@ public class ChatMessageItemView: ListViewItemNode {
}
}
func targetReactionNode(value: String) -> (ASDisplayNode, ASDisplayNode)? {
func targetReactionView(value: String) -> UIView? {
return nil
}

View File

@ -245,9 +245,26 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode {
isReplyThread = true
}
let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: constrainedSize.width, height: CGFloat.greatestFiniteMagnitude), dateReactions, dateReplies, item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, item.message.isSelfExpiring)
statusSize = size
statusApply = apply
let statusSuggestedWidthAndContinue = statusLayout(ChatMessageDateAndStatusNode.Arguments(
context: item.context,
presentationData: item.presentationData,
edited: edited,
impressionCount: viewCount,
dateText: dateText,
type: statusType,
layoutInput: .standalone,
constrainedSize: CGSize(width: constrainedSize.width, height: CGFloat.greatestFiniteMagnitude),
availableReactions: item.associatedData.availableReactions,
reactions: dateReactions,
replyCount: dateReplies,
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
hasAutoremove: item.message.isSelfExpiring
))
let (dateAndStatusSize, dateAndStatusApply) = statusSuggestedWidthAndContinue.1(statusSuggestedWidthAndContinue.0)
statusSize = dateAndStatusSize
statusApply = dateAndStatusApply
}
let contentWidth: CGFloat
@ -487,9 +504,9 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode {
}
}
override func reactionTargetNode(value: String) -> (ASDisplayNode, ASDisplayNode)? {
override func reactionTargetView(value: String) -> UIView? {
if !self.dateAndStatusNode.isHidden {
return self.dateAndStatusNode.reactionNode(value: value)
return self.dateAndStatusNode.reactionView(value: value)
}
return nil
}

View File

@ -207,7 +207,7 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
)
}
let (unboundSize, initialWidth, refineLayout) = interactiveImageLayout(item.context, item.presentationData, item.presentationData.dateTimeFormat, item.message, item.attributes, selectedMedia!, dateAndStatus, automaticDownload, item.associatedData.automaticDownloadPeerType, sizeCalculation, layoutConstants, contentMode)
let (unboundSize, initialWidth, refineLayout) = interactiveImageLayout(item.context, item.presentationData, item.presentationData.dateTimeFormat, item.message, item.associatedData, item.attributes, selectedMedia!, dateAndStatus, automaticDownload, item.associatedData.automaticDownloadPeerType, sizeCalculation, layoutConstants, contentMode)
let forceFullCorners = false
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: true, headerSpacing: 7.0, hidesBackground: .emptyWallpaper, forceFullCorners: forceFullCorners, forceAlignment: .none)
@ -388,9 +388,9 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
return false
}
override func reactionTargetNode(value: String) -> (ASDisplayNode, ASDisplayNode)? {
override func reactionTargetView(value: String) -> UIView? {
if !self.interactiveImageNode.dateAndStatusNode.isHidden {
return self.interactiveImageNode.dateAndStatusNode.reactionNode(value: value)
return self.interactiveImageNode.dateAndStatusNode.reactionView(value: value)
}
return nil
}

View File

@ -1057,8 +1057,7 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
statusType = nil
}
var statusSize: CGSize?
var statusApply: ((Bool) -> Void)?
var statusSuggestedWidthAndContinue: (CGFloat, (CGFloat) -> (CGSize, (Bool) -> Void))?
if let statusType = statusType {
var isReplyThread = false
@ -1066,11 +1065,25 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
isReplyThread = true
}
let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, textConstrainedSize, dateReactions, dateReplies, item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, item.message.isSelfExpiring)
statusSize = size
statusApply = apply
statusSuggestedWidthAndContinue = statusLayout(ChatMessageDateAndStatusNode.Arguments(
context: item.context,
presentationData: item.presentationData,
edited: edited,
impressionCount: viewCount,
dateText: dateText,
type: statusType,
layoutInput: .trailingContent(contentWidth: 100.0, preferAdditionalInset: true),
constrainedSize: textConstrainedSize,
availableReactions: item.associatedData.availableReactions,
reactions: dateReactions,
replyCount: dateReplies,
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
hasAutoremove: item.message.isSelfExpiring
))
}
let _ = statusSuggestedWidthAndContinue
var poll: TelegramMediaPoll?
for media in item.message.media {
if let media = media as? TelegramMediaPoll {
@ -1152,21 +1165,21 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
var textFrame = CGRect(origin: CGPoint(x: -textInsets.left, y: -textInsets.top), size: textLayout.size)
var textFrameWithoutInsets = CGRect(origin: CGPoint(x: textFrame.origin.x + textInsets.left, y: textFrame.origin.y + textInsets.top), size: CGSize(width: textFrame.width - textInsets.left - textInsets.right, height: textFrame.height - textInsets.top - textInsets.bottom))
var statusFrame: CGRect?
/*var statusFrame: CGRect?
if let statusSize = statusSize {
statusFrame = CGRect(origin: CGPoint(x: textFrameWithoutInsets.maxX - statusSize.width, y: textFrameWithoutInsets.maxY - statusSize.height), size: statusSize)
}
}*/
textFrame = textFrame.offsetBy(dx: layoutConstants.text.bubbleInsets.left, dy: layoutConstants.text.bubbleInsets.top)
textFrameWithoutInsets = textFrameWithoutInsets.offsetBy(dx: layoutConstants.text.bubbleInsets.left, dy: layoutConstants.text.bubbleInsets.top)
statusFrame = statusFrame?.offsetBy(dx: layoutConstants.text.bubbleInsets.left, dy: layoutConstants.text.bubbleInsets.top)
//statusFrame = statusFrame?.offsetBy(dx: layoutConstants.text.bubbleInsets.left, dy: layoutConstants.text.bubbleInsets.top)
var boundingSize: CGSize = textFrameWithoutInsets.size
boundingSize.width += additionalTextRightInset
boundingSize.width = max(boundingSize.width, typeLayout.size.width)
boundingSize.width = max(boundingSize.width, votersLayout.size.width + 4.0 + (statusSize?.width ?? 0.0))
boundingSize.width = max(boundingSize.width, buttonSubmitInactiveTextLayout.size.width + 4.0 + (statusSize?.width ?? 0.0))
boundingSize.width = max(boundingSize.width, buttonViewResultsTextLayout.size.width + 4.0 + (statusSize?.width ?? 0.0))
boundingSize.width = max(boundingSize.width, votersLayout.size.width + 4.0/* + (statusSize?.width ?? 0.0)*/)
boundingSize.width = max(boundingSize.width, buttonSubmitInactiveTextLayout.size.width + 4.0/* + (statusSize?.width ?? 0.0)*/)
boundingSize.width = max(boundingSize.width, buttonViewResultsTextLayout.size.width + 4.0/* + (statusSize?.width ?? 0.0)*/)
boundingSize.width += layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right
boundingSize.height += layoutConstants.text.bubbleInsets.top + layoutConstants.text.bubbleInsets.bottom
@ -1288,7 +1301,7 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
let buttonSubmitActiveTextFrame = CGRect(origin: CGPoint(x: floor((resultSize.width - buttonSubmitActiveTextLayout.size.width) / 2.0), y: optionsButtonSpacing), size: buttonSubmitActiveTextLayout.size)
let buttonViewResultsTextFrame = CGRect(origin: CGPoint(x: floor((resultSize.width - buttonViewResultsTextLayout.size.width) / 2.0), y: optionsButtonSpacing), size: buttonViewResultsTextLayout.size)
var adjustedStatusFrame: CGRect?
/*var adjustedStatusFrame: CGRect?
if let statusFrame = statusFrame {
var localStatusFrame = CGRect(origin: CGPoint(x: boundingWidth - statusFrame.size.width - layoutConstants.text.bubbleInsets.right, y: resultSize.height - statusFrame.size.height - 6.0), size: statusFrame.size)
if localStatusFrame.minX <= buttonViewResultsTextFrame.maxX || localStatusFrame.minX <= buttonSubmitActiveTextFrame.maxX {
@ -1296,7 +1309,7 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
resultSize.height += 10.0
}
adjustedStatusFrame = localStatusFrame
}
}*/
return (resultSize, { [weak self] animation, synchronousLoad in
if let strongSelf = self {
@ -1367,7 +1380,7 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
}
strongSelf.optionNodes = updatedOptionNodes
if let statusApply = statusApply, let adjustedStatusFrame = adjustedStatusFrame {
/*if let statusApply = statusApply, let adjustedStatusFrame = adjustedStatusFrame {
let previousStatusFrame = strongSelf.statusNode.frame
strongSelf.statusNode.frame = adjustedStatusFrame
var hasAnimation = true
@ -1385,7 +1398,7 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
strongSelf.statusNode.layer.animatePosition(from: previousPosition, to: statusPosition, duration: duration, timingFunction: kCAMediaTimingFunctionSpring)
}
}
} else if strongSelf.statusNode.supernode != nil {
} else */if strongSelf.statusNode.supernode != nil {
strongSelf.statusNode.removeFromSupernode()
}
@ -1749,9 +1762,9 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
}
}
override func reactionTargetNode(value: String) -> (ASDisplayNode, ASDisplayNode)? {
override func reactionTargetView(value: String) -> UIView? {
if !self.statusNode.isHidden {
return self.statusNode.reactionNode(value: value)
return self.statusNode.reactionView(value: value)
}
return nil
}

View File

@ -88,82 +88,66 @@ class ChatMessageRestrictedBubbleContentNode: ChatMessageBubbleContentNode {
statusType = nil
}
var statusSize: CGSize?
var statusApply: ((Bool) -> Void)?
let entities = [MessageTextEntity(range: 0..<rawText.count, type: .Italic)]
let messageTheme = incoming ? item.presentationData.theme.theme.chat.message.incoming : item.presentationData.theme.theme.chat.message.outgoing
let textFont = item.presentationData.messageFont
let attributedText = stringWithAppliedEntities(rawText, entities: entities, baseColor: messageTheme.primaryTextColor.withAlphaComponent(0.7), linkColor: messageTheme.linkTextColor, baseFont: textFont, linkFont: textFont, boldFont: item.presentationData.messageBoldFont, italicFont: item.presentationData.messageItalicFont, boldItalicFont: item.presentationData.messageBoldItalicFont, fixedFont: item.presentationData.messageFixedFont, blockQuoteFont: item.presentationData.messageBlockQuoteFont)
let textInsets = UIEdgeInsets(top: 2.0, left: 0.0, bottom: 5.0, right: 0.0)
let (textLayout, textApply) = textLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: textInsets, lineColor: messageTheme.accentControlColor))
var textFrame = CGRect(origin: CGPoint(x: -textInsets.left, y: -textInsets.top), size: textLayout.size)
var textFrameWithoutInsets = CGRect(origin: CGPoint(x: textFrame.origin.x + textInsets.left, y: textFrame.origin.y + textInsets.top), size: CGSize(width: textFrame.width - textInsets.left - textInsets.right, height: textFrame.height - textInsets.top - textInsets.bottom))
textFrame = textFrame.offsetBy(dx: layoutConstants.text.bubbleInsets.left, dy: layoutConstants.text.bubbleInsets.top)
textFrameWithoutInsets = textFrameWithoutInsets.offsetBy(dx: layoutConstants.text.bubbleInsets.left, dy: layoutConstants.text.bubbleInsets.top)
var statusSuggestedWidthAndContinue: (CGFloat, (CGFloat) -> (CGSize, (Bool) -> Void))?
if let statusType = statusType {
var isReplyThread = false
if case .replyThread = item.chatLocation {
isReplyThread = true
}
let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, textConstrainedSize, dateReactions, dateReplies, item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && isReplyThread, item.message.isSelfExpiring)
statusSize = size
statusApply = apply
statusSuggestedWidthAndContinue = statusLayout(ChatMessageDateAndStatusNode.Arguments(
context: item.context,
presentationData: item.presentationData,
edited: edited,
impressionCount: viewCount,
dateText: dateText,
type: statusType,
layoutInput: .trailingContent(contentWidth: textLayout.trailingLineWidth, preferAdditionalInset: false),
constrainedSize: textConstrainedSize,
availableReactions: item.associatedData.availableReactions,
reactions: dateReactions,
replyCount: dateReplies,
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && isReplyThread,
hasAutoremove: item.message.isSelfExpiring
))
}
let entities = [MessageTextEntity(range: 0..<rawText.count, type: .Italic)]
let messageTheme = incoming ? item.presentationData.theme.theme.chat.message.incoming : item.presentationData.theme.theme.chat.message.outgoing
let textFont = item.presentationData.messageFont
let forceStatusNewline = false
let attributedText = stringWithAppliedEntities(rawText, entities: entities, baseColor: messageTheme.primaryTextColor.withAlphaComponent(0.7), linkColor: messageTheme.linkTextColor, baseFont: textFont, linkFont: textFont, boldFont: item.presentationData.messageBoldFont, italicFont: item.presentationData.messageItalicFont, boldItalicFont: item.presentationData.messageBoldItalicFont, fixedFont: item.presentationData.messageFixedFont, blockQuoteFont: item.presentationData.messageBlockQuoteFont)
var cutout: TextNodeCutout?
if let statusSize = statusSize, !forceStatusNewline {
cutout = TextNodeCutout(bottomRight: statusSize)
var suggestedBoundingWidth: CGFloat = textFrameWithoutInsets.width
if let statusSuggestedWidthAndContinue = statusSuggestedWidthAndContinue {
suggestedBoundingWidth = max(suggestedBoundingWidth, statusSuggestedWidthAndContinue.0)
}
let textInsets = UIEdgeInsets(top: 2.0, left: 0.0, bottom: 5.0, right: 0.0)
let (textLayout, textApply) = textLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: cutout, insets: textInsets, lineColor: messageTheme.accentControlColor))
var textFrame = CGRect(origin: CGPoint(x: -textInsets.left, y: -textInsets.top), size: textLayout.size)
var textFrameWithoutInsets = CGRect(origin: CGPoint(x: textFrame.origin.x + textInsets.left, y: textFrame.origin.y + textInsets.top), size: CGSize(width: textFrame.width - textInsets.left - textInsets.right, height: textFrame.height - textInsets.top - textInsets.bottom))
var statusFrame: CGRect?
if let statusSize = statusSize {
statusFrame = CGRect(origin: CGPoint(x: textFrameWithoutInsets.maxX - statusSize.width, y: textFrameWithoutInsets.maxY - statusSize.height), size: statusSize)
}
textFrame = textFrame.offsetBy(dx: layoutConstants.text.bubbleInsets.left, dy: layoutConstants.text.bubbleInsets.top)
textFrameWithoutInsets = textFrameWithoutInsets.offsetBy(dx: layoutConstants.text.bubbleInsets.left, dy: layoutConstants.text.bubbleInsets.top)
statusFrame = statusFrame?.offsetBy(dx: layoutConstants.text.bubbleInsets.left, dy: layoutConstants.text.bubbleInsets.top)
var suggestedBoundingWidth: CGFloat
if let statusFrame = statusFrame {
suggestedBoundingWidth = textFrameWithoutInsets.union(statusFrame).width
} else {
suggestedBoundingWidth = textFrameWithoutInsets.width
}
suggestedBoundingWidth += layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right
let sideInsets = layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right
suggestedBoundingWidth += sideInsets
return (suggestedBoundingWidth, { boundingWidth in
var boundingSize: CGSize
var adjustedStatusFrame: CGRect?
if let statusFrame = statusFrame {
let centeredTextFrame = CGRect(origin: CGPoint(x: floor((boundingWidth - textFrame.size.width) / 2.0), y: 0.0), size: textFrame.size)
let statusOverlapsCenteredText = CGRect(origin: CGPoint(), size: statusFrame.size).intersects(centeredTextFrame)
if !forceStatusNewline || statusOverlapsCenteredText {
boundingSize = textFrameWithoutInsets.union(statusFrame).size
boundingSize.width += layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right
boundingSize.height += layoutConstants.text.bubbleInsets.top + layoutConstants.text.bubbleInsets.bottom
adjustedStatusFrame = CGRect(origin: CGPoint(x: boundingWidth - statusFrame.size.width - layoutConstants.text.bubbleInsets.right, y: statusFrame.origin.y), size: statusFrame.size)
} else {
boundingSize = textFrameWithoutInsets.size
boundingSize.width += layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right
boundingSize.height += layoutConstants.text.bubbleInsets.top + layoutConstants.text.bubbleInsets.bottom
adjustedStatusFrame = CGRect(origin: CGPoint(x: boundingWidth - statusFrame.size.width - layoutConstants.text.bubbleInsets.right, y: boundingSize.height - statusFrame.height - layoutConstants.text.bubbleInsets.bottom), size: statusFrame.size)
}
} else {
boundingSize = textFrameWithoutInsets.size
boundingSize.width += layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right
boundingSize.height += layoutConstants.text.bubbleInsets.top + layoutConstants.text.bubbleInsets.bottom
let statusSizeAndApply = statusSuggestedWidthAndContinue?.1(boundingWidth)
boundingSize = textFrameWithoutInsets.size
if let statusSizeAndApply = statusSizeAndApply {
boundingSize.height += statusSizeAndApply.0.height
}
boundingSize.width += layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right
boundingSize.height += layoutConstants.text.bubbleInsets.top + layoutConstants.text.bubbleInsets.bottom
return (boundingSize, { [weak self] animation, _ in
if let strongSelf = self {
@ -192,33 +176,19 @@ class ChatMessageRestrictedBubbleContentNode: ChatMessageBubbleContentNode {
let _ = textApply()
if let statusApply = statusApply, let adjustedStatusFrame = adjustedStatusFrame {
let previousStatusFrame = strongSelf.statusNode.frame
strongSelf.statusNode.frame = adjustedStatusFrame
var hasAnimation = true
if case .None = animation {
hasAnimation = false
}
statusApply(hasAnimation)
strongSelf.textNode.frame = textFrame
if let statusSizeAndApply = statusSizeAndApply {
strongSelf.statusNode.frame = CGRect(origin: CGPoint(x: textFrameWithoutInsets.minX, y: textFrameWithoutInsets.maxY), size: statusSizeAndApply.0)
if strongSelf.statusNode.supernode == nil {
strongSelf.addSubnode(strongSelf.statusNode)
statusSizeAndApply.1(false)
} else {
if case let .System(duration) = animation {
let delta = CGPoint(x: previousStatusFrame.maxX - adjustedStatusFrame.maxX, y: previousStatusFrame.minY - adjustedStatusFrame.minY)
let statusPosition = strongSelf.statusNode.layer.position
let previousPosition = CGPoint(x: statusPosition.x + delta.x, y: statusPosition.y + delta.y)
strongSelf.statusNode.layer.animatePosition(from: previousPosition, to: statusPosition, duration: duration, timingFunction: kCAMediaTimingFunctionSpring)
}
statusSizeAndApply.1(animation.isAnimated)
}
} else if strongSelf.statusNode.supernode != nil {
strongSelf.statusNode.removeFromSupernode()
}
var adjustedTextFrame = textFrame
if forceStatusNewline {
adjustedTextFrame.origin.x = floor((boundingWidth - adjustedTextFrame.width) / 2.0)
}
strongSelf.textNode.frame = adjustedTextFrame
}
})
})

View File

@ -483,7 +483,23 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
isReplyThread = true
}
let (dateAndStatusSize, dateAndStatusApply) = makeDateAndStatusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), dateReactions, dateReplies, item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, item.message.isSelfExpiring)
let statusSuggestedWidthAndContinue = makeDateAndStatusLayout(ChatMessageDateAndStatusNode.Arguments(
context: item.context,
presentationData: item.presentationData,
edited: edited,
impressionCount: viewCount,
dateText: dateText,
type: statusType,
layoutInput: .standalone,
constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude),
availableReactions: item.associatedData.availableReactions,
reactions: dateReactions,
replyCount: dateReplies,
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
hasAutoremove: item.message.isSelfExpiring
))
let (dateAndStatusSize, dateAndStatusApply) = statusSuggestedWidthAndContinue.1(statusSuggestedWidthAndContinue.0)
var viaBotApply: (TextNodeLayout, () -> TextNode)?
var replyInfoApply: (CGSize, () -> ChatMessageReplyInfoNode)?
@ -931,41 +947,8 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
}
if let (_, f) = strongSelf.awaitingAppliedReaction {
/*var bounds = strongSelf.bounds
let offset = bounds.origin.x
bounds.origin.x = 0.0
strongSelf.bounds = bounds
var shadowBounds = strongSelf.shadowNode.bounds
let shadowOffset = shadowBounds.origin.x
shadowBounds.origin.x = 0.0
strongSelf.shadowNode.bounds = shadowBounds
if !offset.isZero {
strongSelf.layer.animateBoundsOriginXAdditive(from: offset, to: 0.0, duration: 0.2, timingFunction: kCAMediaTimingFunctionSpring)
}
if !shadowOffset.isZero {
strongSelf.shadowNode.layer.animateBoundsOriginXAdditive(from: shadowOffset, to: 0.0, duration: 0.2, timingFunction: kCAMediaTimingFunctionSpring)
}
if let swipeToReplyNode = strongSelf.swipeToReplyNode {
strongSelf.swipeToReplyNode = nil
swipeToReplyNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak swipeToReplyNode] _ in
swipeToReplyNode?.removeFromSupernode()
})
swipeToReplyNode.layer.animateScale(from: 1.0, to: 0.2, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
}
*/
strongSelf.awaitingAppliedReaction = nil
/*var targetNode: ASDisplayNode?
var hideTarget = false
if let awaitingAppliedReaction = awaitingAppliedReaction {
for contentNode in strongSelf.contentNodes {
if let (reactionNode, count) = contentNode.reactionTargetNode(value: awaitingAppliedReaction) {
targetNode = reactionNode
hideTarget = count == 1
break
}
}
}
strongSelf.reactionRecognizer?.complete(into: targetNode, hideTarget: hideTarget)*/
f()
}
}
@ -1512,9 +1495,9 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
}
}
override func targetReactionNode(value: String) -> (ASDisplayNode, ASDisplayNode)? {
override func targetReactionView(value: String) -> UIView? {
if !self.dateAndStatusNode.isHidden {
return self.dateAndStatusNode.reactionNode(value: value)
return self.dateAndStatusNode.reactionView(value: value)
}
return nil
}

View File

@ -65,6 +65,13 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
self.textAccessibilityOverlayNode.openUrl = { [weak self] url in
self?.item?.controllerInteraction.openUrl(url, false, false, nil)
}
self.statusNode.reactionSelected = { [weak self] value in
guard let strongSelf = self, let item = strongSelf.item else {
return
}
item.controllerInteraction.updateMessageReaction(item.message, value)
}
}
required init?(coder aDecoder: NSCoder) {
@ -157,20 +164,6 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
statusType = nil
}
var statusSize: CGSize?
var statusApply: ((Bool) -> Void)?
if let statusType = statusType {
var isReplyThread = false
if case .replyThread = item.chatLocation {
isReplyThread = true
}
let (size, apply) = statusLayout(item.context, item.presentationData, edited, viewCount, dateText, statusType, textConstrainedSize, dateReactions, dateReplies, item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, item.message.isSelfExpiring)
statusSize = size
statusApply = apply
}
let rawText: String
let attributedText: NSAttributedString
var messageEntities: [MessageTextEntity]?
@ -265,7 +258,6 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
let messageTheme = incoming ? item.presentationData.theme.theme.chat.message.incoming : item.presentationData.theme.theme.chat.message.outgoing
let textFont = item.presentationData.messageFont
let forceStatusNewline = false
if let entities = entities {
attributedText = stringWithAppliedEntities(rawText, entities: entities, baseColor: messageTheme.primaryTextColor, linkColor: messageTheme.linkTextColor, baseFont: textFont, linkFont: textFont, boldFont: item.presentationData.messageBoldFont, italicFont: item.presentationData.messageItalicFont, boldItalicFont: item.presentationData.messageBoldItalicFont, fixedFont: item.presentationData.messageFixedFont, blockQuoteFont: item.presentationData.messageBlockQuoteFont)
@ -275,65 +267,61 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
attributedText = NSAttributedString(string: " ", font: textFont, textColor: messageTheme.primaryTextColor)
}
var cutout: TextNodeCutout?
if let statusSize = statusSize, !forceStatusNewline {
cutout = TextNodeCutout(bottomRight: statusSize)
}
let cutout: TextNodeCutout? = nil
let textInsets = UIEdgeInsets(top: 2.0, left: 2.0, bottom: 5.0, right: 2.0)
let (textLayout, textApply) = textLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: cutout, insets: textInsets, lineColor: messageTheme.accentControlColor))
var statusSuggestedWidthAndContinue: (CGFloat, (CGFloat) -> (CGSize, (Bool) -> Void))?
if let statusType = statusType {
var isReplyThread = false
if case .replyThread = item.chatLocation {
isReplyThread = true
}
statusSuggestedWidthAndContinue = statusLayout(ChatMessageDateAndStatusNode.Arguments(
context: item.context,
presentationData: item.presentationData,
edited: edited,
impressionCount: viewCount,
dateText: dateText,
type: statusType,
layoutInput: .trailingContent(contentWidth: textLayout.trailingLineWidth, preferAdditionalInset: false),
constrainedSize: textConstrainedSize,
availableReactions: item.associatedData.availableReactions,
reactions: dateReactions,
replyCount: dateReplies,
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && isReplyThread,
hasAutoremove: item.message.isSelfExpiring
))
}
var textFrame = CGRect(origin: CGPoint(x: -textInsets.left, y: -textInsets.top), size: textLayout.size)
var textFrameWithoutInsets = CGRect(origin: CGPoint(x: textFrame.origin.x + textInsets.left, y: textFrame.origin.y + textInsets.top), size: CGSize(width: textFrame.width - textInsets.left - textInsets.right, height: textFrame.height - textInsets.top - textInsets.bottom))
var statusFrame: CGRect?
if let statusSize = statusSize {
statusFrame = CGRect(origin: CGPoint(x: textFrameWithoutInsets.maxX - statusSize.width, y: textFrameWithoutInsets.maxY - statusSize.height), size: statusSize)
}
textFrame = textFrame.offsetBy(dx: layoutConstants.text.bubbleInsets.left, dy: layoutConstants.text.bubbleInsets.top)
textFrameWithoutInsets = textFrameWithoutInsets.offsetBy(dx: layoutConstants.text.bubbleInsets.left, dy: layoutConstants.text.bubbleInsets.top)
statusFrame = statusFrame?.offsetBy(dx: layoutConstants.text.bubbleInsets.left, dy: layoutConstants.text.bubbleInsets.top)
var suggestedBoundingWidth: CGFloat
if let statusFrame = statusFrame {
suggestedBoundingWidth = textFrameWithoutInsets.union(statusFrame).width
} else {
suggestedBoundingWidth = textFrameWithoutInsets.width
var suggestedBoundingWidth: CGFloat = textFrameWithoutInsets.width
if let statusSuggestedWidthAndContinue = statusSuggestedWidthAndContinue {
suggestedBoundingWidth = max(suggestedBoundingWidth, statusSuggestedWidthAndContinue.0)
}
suggestedBoundingWidth += layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right
let sideInsets = layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right
suggestedBoundingWidth += sideInsets
return (suggestedBoundingWidth, { boundingWidth in
var boundingSize: CGSize
var adjustedStatusFrame: CGRect?
if let statusFrame = statusFrame {
let centeredTextFrame = CGRect(origin: CGPoint(x: floor((boundingWidth - textFrame.size.width) / 2.0), y: 0.0), size: textFrame.size)
let statusOverlapsCenteredText = CGRect(origin: CGPoint(), size: statusFrame.size).intersects(centeredTextFrame)
if !forceStatusNewline || statusOverlapsCenteredText {
boundingSize = textFrameWithoutInsets.union(statusFrame).size
boundingSize.width += layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right
boundingSize.height += layoutConstants.text.bubbleInsets.top + layoutConstants.text.bubbleInsets.bottom
adjustedStatusFrame = CGRect(origin: CGPoint(x: boundingWidth - statusFrame.size.width - layoutConstants.text.bubbleInsets.right, y: statusFrame.origin.y), size: statusFrame.size)
} else {
boundingSize = textFrameWithoutInsets.size
boundingSize.width += layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right
boundingSize.height += layoutConstants.text.bubbleInsets.top + layoutConstants.text.bubbleInsets.bottom
adjustedStatusFrame = CGRect(origin: CGPoint(x: boundingWidth - statusFrame.size.width - layoutConstants.text.bubbleInsets.right, y: boundingSize.height - statusFrame.height - layoutConstants.text.bubbleInsets.bottom), size: statusFrame.size)
}
} else {
boundingSize = textFrameWithoutInsets.size
boundingSize.width += layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right
boundingSize.height += layoutConstants.text.bubbleInsets.top + layoutConstants.text.bubbleInsets.bottom
let statusSizeAndApply = statusSuggestedWidthAndContinue?.1(boundingWidth - sideInsets)
boundingSize = textFrameWithoutInsets.size
if let statusSizeAndApply = statusSizeAndApply {
boundingSize.height += statusSizeAndApply.0.height
}
if attributedText.string.isEmpty, var adjustedStatusFrameValue = adjustedStatusFrame {
adjustedStatusFrameValue.origin.y = 1.0
boundingSize.height = adjustedStatusFrameValue.maxY + 5.0
adjustedStatusFrame = adjustedStatusFrameValue
}
boundingSize.width += layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right
boundingSize.height += layoutConstants.text.bubbleInsets.top + layoutConstants.text.bubbleInsets.bottom
return (boundingSize, { [weak self] animation, _ in
if let strongSelf = self {
@ -366,37 +354,11 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
strongSelf.textNode.displaysAsynchronously = !item.presentationData.isPreview && !item.presentationData.theme.theme.forceSync
let _ = textApply()
if let statusApply = statusApply, let adjustedStatusFrame = adjustedStatusFrame {
let previousStatusFrame = strongSelf.statusNode.frame
strongSelf.statusNode.frame = adjustedStatusFrame
var hasAnimation = true
if case .None = animation {
hasAnimation = false
}
statusApply(hasAnimation)
if strongSelf.statusNode.supernode == nil {
strongSelf.addSubnode(strongSelf.statusNode)
} else {
if case let .System(duration) = animation {
let delta = CGPoint(x: previousStatusFrame.maxX - adjustedStatusFrame.maxX, y: previousStatusFrame.minY - adjustedStatusFrame.minY)
let statusPosition = strongSelf.statusNode.layer.position
let previousPosition = CGPoint(x: statusPosition.x + delta.x, y: statusPosition.y + delta.y)
strongSelf.statusNode.layer.animatePosition(from: previousPosition, to: statusPosition, duration: duration, timingFunction: kCAMediaTimingFunctionSpring)
}
}
} else if strongSelf.statusNode.supernode != nil {
strongSelf.statusNode.removeFromSupernode()
}
var adjustedTextFrame = textFrame
if forceStatusNewline {
adjustedTextFrame.origin.x = floor((boundingWidth - adjustedTextFrame.width) / 2.0)
}
strongSelf.textNode.frame = adjustedTextFrame
strongSelf.textNode.frame = textFrame
if let textSelectionNode = strongSelf.textSelectionNode {
let shouldUpdateLayout = textSelectionNode.frame.size != adjustedTextFrame.size
textSelectionNode.frame = adjustedTextFrame
textSelectionNode.highlightAreaNode.frame = adjustedTextFrame
let shouldUpdateLayout = textSelectionNode.frame.size != textFrame.size
textSelectionNode.frame = textFrame
textSelectionNode.highlightAreaNode.frame = textFrame
if shouldUpdateLayout {
textSelectionNode.updateLayout()
}
@ -404,6 +366,18 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
strongSelf.textAccessibilityOverlayNode.frame = textFrame
strongSelf.textAccessibilityOverlayNode.cachedLayout = textLayout
if let statusSizeAndApply = statusSizeAndApply {
strongSelf.statusNode.frame = CGRect(origin: CGPoint(x: textFrameWithoutInsets.minX, y: textFrameWithoutInsets.maxY), size: statusSizeAndApply.0)
if strongSelf.statusNode.supernode == nil {
strongSelf.addSubnode(strongSelf.statusNode)
statusSizeAndApply.1(false)
} else {
statusSizeAndApply.1(animation.isAnimated)
}
} else if strongSelf.statusNode.supernode != nil {
strongSelf.statusNode.removeFromSupernode()
}
if let forwardInfo = item.message.forwardInfo, forwardInfo.flags.contains(.isImported) {
strongSelf.statusNode.pressed = {
guard let strongSelf = self else {
@ -470,6 +444,13 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
}
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if self.statusNode.supernode != nil, let result = self.statusNode.hitTest(self.view.convert(point, to: self.statusNode.view), with: event) {
return result
}
return super.hitTest(point, with: event)
}
override func updateTouchesAtPoint(_ point: CGPoint?) {
if let item = self.item {
var rects: [CGRect]?
@ -618,9 +599,9 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
}
}
override func reactionTargetNode(value: String) -> (ASDisplayNode, ASDisplayNode)? {
override func reactionTargetView(value: String) -> UIView? {
if !self.statusNode.isHidden {
return self.statusNode.reactionNode(value: value)
return self.statusNode.reactionView(value: value)
}
return nil
}

View File

@ -543,9 +543,9 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode {
self.contentNode.updateTouchesAtPoint(point.flatMap { $0.offsetBy(dx: -contentNodeFrame.minX, dy: -contentNodeFrame.minY) })
}
override func reactionTargetNode(value: String) -> (ASDisplayNode, ASDisplayNode)? {
override func reactionTargetView(value: String) -> UIView? {
if !self.contentNode.statusNode.isHidden {
return self.contentNode.statusNode.reactionNode(value: value)
return self.contentNode.statusNode.reactionView(value: value)
}
return nil
}

View File

@ -255,7 +255,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
self?.openPeerMention(name)
}, openMessageContextMenu: { [weak self] message, selectAll, node, frame, _ in
self?.openMessageContextMenu(message: message, selectAll: selectAll, node: node, frame: frame)
}, updateMessageReaction: { _ in
}, updateMessageReaction: { _, _ in
}, activateMessagePinch: { _ in
}, openMessageContextActions: { _, _, _, _ in
}, navigateToMessage: { _, _ in }, navigateToMessageStandalone: { _ in

View File

@ -114,7 +114,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
let action = TelegramMediaActionType.titleUpdated(title: new)
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
case let .changeAbout(prev, new):
var peers = SimpleDictionary<PeerId, Peer>()
var author: Peer?
@ -145,14 +145,14 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
}
let action = TelegramMediaActionType.customText(text: text, entities: entities)
let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
case .content:
let peers = SimpleDictionary<PeerId, Peer>()
let attributes: [MessageAttribute] = []
let prevMessage = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: prev, attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: new, attributes: attributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil), additionalContent: !prev.isEmpty ? .eventLogPreviousDescription(prevMessage) : nil)
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil), additionalContent: !prev.isEmpty ? .eventLogPreviousDescription(prevMessage) : nil)
}
case let .changeUsername(prev, new):
var peers = SimpleDictionary<PeerId, Peer>()
@ -183,7 +183,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
}
let action: TelegramMediaActionType = TelegramMediaActionType.customText(text: text, entities: entities)
let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
case .content:
var previousAttributes: [MessageAttribute] = []
var attributes: [MessageAttribute] = []
@ -202,7 +202,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
let prevMessage = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: prevText, attributes: previousAttributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: text, attributes: attributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil), additionalContent: !prev.isEmpty ? .eventLogPreviousLink(prevMessage) : nil)
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil), additionalContent: !prev.isEmpty ? .eventLogPreviousLink(prevMessage) : nil)
}
case let .changePhoto(_, new):
var peers = SimpleDictionary<PeerId, Peer>()
@ -221,7 +221,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
let action = TelegramMediaActionType.photoUpdated(image: photo)
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
case let .toggleInvites(value):
var peers = SimpleDictionary<PeerId, Peer>()
var author: Peer?
@ -248,7 +248,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
}
let action = TelegramMediaActionType.customText(text: text, entities: entities)
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
case let .toggleSignatures(value):
var peers = SimpleDictionary<PeerId, Peer>()
var author: Peer?
@ -275,7 +275,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
}
let action = TelegramMediaActionType.customText(text: text, entities: entities)
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
case let .updatePinned(message):
switch self.id.contentIndex {
case .header:
@ -306,7 +306,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
let action = TelegramMediaActionType.customText(text: text, entities: entities)
let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
case .content:
if let message = message {
var peers = SimpleDictionary<PeerId, Peer>()
@ -324,7 +324,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
}
}
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: message.effectiveAuthor, text: message.text, attributes: attributes, media: message.media, peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
} else {
var peers = SimpleDictionary<PeerId, Peer>()
var author: Peer?
@ -346,7 +346,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
let action = TelegramMediaActionType.customText(text: text, entities: entities)
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 0), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
}
}
case let .editMessage(prev, message):
@ -391,7 +391,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
let action = TelegramMediaActionType.customText(text: text, entities: entities)
let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
case .content:
var peers = SimpleDictionary<PeerId, Peer>()
var attributes: [MessageAttribute] = []
@ -408,7 +408,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
}
}
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: message.effectiveAuthor, text: message.text, attributes: attributes, media: message.media, peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: filterOriginalMessageFlags(message), read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil), additionalContent: !prev.text.isEmpty || !message.text.isEmpty ? .eventLogPreviousMessage(filterOriginalMessageFlags(prev)) : nil)
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil), controllerInteraction: controllerInteraction, content: .message(message: filterOriginalMessageFlags(message), read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil), additionalContent: !prev.text.isEmpty || !message.text.isEmpty ? .eventLogPreviousMessage(filterOriginalMessageFlags(prev)) : nil)
}
case let .deleteMessage(message):
switch self.id.contentIndex {
@ -434,7 +434,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
let action = TelegramMediaActionType.customText(text: text, entities: entities)
let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
case .content:
var peers = SimpleDictionary<PeerId, Peer>()
var attributes: [MessageAttribute] = []
@ -458,7 +458,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
}
}
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: message.effectiveAuthor, text: message.text, attributes: attributes, media: message.media, peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
}
case .participantJoin, .participantLeave:
var peers = SimpleDictionary<PeerId, Peer>()
@ -476,7 +476,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
action = TelegramMediaActionType.removedMembers(peerIds: [self.entry.event.peerId])
}
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
case let .participantInvite(participant):
var peers = SimpleDictionary<PeerId, Peer>()
var author: Peer?
@ -493,7 +493,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
let action: TelegramMediaActionType
action = TelegramMediaActionType.addedMembers(peerIds: [participant.peer.id])
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
case let .participantToggleBan(prev, new):
var peers = SimpleDictionary<PeerId, Peer>()
var attributes: [MessageAttribute] = []
@ -623,7 +623,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
}
}
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: text, attributes: attributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
case let .participantToggleAdmin(prev, new):
var peers = SimpleDictionary<PeerId, Peer>()
var attributes: [MessageAttribute] = []
@ -856,7 +856,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
}
}
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: text, attributes: attributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
case let .changeStickerPack(_, new):
var peers = SimpleDictionary<PeerId, Peer>()
var author: Peer?
@ -885,7 +885,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
let action = TelegramMediaActionType.customText(text: text, entities: entities)
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
case let .togglePreHistoryHidden(value):
var peers = SimpleDictionary<PeerId, Peer>()
var author: Peer?
@ -915,7 +915,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
let action = TelegramMediaActionType.customText(text: text, entities: entities)
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
case let .updateDefaultBannedRights(prev, new):
var peers = SimpleDictionary<PeerId, Peer>()
var attributes: [MessageAttribute] = []
@ -973,7 +973,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
}
}
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: text, attributes: attributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
case let .pollStopped(message):
switch self.id.contentIndex {
case .header:
@ -1001,7 +1001,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
let action = TelegramMediaActionType.customText(text: text, entities: entities)
let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
case .content:
var peers = SimpleDictionary<PeerId, Peer>()
var attributes: [MessageAttribute] = []
@ -1018,7 +1018,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
}
}
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: message.author, text: message.text, attributes: attributes, media: message.media, peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: filterOriginalMessageFlags(message), read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil), additionalContent: nil)
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil), controllerInteraction: controllerInteraction, content: .message(message: filterOriginalMessageFlags(message), read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil), additionalContent: nil)
}
case let .linkedPeerUpdated(previous, updated):
var peers = SimpleDictionary<PeerId, Peer>()
@ -1074,7 +1074,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
let action = TelegramMediaActionType.customText(text: text, entities: entities)
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
case let .changeGeoLocation(_, updated):
var peers = SimpleDictionary<PeerId, Peer>()
var author: Peer?
@ -1096,12 +1096,12 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
let mediaMap = TelegramMediaMap(latitude: updated.latitude, longitude: updated.longitude, heading: nil, accuracyRadius: nil, geoPlace: nil, venue: nil, liveBroadcastingTimeout: nil, liveProximityNotificationRadius: nil)
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: text, attributes: [], media: [mediaMap], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
} else {
let action = TelegramMediaActionType.customText(text: text, entities: entities)
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
}
case let .updateSlowmode(_, newValue):
var peers = SimpleDictionary<PeerId, Peer>()
@ -1132,7 +1132,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
let action = TelegramMediaActionType.customText(text: text, entities: entities)
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
case .startGroupCall, .endGroupCall:
var peers = SimpleDictionary<PeerId, Peer>()
var author: Peer?
@ -1169,7 +1169,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
let action = TelegramMediaActionType.customText(text: text, entities: entities)
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
case let .groupCallUpdateParticipantMuteStatus(participantId, isMuted):
var peers = SimpleDictionary<PeerId, Peer>()
var author: Peer?
@ -1203,7 +1203,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
let action = TelegramMediaActionType.customText(text: text, entities: entities)
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
case let .updateGroupCallSettings(joinMuted):
var peers = SimpleDictionary<PeerId, Peer>()
var author: Peer?
@ -1232,7 +1232,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
let action = TelegramMediaActionType.customText(text: text, entities: entities)
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
case let .groupCallUpdateParticipantVolume(participantId, volume):
var peers = SimpleDictionary<PeerId, Peer>()
var author: Peer?
@ -1263,7 +1263,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
let action = TelegramMediaActionType.customText(text: text, entities: entities)
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
case let .deleteExportedInvitation(invite):
var peers = SimpleDictionary<PeerId, Peer>()
var author: Peer?
@ -1289,7 +1289,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
let action = TelegramMediaActionType.customText(text: text, entities: entities)
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
case let .revokeExportedInvitation(invite):
var peers = SimpleDictionary<PeerId, Peer>()
var author: Peer?
@ -1315,7 +1315,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
let action = TelegramMediaActionType.customText(text: text, entities: entities)
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
case let .editExportedInvitation(_, updatedInvite):
var peers = SimpleDictionary<PeerId, Peer>()
var author: Peer?
@ -1341,7 +1341,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
let action = TelegramMediaActionType.customText(text: text, entities: entities)
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
case let .participantJoinedViaInvite(invite):
var peers = SimpleDictionary<PeerId, Peer>()
var author: Peer?
@ -1367,7 +1367,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
let action = TelegramMediaActionType.customText(text: text, entities: entities)
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
case let .changeHistoryTTL(_, updatedValue):
var peers = SimpleDictionary<PeerId, Peer>()
var author: Peer?
@ -1398,7 +1398,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
let action = TelegramMediaActionType.customText(text: text, entities: entities)
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
case let .changeTheme(_, updatedValue):
var peers = SimpleDictionary<PeerId, Peer>()
var author: Peer?
@ -1429,7 +1429,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
let action = TelegramMediaActionType.customText(text: text, entities: entities)
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
case let .participantJoinByRequest(invite, approvedBy):
var peers = SimpleDictionary<PeerId, Peer>()
var author: Peer?
@ -1462,7 +1462,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
let action = TelegramMediaActionType.customText(text: text, entities: entities)
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
case let .toggleCopyProtection(value):
var peers = SimpleDictionary<PeerId, Peer>()
var author: Peer?
@ -1489,7 +1489,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
}
let action = TelegramMediaActionType.customText(text: text, entities: entities)
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
case let .sendMessage(message):
switch self.id.contentIndex {
case .header:
@ -1514,7 +1514,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
let action = TelegramMediaActionType.customText(text: text, entities: entities)
let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
case .content:
var peers = SimpleDictionary<PeerId, Peer>()
var attributes: [MessageAttribute] = []
@ -1531,7 +1531,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
}
}
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: message.effectiveAuthor, text: message.text, attributes: attributes, media: message.media, peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
}
}
}

View File

@ -108,7 +108,7 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode {
var selectStickerImpl: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)?
self.controllerInteraction = ChatControllerInteraction(openMessage: { _, _ in
return false }, openPeer: { _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _ in }, updateMessageReaction: { _ in }, activateMessagePinch: { _ in
return false }, openPeer: { _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _ in }, updateMessageReaction: { _, _ in }, activateMessagePinch: { _ in
}, openMessageContextActions: { _, _, _, _ in }, navigateToMessage: { _, _ in }, navigateToMessageStandalone: { _ in
}, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { fileReference, _, _, _, _, node, rect in return selectStickerImpl?(fileReference, node, rect) ?? false }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _ in return false }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openTheme: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in
}, presentController: { _, _ in

View File

@ -69,7 +69,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
}, openPeer: { _, _, _ in
}, openPeerMention: { _ in
}, openMessageContextMenu: { _, _, _, _, _ in
}, updateMessageReaction: { _ in
}, updateMessageReaction: { _, _ in
}, activateMessagePinch: { _ in
}, openMessageContextActions: { _, _, _, _ in
}, navigateToMessage: { _, _ in

View File

@ -1835,7 +1835,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
let controller = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .extracted(MessageContextExtractedContentSource(sourceNode: node)), items: .single(ContextController.Items(items: items)), recognizer: nil, gesture: gesture)
strongSelf.controller?.window?.presentInGlobalOverlay(controller)
})
}, updateMessageReaction: { _ in
}, updateMessageReaction: { _, _ in
}, activateMessagePinch: { _ in
}, openMessageContextActions: { [weak self] message, node, rect, gesture in
guard let strongSelf = self else {

View File

@ -1239,7 +1239,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
let controllerInteraction: ChatControllerInteraction
controllerInteraction = ChatControllerInteraction(openMessage: { _, _ in
return false }, openPeer: { _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _ in }, updateMessageReaction: { _ in }, activateMessagePinch: { _ in
return false }, openPeer: { _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _ in }, updateMessageReaction: { _, _ in }, activateMessagePinch: { _ in
}, openMessageContextActions: { _, _, _, _ in }, navigateToMessage: { _, _ in }, navigateToMessageStandalone: { _ in
}, tapMessage: { message in
tapMessage?(message)
@ -1307,7 +1307,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
chatLocation = .peer(messages.first!.id.peerId)
}
return ChatMessageItem(presentationData: ChatPresentationData(theme: ChatPresentationThemeData(theme: theme, wallpaper: wallpaper), fontSize: fontSize, strings: strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameOrder, disableAnimations: false, largeEmoji: false, chatBubbleCorners: chatBubbleCorners, animatedEmojiScale: 1.0, isPreview: true), context: context, chatLocation: chatLocation, associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .contact, automaticDownloadNetworkType: .cellular, isRecentActions: false, subject: nil, contactsPeerIds: Set(), animatedEmojiStickers: [:], forcedResourceStatus: forcedResourceStatus), controllerInteraction: controllerInteraction, content: content, disableDate: true, additionalContent: nil)
return ChatMessageItem(presentationData: ChatPresentationData(theme: ChatPresentationThemeData(theme: theme, wallpaper: wallpaper), fontSize: fontSize, strings: strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameOrder, disableAnimations: false, largeEmoji: false, chatBubbleCorners: chatBubbleCorners, animatedEmojiScale: 1.0, isPreview: true), context: context, chatLocation: chatLocation, associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .contact, automaticDownloadNetworkType: .cellular, isRecentActions: false, subject: nil, contactsPeerIds: Set(), animatedEmojiStickers: [:], forcedResourceStatus: forcedResourceStatus, availableReactions: nil), controllerInteraction: controllerInteraction, content: content, disableDate: true, additionalContent: nil)
}
public func makeChatMessageDateHeaderItem(context: AccountContext, timestamp: Int32, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, dateTimeFormat: PresentationDateTimeFormat, nameOrder: PresentationPersonNameOrder) -> ListViewItemHeader {