mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-07-18 01:01:11 +00:00
[WIP] Monoforums
This commit is contained in:
parent
9e18743b1b
commit
f90402102b
@ -204,6 +204,7 @@ private enum ChatListSearchItemHeaderId: Hashable {
|
|||||||
|
|
||||||
public final class ChatListSearchItemHeader: ListViewItemHeader {
|
public final class ChatListSearchItemHeader: ListViewItemHeader {
|
||||||
public let id: ListViewItemNode.HeaderId
|
public let id: ListViewItemNode.HeaderId
|
||||||
|
public let stackingId: ListViewItemNode.HeaderId? = nil
|
||||||
public let type: ChatListSearchItemHeaderType
|
public let type: ChatListSearchItemHeaderType
|
||||||
public let stickDirection: ListViewItemHeaderStickDirection = .top
|
public let stickDirection: ListViewItemHeaderStickDirection = .top
|
||||||
public let stickOverInsets: Bool = true
|
public let stickOverInsets: Bool = true
|
||||||
|
@ -6,6 +6,7 @@ import ListSectionHeaderNode
|
|||||||
|
|
||||||
final class ContactListNameIndexHeader: Equatable, ListViewItemHeader {
|
final class ContactListNameIndexHeader: Equatable, ListViewItemHeader {
|
||||||
let id: ListViewItemNode.HeaderId
|
let id: ListViewItemNode.HeaderId
|
||||||
|
let stackingId: ListViewItemNode.HeaderId? = nil
|
||||||
let theme: PresentationTheme
|
let theme: PresentationTheme
|
||||||
let letter: unichar
|
let letter: unichar
|
||||||
let stickDirection: ListViewItemHeaderStickDirection = .top
|
let stickDirection: ListViewItemHeaderStickDirection = .top
|
||||||
|
@ -9,6 +9,11 @@ private let insertionAnimationDuration: Double = 0.4
|
|||||||
private struct VisibleHeaderNodeId: Hashable {
|
private struct VisibleHeaderNodeId: Hashable {
|
||||||
var id: ListViewItemNode.HeaderId
|
var id: ListViewItemNode.HeaderId
|
||||||
var affinity: Int
|
var affinity: Int
|
||||||
|
|
||||||
|
init(id: ListViewItemNode.HeaderId, affinity: Int) {
|
||||||
|
self.id = id
|
||||||
|
self.affinity = affinity
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class ListViewBackingLayer: CALayer {
|
private final class ListViewBackingLayer: CALayer {
|
||||||
@ -3871,23 +3876,84 @@ open class ListView: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDel
|
|||||||
func addHeader(id: VisibleHeaderNodeId, upperBound: CGFloat, upperIndex: Int, upperBoundEdge: CGFloat, lowerBound: CGFloat, lowerIndex: Int, item: ListViewItemHeader, hasValidNodes: Bool) {
|
func addHeader(id: VisibleHeaderNodeId, upperBound: CGFloat, upperIndex: Int, upperBoundEdge: CGFloat, lowerBound: CGFloat, lowerIndex: Int, item: ListViewItemHeader, hasValidNodes: Bool) {
|
||||||
let itemHeaderHeight: CGFloat = item.height
|
let itemHeaderHeight: CGFloat = item.height
|
||||||
|
|
||||||
let headerFrame: CGRect
|
var insertItemBelowOtherHeaders = false
|
||||||
let stickLocationDistanceFactor: CGFloat
|
var offsetByHeaderNodeId: ListViewItemNode.HeaderId?
|
||||||
let stickLocationDistance: CGFloat
|
var didOffsetByHeaderNode = false
|
||||||
|
|
||||||
|
var headerFrame: CGRect
|
||||||
|
let naturalY: CGFloat
|
||||||
|
var stickLocationDistanceFactor: CGFloat = 0.0
|
||||||
|
var stickLocationDistance: CGFloat
|
||||||
switch item.stickDirection {
|
switch item.stickDirection {
|
||||||
case .top:
|
case .top:
|
||||||
|
naturalY = lowerBound
|
||||||
headerFrame = CGRect(origin: CGPoint(x: 0.0, y: min(max(upperDisplayBound, upperBound), lowerBound - itemHeaderHeight)), size: CGSize(width: self.visibleSize.width, height: itemHeaderHeight))
|
headerFrame = CGRect(origin: CGPoint(x: 0.0, y: min(max(upperDisplayBound, upperBound), lowerBound - itemHeaderHeight)), size: CGSize(width: self.visibleSize.width, height: itemHeaderHeight))
|
||||||
stickLocationDistance = headerFrame.minY - upperBound
|
stickLocationDistance = headerFrame.minY - upperBound
|
||||||
stickLocationDistanceFactor = max(0.0, min(1.0, stickLocationDistance / itemHeaderHeight))
|
stickLocationDistanceFactor = max(0.0, min(1.0, stickLocationDistance / itemHeaderHeight))
|
||||||
case .topEdge:
|
case .topEdge:
|
||||||
|
naturalY = lowerBound
|
||||||
headerFrame = CGRect(origin: CGPoint(x: 0.0, y: min(max(upperDisplayBound, upperBoundEdge - itemHeaderHeight), lowerBound - itemHeaderHeight)), size: CGSize(width: self.visibleSize.width, height: itemHeaderHeight))
|
headerFrame = CGRect(origin: CGPoint(x: 0.0, y: min(max(upperDisplayBound, upperBoundEdge - itemHeaderHeight), lowerBound - itemHeaderHeight)), size: CGSize(width: self.visibleSize.width, height: itemHeaderHeight))
|
||||||
stickLocationDistance = headerFrame.minY - upperBoundEdge + itemHeaderHeight
|
stickLocationDistance = headerFrame.minY - upperBoundEdge + itemHeaderHeight
|
||||||
stickLocationDistanceFactor = max(0.0, min(1.0, stickLocationDistance / itemHeaderHeight))
|
stickLocationDistanceFactor = max(0.0, min(1.0, stickLocationDistance / itemHeaderHeight))
|
||||||
case .bottom:
|
case .bottom:
|
||||||
|
naturalY = lowerBound
|
||||||
headerFrame = CGRect(origin: CGPoint(x: 0.0, y: max(upperBound, min(lowerBound, lowerDisplayBound) - itemHeaderHeight)), size: CGSize(width: self.visibleSize.width, height: itemHeaderHeight))
|
headerFrame = CGRect(origin: CGPoint(x: 0.0, y: max(upperBound, min(lowerBound, lowerDisplayBound) - itemHeaderHeight)), size: CGSize(width: self.visibleSize.width, height: itemHeaderHeight))
|
||||||
stickLocationDistance = lowerBound - headerFrame.maxY
|
stickLocationDistance = lowerBound - headerFrame.maxY
|
||||||
stickLocationDistanceFactor = max(0.0, min(1.0, stickLocationDistance / itemHeaderHeight))
|
stickLocationDistanceFactor = max(0.0, min(1.0, stickLocationDistance / itemHeaderHeight))
|
||||||
|
|
||||||
|
if let stackingId = item.stackingId {
|
||||||
|
insertItemBelowOtherHeaders = true
|
||||||
|
|
||||||
|
var naturalOverlapLowerBound: CGFloat = naturalY
|
||||||
|
do {
|
||||||
|
for (otherId, otherNode) in self.itemHeaderNodes {
|
||||||
|
if otherId.id.space == stackingId.space {
|
||||||
|
if !visibleHeaderNodes.contains(otherId) {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
if let otherNaturalOriginY = otherNode.naturalOriginY, otherNaturalOriginY == naturalY {
|
||||||
|
naturalOverlapLowerBound = otherNaturalOriginY - 7.0 - 20.0
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _ in 0 ..< 2 {
|
||||||
|
var mostOverlap: (CGRect, CGFloat, ListViewItemHeaderNode)?
|
||||||
|
for (otherId, otherNode) in self.itemHeaderNodes {
|
||||||
|
if otherId.id.space == stackingId.space {
|
||||||
|
if !visibleHeaderNodes.contains(otherId) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if headerFrame.intersects(otherNode.frame) {
|
||||||
|
let intersectionHeight = headerFrame.intersection(otherNode.frame).height
|
||||||
|
if intersectionHeight > 0.0 {
|
||||||
|
if let (currentOverlapFrame, _, _) = mostOverlap {
|
||||||
|
if headerFrame.minY < currentOverlapFrame.minY {
|
||||||
|
mostOverlap = (otherNode.frame, intersectionHeight, otherNode)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mostOverlap = (otherNode.frame, intersectionHeight, otherNode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let (mostOverlap, _, otherNode) = mostOverlap {
|
||||||
|
let originalY = headerFrame.origin.y
|
||||||
|
headerFrame.origin.y = min(headerFrame.origin.y, mostOverlap.minY - 7.0 - 20.0)
|
||||||
|
headerFrame.origin.y = max(upperBound, headerFrame.origin.y)
|
||||||
|
offsetByHeaderNodeId = otherNode.item?.id
|
||||||
|
didOffsetByHeaderNode = originalY != headerFrame.origin.y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stickLocationDistance = naturalOverlapLowerBound - headerFrame.maxY
|
||||||
|
stickLocationDistanceFactor = max(0.0, min(1.0, stickLocationDistance / itemHeaderHeight))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
visibleHeaderNodes.append(id)
|
visibleHeaderNodes.append(id)
|
||||||
|
|
||||||
let initialHeaderNodeAlpha = self.itemHeaderNodesAlpha
|
let initialHeaderNodeAlpha = self.itemHeaderNodesAlpha
|
||||||
@ -3896,7 +3962,14 @@ open class ListView: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDel
|
|||||||
headerNode = current
|
headerNode = current
|
||||||
switch transition.0 {
|
switch transition.0 {
|
||||||
case .immediate:
|
case .immediate:
|
||||||
|
let previousFrame = headerNode.frame
|
||||||
headerNode.updateFrame(headerFrame, within: self.visibleSize)
|
headerNode.updateFrame(headerFrame, within: self.visibleSize)
|
||||||
|
if headerNode.offsetByHeaderNodeId != nil && offsetByHeaderNodeId != nil && headerNode.offsetByHeaderNodeId != offsetByHeaderNodeId {
|
||||||
|
let _ = didOffsetByHeaderNode
|
||||||
|
if !previousFrame.isEmpty {
|
||||||
|
ContainedViewLayoutTransition.animated(duration: 0.35, curve: .spring).animatePositionAdditive(node: headerNode, offset: CGPoint(x: 0.0, y: previousFrame.minY - headerFrame.minY))
|
||||||
|
}
|
||||||
|
}
|
||||||
case let .animated(duration, curve):
|
case let .animated(duration, curve):
|
||||||
let previousFrame = headerNode.frame
|
let previousFrame = headerNode.frame
|
||||||
headerNode.updateFrame(headerFrame, within: self.visibleSize)
|
headerNode.updateFrame(headerFrame, within: self.visibleSize)
|
||||||
@ -3927,7 +4000,7 @@ open class ListView: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDel
|
|||||||
item.updateNode(headerNode, previous: nil, next: nil)
|
item.updateNode(headerNode, previous: nil, next: nil)
|
||||||
headerNode.item = item
|
headerNode.item = item
|
||||||
}
|
}
|
||||||
headerNode.updateLayoutInternal(size: headerFrame.size, leftInset: leftInset, rightInset: rightInset, transition: transition.0)
|
headerNode.updateLayoutInternal(size: headerFrame.size, leftInset: leftInset, rightInset: rightInset, transition: animateInsertion ? .immediate : transition.0)
|
||||||
headerNode.updateInternalStickLocationDistanceFactor(stickLocationDistanceFactor, animated: true)
|
headerNode.updateInternalStickLocationDistanceFactor(stickLocationDistanceFactor, animated: true)
|
||||||
headerNode.internalStickLocationDistance = stickLocationDistance
|
headerNode.internalStickLocationDistance = stickLocationDistance
|
||||||
if !hasValidNodes && !headerNode.alpha.isZero {
|
if !hasValidNodes && !headerNode.alpha.isZero {
|
||||||
@ -3940,7 +4013,7 @@ open class ListView: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDel
|
|||||||
headerNode.animateAdded(duration: 0.2)
|
headerNode.animateAdded(duration: 0.2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
headerNode.updateStickDistanceFactor(stickLocationDistanceFactor, transition: transition.0)
|
headerNode.updateStickDistanceFactor(stickLocationDistanceFactor, distance: stickLocationDistance, transition: transition.0)
|
||||||
} else {
|
} else {
|
||||||
headerNode = item.node(synchronousLoad: synchronousLoad)
|
headerNode = item.node(synchronousLoad: synchronousLoad)
|
||||||
headerNode.alpha = initialHeaderNodeAlpha
|
headerNode.alpha = initialHeaderNodeAlpha
|
||||||
@ -3950,10 +4023,28 @@ open class ListView: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDel
|
|||||||
}
|
}
|
||||||
headerNode.updateFlashingOnScrolling(flashing, animated: false)
|
headerNode.updateFlashingOnScrolling(flashing, animated: false)
|
||||||
headerNode.frame = headerFrame
|
headerNode.frame = headerFrame
|
||||||
headerNode.updateLayoutInternal(size: headerFrame.size, leftInset: leftInset, rightInset: rightInset, transition: transition.0)
|
headerNode.updateLayoutInternal(size: headerFrame.size, leftInset: leftInset, rightInset: rightInset, transition: animateInsertion ? .immediate : transition.0)
|
||||||
headerNode.updateInternalStickLocationDistanceFactor(stickLocationDistanceFactor, animated: false)
|
headerNode.updateInternalStickLocationDistanceFactor(stickLocationDistanceFactor, animated: false)
|
||||||
self.itemHeaderNodes[id] = headerNode
|
self.itemHeaderNodes[id] = headerNode
|
||||||
if let verticalScrollIndicator = self.verticalScrollIndicator {
|
if insertItemBelowOtherHeaders {
|
||||||
|
var lowestHeaderNode: ASDisplayNode?
|
||||||
|
var lowestHeaderNodeIndex: Int?
|
||||||
|
for (_, headerNode) in self.itemHeaderNodes {
|
||||||
|
if let index = self.view.subviews.firstIndex(of: headerNode.view) {
|
||||||
|
if lowestHeaderNodeIndex == nil || index < lowestHeaderNodeIndex! {
|
||||||
|
lowestHeaderNodeIndex = index
|
||||||
|
lowestHeaderNode = headerNode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let lowestHeaderNode {
|
||||||
|
self.insertSubnode(headerNode, belowSubnode: lowestHeaderNode)
|
||||||
|
} else if let verticalScrollIndicator = self.verticalScrollIndicator {
|
||||||
|
self.insertSubnode(headerNode, belowSubnode: verticalScrollIndicator)
|
||||||
|
} else {
|
||||||
|
self.addSubnode(headerNode)
|
||||||
|
}
|
||||||
|
} else if let verticalScrollIndicator = self.verticalScrollIndicator {
|
||||||
self.insertSubnode(headerNode, belowSubnode: verticalScrollIndicator)
|
self.insertSubnode(headerNode, belowSubnode: verticalScrollIndicator)
|
||||||
} else {
|
} else {
|
||||||
self.addSubnode(headerNode)
|
self.addSubnode(headerNode)
|
||||||
@ -3962,8 +4053,10 @@ open class ListView: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDel
|
|||||||
headerNode.alpha = initialHeaderNodeAlpha
|
headerNode.alpha = initialHeaderNodeAlpha
|
||||||
headerNode.animateAdded(duration: 0.2)
|
headerNode.animateAdded(duration: 0.2)
|
||||||
}
|
}
|
||||||
headerNode.updateStickDistanceFactor(stickLocationDistanceFactor, transition: .immediate)
|
headerNode.updateStickDistanceFactor(stickLocationDistanceFactor, distance: stickLocationDistance, transition: .immediate)
|
||||||
}
|
}
|
||||||
|
headerNode.offsetByHeaderNodeId = offsetByHeaderNodeId
|
||||||
|
headerNode.naturalOriginY = naturalY
|
||||||
var maxIntersectionHeight: (CGFloat, Int)?
|
var maxIntersectionHeight: (CGFloat, Int)?
|
||||||
for i in upperIndex ... lowerIndex {
|
for i in upperIndex ... lowerIndex {
|
||||||
let itemNode = self.itemNodes[i]
|
let itemNode = self.itemNodes[i]
|
||||||
@ -4008,13 +4101,24 @@ open class ListView: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDel
|
|||||||
|
|
||||||
var previousHeaderBySpace: [AnyHashable: (id: VisibleHeaderNodeId, upperBound: CGFloat, upperBoundIndex: Int, upperBoundEdge: CGFloat, lowerBound: CGFloat, lowerBoundIndex: Int, item: ListViewItemHeader, hasValidNodes: Bool)] = [:]
|
var previousHeaderBySpace: [AnyHashable: (id: VisibleHeaderNodeId, upperBound: CGFloat, upperBoundIndex: Int, upperBoundEdge: CGFloat, lowerBound: CGFloat, lowerBoundIndex: Int, item: ListViewItemHeader, hasValidNodes: Bool)] = [:]
|
||||||
|
|
||||||
|
for phase in 0 ..< 2 {
|
||||||
for i in 0 ..< self.itemNodes.count {
|
for i in 0 ..< self.itemNodes.count {
|
||||||
let itemNode = self.itemNodes[i]
|
let itemNode = self.itemNodes[i]
|
||||||
let itemFrame = itemNode.apparentFrame
|
let itemFrame = itemNode.apparentFrame
|
||||||
let itemTopInset = itemNode.insets.top
|
let itemTopInset = itemNode.insets.top
|
||||||
var validItemHeaderSpaces: [AnyHashable] = []
|
var validItemHeaderSpaces: [AnyHashable] = []
|
||||||
if let itemHeaders = itemNode.headers() {
|
if let itemHeaders = itemNode.headers() {
|
||||||
for itemHeader in itemHeaders {
|
outerItemHeaders: for itemHeader in itemHeaders {
|
||||||
|
if phase == 0 {
|
||||||
|
if itemHeader.stackingId != nil {
|
||||||
|
continue outerItemHeaders
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if itemHeader.stackingId == nil {
|
||||||
|
continue outerItemHeaders
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
guard let affinity = itemNode.headerSpaceAffinities[itemHeader.id] else {
|
guard let affinity = itemNode.headerSpaceAffinities[itemHeader.id] else {
|
||||||
assertionFailure()
|
assertionFailure()
|
||||||
continue
|
continue
|
||||||
@ -4024,7 +4128,7 @@ open class ListView: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDel
|
|||||||
|
|
||||||
validItemHeaderSpaces.append(itemHeader.id.space)
|
validItemHeaderSpaces.append(itemHeader.id.space)
|
||||||
|
|
||||||
let itemMaxY: CGFloat
|
var itemMaxY: CGFloat
|
||||||
if itemHeader.stickOverInsets {
|
if itemHeader.stickOverInsets {
|
||||||
itemMaxY = itemFrame.maxY
|
itemMaxY = itemFrame.maxY
|
||||||
} else {
|
} else {
|
||||||
@ -4057,6 +4161,7 @@ open class ListView: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDel
|
|||||||
previousHeaderBySpace.removeValue(forKey: space)
|
previousHeaderBySpace.removeValue(forKey: space)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (space, previousHeader) in previousHeaderBySpace {
|
for (space, previousHeader) in previousHeaderBySpace {
|
||||||
let (previousHeaderId, previousUpperBound, previousUpperIndex, previousUpperBoundEdge, previousLowerBound, previousLowerIndex, previousHeaderItem, hasValidNodes) = previousHeader
|
let (previousHeaderId, previousUpperBound, previousUpperIndex, previousUpperBoundEdge, previousLowerBound, previousLowerIndex, previousHeaderItem, hasValidNodes) = previousHeader
|
||||||
|
@ -10,6 +10,7 @@ public enum ListViewItemHeaderStickDirection {
|
|||||||
|
|
||||||
public protocol ListViewItemHeader: AnyObject {
|
public protocol ListViewItemHeader: AnyObject {
|
||||||
var id: ListViewItemNode.HeaderId { get }
|
var id: ListViewItemNode.HeaderId { get }
|
||||||
|
var stackingId: ListViewItemNode.HeaderId? { get }
|
||||||
var stickDirection: ListViewItemHeaderStickDirection { get }
|
var stickDirection: ListViewItemHeaderStickDirection { get }
|
||||||
var height: CGFloat { get }
|
var height: CGFloat { get }
|
||||||
var stickOverInsets: Bool { get }
|
var stickOverInsets: Bool { get }
|
||||||
@ -29,6 +30,9 @@ open class ListViewItemHeaderNode: ASDisplayNode {
|
|||||||
private var isFlashingOnScrolling = false
|
private var isFlashingOnScrolling = false
|
||||||
weak var attachedToItemNode: ListViewItemNode?
|
weak var attachedToItemNode: ListViewItemNode?
|
||||||
|
|
||||||
|
var offsetByHeaderNodeId: ListViewItemNode.HeaderId?
|
||||||
|
var naturalOriginY: CGFloat?
|
||||||
|
|
||||||
public var item: ListViewItemHeader?
|
public var item: ListViewItemHeader?
|
||||||
|
|
||||||
func updateInternalStickLocationDistanceFactor(_ factor: CGFloat, animated: Bool) {
|
func updateInternalStickLocationDistanceFactor(_ factor: CGFloat, animated: Bool) {
|
||||||
@ -61,7 +65,7 @@ open class ListViewItemHeaderNode: ASDisplayNode {
|
|||||||
self.isLayerBacked = layerBacked
|
self.isLayerBacked = layerBacked
|
||||||
}
|
}
|
||||||
|
|
||||||
open func updateStickDistanceFactor(_ factor: CGFloat, transition: ContainedViewLayoutTransition) {
|
open func updateStickDistanceFactor(_ factor: CGFloat, distance: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||||
}
|
}
|
||||||
|
|
||||||
final func addScrollingOffset(_ scrollingOffset: CGFloat) {
|
final func addScrollingOffset(_ scrollingOffset: CGFloat) {
|
||||||
|
@ -2045,6 +2045,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
|
|||||||
|
|
||||||
public final class ItemListPeerItemHeader: ListViewItemHeader {
|
public final class ItemListPeerItemHeader: ListViewItemHeader {
|
||||||
public let id: ListViewItemNode.HeaderId
|
public let id: ListViewItemNode.HeaderId
|
||||||
|
public let stackingId: ListViewItemNode.HeaderId? = nil
|
||||||
public let context: AccountContext
|
public let context: AccountContext
|
||||||
public let text: NSAttributedString
|
public let text: NSAttributedString
|
||||||
public let additionalText: String
|
public let additionalText: String
|
||||||
@ -2229,7 +2230,7 @@ public final class ItemListPeerItemHeaderNode: ListViewItemHeaderNode, ItemListH
|
|||||||
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration, removeOnCompletion: true)
|
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration, removeOnCompletion: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
override public func updateStickDistanceFactor(_ factor: CGFloat, transition: ContainedViewLayoutTransition) {
|
override public func updateStickDistanceFactor(_ factor: CGFloat, distance: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||||
if self.stickDistanceFactor == factor {
|
if self.stickDistanceFactor == factor {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -42,6 +42,7 @@ final class ListMessageDateHeader: ListViewItemHeader {
|
|||||||
private let year: Int32
|
private let year: Int32
|
||||||
|
|
||||||
let id: ListViewItemNode.HeaderId
|
let id: ListViewItemNode.HeaderId
|
||||||
|
let stackingId: ListViewItemNode.HeaderId? = nil
|
||||||
let theme: PresentationTheme
|
let theme: PresentationTheme
|
||||||
let strings: PresentationStrings
|
let strings: PresentationStrings
|
||||||
let fontSize: PresentationFontSize
|
let fontSize: PresentationFontSize
|
||||||
|
@ -2776,6 +2776,10 @@ final class MessageHistoryTable: Table {
|
|||||||
associatedThreadInfo = self.seedConfiguration.decodeMessageThreadInfo(data.data)
|
associatedThreadInfo = self.seedConfiguration.decodeMessageThreadInfo(data.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let threadId = message.threadId, let possibleThreadPeer = peerTable.get(PeerId(threadId)) {
|
||||||
|
peers[possibleThreadPeer.id] = possibleThreadPeer
|
||||||
|
}
|
||||||
|
|
||||||
return Message(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, threadId: message.threadId, timestamp: message.timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, customTags: message.customTags, forwardInfo: forwardInfo, author: author, text: message.text, attributes: parsedAttributes, media: parsedMedia, peers: peers, associatedMessages: associatedMessages, associatedMessageIds: associatedMessageIds, associatedMedia: associatedMedia, associatedThreadInfo: associatedThreadInfo, associatedStories: associatedStories)
|
return Message(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, threadId: message.threadId, timestamp: message.timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, customTags: message.customTags, forwardInfo: forwardInfo, author: author, text: message.text, attributes: parsedAttributes, media: parsedMedia, peers: peers, associatedMessages: associatedMessages, associatedMessageIds: associatedMessageIds, associatedMedia: associatedMedia, associatedThreadInfo: associatedThreadInfo, associatedStories: associatedStories)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -498,6 +498,7 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch message {
|
switch message {
|
||||||
case let .message(_, attributes, _, _, threadId, replyToMessageId, _, _, _, _):
|
case let .message(_, attributes, _, _, threadId, replyToMessageId, _, _, _, _):
|
||||||
if let replyToMessageId = replyToMessageId, (replyToMessageId.messageId.peerId != peerId && peerId.namespace == Namespaces.Peer.SecretChat), let replyMessage = transaction.getMessage(replyToMessageId.messageId) {
|
if let replyToMessageId = replyToMessageId, (replyToMessageId.messageId.peerId != peerId && peerId.namespace == Namespaces.Peer.SecretChat), let replyMessage = transaction.getMessage(replyToMessageId.messageId) {
|
||||||
|
@ -583,9 +583,6 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if let inputChannel = maybePeer.flatMap(apiInputChannel) {
|
} else if let inputChannel = maybePeer.flatMap(apiInputChannel) {
|
||||||
if let channel = maybePeer as? TelegramChannel, channel.flags.contains(.isMonoforum) {
|
|
||||||
return .single(false)
|
|
||||||
}
|
|
||||||
let fullChannelSignal = network.request(Api.functions.channels.getFullChannel(channel: inputChannel))
|
let fullChannelSignal = network.request(Api.functions.channels.getFullChannel(channel: inputChannel))
|
||||||
|> map(Optional.init)
|
|> map(Optional.init)
|
||||||
|> `catch` { error -> Signal<Api.messages.ChatFull?, NoError> in
|
|> `catch` { error -> Signal<Api.messages.ChatFull?, NoError> in
|
||||||
@ -594,11 +591,18 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee
|
|||||||
}
|
}
|
||||||
return .single(nil)
|
return .single(nil)
|
||||||
}
|
}
|
||||||
let participantSignal = network.request(Api.functions.channels.getParticipant(channel: inputChannel, participant: .inputPeerSelf))
|
|
||||||
|
|
||||||
|
let participantSignal: Signal<Api.channels.ChannelParticipant?, NoError>
|
||||||
|
if let channel = maybePeer as? TelegramChannel, channel.flags.contains(.isMonoforum) {
|
||||||
|
participantSignal = .single(nil)
|
||||||
|
} else {
|
||||||
|
participantSignal = network.request(Api.functions.channels.getParticipant(channel: inputChannel, participant: .inputPeerSelf))
|
||||||
|> map(Optional.init)
|
|> map(Optional.init)
|
||||||
|> `catch` { error -> Signal<Api.channels.ChannelParticipant?, NoError> in
|
|> `catch` { error -> Signal<Api.channels.ChannelParticipant?, NoError> in
|
||||||
return .single(nil)
|
return .single(nil)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return combineLatest(fullChannelSignal, participantSignal)
|
return combineLatest(fullChannelSignal, participantSignal)
|
||||||
|> mapToSignal { result, participantResult -> Signal<Bool, NoError> in
|
|> mapToSignal { result, participantResult -> Signal<Bool, NoError> in
|
||||||
|
@ -801,7 +801,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override public func asyncLayout() -> (_ item: ChatMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: ChatMessageMerge, _ mergedBottom: ChatMessageMerge, _ dateHeaderAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation, ListViewItemApply, Bool) -> Void) {
|
override public func asyncLayout() -> (_ item: ChatMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: ChatMessageMerge, _ mergedBottom: ChatMessageMerge, _ dateHeaderAtBottom: ChatMessageHeaderSpec) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation, ListViewItemApply, Bool) -> Void) {
|
||||||
var displaySize = CGSize(width: 180.0, height: 180.0)
|
var displaySize = CGSize(width: 180.0, height: 180.0)
|
||||||
let telegramFile = self.telegramFile
|
let telegramFile = self.telegramFile
|
||||||
let emojiFile = self.emojiFile
|
let emojiFile = self.emojiFile
|
||||||
@ -823,7 +823,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
|
|
||||||
let textLayout = TextNodeWithEntities.asyncLayout(self.textNode)
|
let textLayout = TextNodeWithEntities.asyncLayout(self.textNode)
|
||||||
|
|
||||||
func continueAsyncLayout(_ weakSelf: Weak<ChatMessageAnimatedStickerItemNode>, _ item: ChatMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: ChatMessageMerge, _ mergedBottom: ChatMessageMerge, _ dateHeaderAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation, ListViewItemApply, Bool) -> Void) {
|
func continueAsyncLayout(_ weakSelf: Weak<ChatMessageAnimatedStickerItemNode>, _ item: ChatMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: ChatMessageMerge, _ mergedBottom: ChatMessageMerge, _ dateHeaderAtBottom: ChatMessageHeaderSpec) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation, ListViewItemApply, Bool) -> Void) {
|
||||||
let accessibilityData = ChatMessageAccessibilityData(item: item, isSelected: nil)
|
let accessibilityData = ChatMessageAccessibilityData(item: item, isSelected: nil)
|
||||||
let layoutConstants = chatMessageItemLayoutConstants(layoutConstants, params: params, presentationData: item.presentationData)
|
let layoutConstants = chatMessageItemLayoutConstants(layoutConstants, params: params, presentationData: item.presentationData)
|
||||||
let incoming = item.content.effectivelyIncoming(item.context.account.peerId, associatedData: item.associatedData)
|
let incoming = item.content.effectivelyIncoming(item.context.account.peerId, associatedData: item.associatedData)
|
||||||
@ -1005,9 +1005,16 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var layoutInsets = UIEdgeInsets(top: mergedTop.merged ? layoutConstants.bubble.mergedSpacing : layoutConstants.bubble.defaultSpacing, left: 0.0, bottom: mergedBottom.merged ? layoutConstants.bubble.mergedSpacing : layoutConstants.bubble.defaultSpacing, right: 0.0)
|
var layoutInsets = UIEdgeInsets(top: mergedTop.merged ? layoutConstants.bubble.mergedSpacing : layoutConstants.bubble.defaultSpacing, left: 0.0, bottom: mergedBottom.merged ? layoutConstants.bubble.mergedSpacing : layoutConstants.bubble.defaultSpacing, right: 0.0)
|
||||||
if dateHeaderAtBottom {
|
if dateHeaderAtBottom.hasDate && dateHeaderAtBottom.hasTopic {
|
||||||
|
layoutInsets.top += layoutConstants.timestampDateAndTopicHeaderHeight
|
||||||
|
} else {
|
||||||
|
if dateHeaderAtBottom.hasDate {
|
||||||
layoutInsets.top += layoutConstants.timestampHeaderHeight
|
layoutInsets.top += layoutConstants.timestampHeaderHeight
|
||||||
}
|
}
|
||||||
|
if dateHeaderAtBottom.hasTopic {
|
||||||
|
layoutInsets.top += layoutConstants.timestampHeaderHeight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var deliveryFailedInset: CGFloat = 0.0
|
var deliveryFailedInset: CGFloat = 0.0
|
||||||
if isFailed {
|
if isFailed {
|
||||||
@ -1377,6 +1384,8 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
strongSelf.appliedForwardInfo = (forwardSource, forwardAuthorSignature)
|
strongSelf.appliedForwardInfo = (forwardSource, forwardAuthorSignature)
|
||||||
strongSelf.updateAccessibilityData(accessibilityData)
|
strongSelf.updateAccessibilityData(accessibilityData)
|
||||||
|
|
||||||
|
strongSelf.updateAttachedDateHeader(hasDate: dateHeaderAtBottom.hasDate, hasPeer: dateHeaderAtBottom.hasTopic)
|
||||||
|
|
||||||
strongSelf.messageAccessibilityArea.frame = CGRect(origin: CGPoint(), size: layoutSize)
|
strongSelf.messageAccessibilityArea.frame = CGRect(origin: CGPoint(), size: layoutSize)
|
||||||
strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: layoutSize)
|
strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: layoutSize)
|
||||||
strongSelf.contextSourceNode.frame = CGRect(origin: CGPoint(), size: layoutSize)
|
strongSelf.contextSourceNode.frame = CGRect(origin: CGPoint(), size: layoutSize)
|
||||||
@ -1829,7 +1838,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let weakSelf = Weak(self)
|
let weakSelf = Weak(self)
|
||||||
return { (_ item: ChatMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: ChatMessageMerge, _ mergedBottom: ChatMessageMerge, _ dateHeaderAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation, ListViewItemApply, Bool) -> Void) in
|
return { (_ item: ChatMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: ChatMessageMerge, _ mergedBottom: ChatMessageMerge, _ dateHeaderAtBottom: ChatMessageHeaderSpec) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation, ListViewItemApply, Bool) -> Void) in
|
||||||
return continueAsyncLayout(weakSelf, item, params, mergedTop, mergedBottom, dateHeaderAtBottom)
|
return continueAsyncLayout(weakSelf, item, params, mergedTop, mergedBottom, dateHeaderAtBottom)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -728,7 +728,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||||||
|
|
||||||
private var forceStopAnimations: Bool = false
|
private var forceStopAnimations: Bool = false
|
||||||
|
|
||||||
typealias Params = (item: ChatMessageItem, params: ListViewItemLayoutParams, mergedTop: ChatMessageMerge, mergedBottom: ChatMessageMerge, dateHeaderAtBottom: Bool)
|
typealias Params = (item: ChatMessageItem, params: ListViewItemLayoutParams, mergedTop: ChatMessageMerge, mergedBottom: ChatMessageMerge, dateHeaderAtBottom: ChatMessageHeaderSpec)
|
||||||
private var currentInputParams: Params?
|
private var currentInputParams: Params?
|
||||||
private var currentApplyParams: ListViewItemApply?
|
private var currentApplyParams: ListViewItemApply?
|
||||||
|
|
||||||
@ -1394,7 +1394,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override public func asyncLayout() -> (_ item: ChatMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: ChatMessageMerge, _ mergedBottom: ChatMessageMerge, _ dateHeaderAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation, ListViewItemApply, Bool) -> Void) {
|
override public func asyncLayout() -> (_ item: ChatMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: ChatMessageMerge, _ mergedBottom: ChatMessageMerge, _ dateHeaderAtBottom: ChatMessageHeaderSpec) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation, ListViewItemApply, Bool) -> Void) {
|
||||||
var currentContentClassesPropertiesAndLayouts: [(Message, AnyClass, Bool, Int?, (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))))] = []
|
var currentContentClassesPropertiesAndLayouts: [(Message, AnyClass, Bool, Int?, (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))))] = []
|
||||||
for contentNode in self.contentNodes {
|
for contentNode in self.contentNodes {
|
||||||
if let message = contentNode.item?.message {
|
if let message = contentNode.item?.message {
|
||||||
@ -1431,7 +1431,13 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||||||
|
|
||||||
return { item, params, mergedTop, mergedBottom, dateHeaderAtBottom in
|
return { item, params, mergedTop, mergedBottom, dateHeaderAtBottom in
|
||||||
let layoutConstants = chatMessageItemLayoutConstants(layoutConstants, params: params, presentationData: item.presentationData)
|
let layoutConstants = chatMessageItemLayoutConstants(layoutConstants, params: params, presentationData: item.presentationData)
|
||||||
return ChatMessageBubbleItemNode.beginLayout(selfReference: weakSelf, item, params, mergedTop, mergedBottom, dateHeaderAtBottom,
|
return ChatMessageBubbleItemNode.beginLayout(
|
||||||
|
selfReference: weakSelf,
|
||||||
|
item: item,
|
||||||
|
params: params,
|
||||||
|
mergedTop: mergedTop,
|
||||||
|
mergedBottom: mergedBottom,
|
||||||
|
dateHeaderAtBottom: dateHeaderAtBottom,
|
||||||
currentContentClassesPropertiesAndLayouts: currentContentClassesPropertiesAndLayouts,
|
currentContentClassesPropertiesAndLayouts: currentContentClassesPropertiesAndLayouts,
|
||||||
authorNameLayout: authorNameLayout,
|
authorNameLayout: authorNameLayout,
|
||||||
viaMeasureLayout: viaMeasureLayout,
|
viaMeasureLayout: viaMeasureLayout,
|
||||||
@ -1456,11 +1462,11 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||||||
|
|
||||||
private static func beginLayout(
|
private static func beginLayout(
|
||||||
selfReference: Weak<ChatMessageBubbleItemNode>,
|
selfReference: Weak<ChatMessageBubbleItemNode>,
|
||||||
_ item: ChatMessageItem,
|
item: ChatMessageItem,
|
||||||
_ params: ListViewItemLayoutParams,
|
params: ListViewItemLayoutParams,
|
||||||
_ mergedTop: ChatMessageMerge,
|
mergedTop: ChatMessageMerge,
|
||||||
_ mergedBottom: ChatMessageMerge,
|
mergedBottom: ChatMessageMerge,
|
||||||
_ dateHeaderAtBottom: Bool,
|
dateHeaderAtBottom: ChatMessageHeaderSpec,
|
||||||
currentContentClassesPropertiesAndLayouts: [(Message, AnyClass, Bool, Int?, (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))))],
|
currentContentClassesPropertiesAndLayouts: [(Message, AnyClass, Bool, Int?, (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))))],
|
||||||
authorNameLayout: (TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode),
|
authorNameLayout: (TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode),
|
||||||
viaMeasureLayout: (TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode),
|
viaMeasureLayout: (TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode),
|
||||||
@ -2620,7 +2626,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||||||
}
|
}
|
||||||
|
|
||||||
var hasThreadInfo = false
|
var hasThreadInfo = false
|
||||||
if case let .peer(peerId) = item.chatLocation, (peerId == replyMessage?.id.peerId || item.message.threadId == 1 || item.associatedData.isRecentActions), let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, channel.isForumOrMonoForum, item.message.associatedThreadInfo != nil {
|
if case let .peer(peerId) = item.chatLocation, (peerId == replyMessage?.id.peerId || item.message.threadId == 1 || item.associatedData.isRecentActions), let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, channel.isForum, item.message.associatedThreadInfo != nil {
|
||||||
hasThreadInfo = true
|
hasThreadInfo = true
|
||||||
} else if case let .customChatContents(contents) = item.associatedData.subject, case .hashTagSearch = contents.kind {
|
} else if case let .customChatContents(contents) = item.associatedData.subject, case .hashTagSearch = contents.kind {
|
||||||
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .broadcast = channel.info {
|
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .broadcast = channel.info {
|
||||||
@ -3219,12 +3225,19 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||||||
}
|
}
|
||||||
|
|
||||||
var layoutInsets = UIEdgeInsets(top: mergedTop.merged ? layoutConstants.bubble.mergedSpacing : layoutConstants.bubble.defaultSpacing, left: 0.0, bottom: mergedBottom.merged ? layoutConstants.bubble.mergedSpacing : layoutConstants.bubble.defaultSpacing, right: 0.0)
|
var layoutInsets = UIEdgeInsets(top: mergedTop.merged ? layoutConstants.bubble.mergedSpacing : layoutConstants.bubble.defaultSpacing, left: 0.0, bottom: mergedBottom.merged ? layoutConstants.bubble.mergedSpacing : layoutConstants.bubble.defaultSpacing, right: 0.0)
|
||||||
if dateHeaderAtBottom {
|
if dateHeaderAtBottom.hasDate && dateHeaderAtBottom.hasTopic {
|
||||||
|
layoutInsets.top += layoutConstants.timestampDateAndTopicHeaderHeight
|
||||||
|
} else {
|
||||||
|
if dateHeaderAtBottom.hasDate {
|
||||||
|
layoutInsets.top += layoutConstants.timestampHeaderHeight
|
||||||
|
}
|
||||||
|
if dateHeaderAtBottom.hasTopic {
|
||||||
layoutInsets.top += layoutConstants.timestampHeaderHeight
|
layoutInsets.top += layoutConstants.timestampHeaderHeight
|
||||||
}
|
}
|
||||||
if isAd {
|
if isAd {
|
||||||
layoutInsets.top += 4.0
|
layoutInsets.top += 4.0
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let layout = ListViewItemNodeLayout(contentSize: layoutSize, insets: layoutInsets)
|
let layout = ListViewItemNodeLayout(contentSize: layoutSize, insets: layoutInsets)
|
||||||
|
|
||||||
@ -3468,6 +3481,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||||||
strongSelf.updateAttachedAvatarNodeOffset(offset: avatarOffset, transition: .animated(duration: 0.3, curve: .spring))
|
strongSelf.updateAttachedAvatarNodeOffset(offset: avatarOffset, transition: .animated(duration: 0.3, curve: .spring))
|
||||||
}
|
}
|
||||||
strongSelf.updateAttachedAvatarNodeIsHidden(isHidden: isSidePanelOpen, transition: animation.transition)
|
strongSelf.updateAttachedAvatarNodeIsHidden(isHidden: isSidePanelOpen, transition: animation.transition)
|
||||||
|
strongSelf.updateAttachedDateHeader(hasDate: inputParams.dateHeaderAtBottom.hasDate, hasPeer: inputParams.dateHeaderAtBottom.hasTopic)
|
||||||
|
|
||||||
let isFailed = item.content.firstMessage.effectivelyFailed(timestamp: item.context.account.network.getApproximateRemoteTimestamp())
|
let isFailed = item.content.firstMessage.effectivelyFailed(timestamp: item.context.account.network.getApproximateRemoteTimestamp())
|
||||||
if isFailed {
|
if isFailed {
|
||||||
|
@ -277,7 +277,7 @@ public class ChatMessageInstantVideoItemNode: ChatMessageItemView, ASGestureReco
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override public func asyncLayout() -> (_ item: ChatMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: ChatMessageMerge, _ mergedBottom: ChatMessageMerge, _ dateHeaderAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation, ListViewItemApply, Bool) -> Void) {
|
override public func asyncLayout() -> (_ item: ChatMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: ChatMessageMerge, _ mergedBottom: ChatMessageMerge, _ dateHeaderAtBottom: ChatMessageHeaderSpec) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation, ListViewItemApply, Bool) -> Void) {
|
||||||
let layoutConstants = self.layoutConstants
|
let layoutConstants = self.layoutConstants
|
||||||
|
|
||||||
let makeVideoLayout = self.interactiveVideoNode.asyncLayout()
|
let makeVideoLayout = self.interactiveVideoNode.asyncLayout()
|
||||||
@ -296,7 +296,7 @@ public class ChatMessageInstantVideoItemNode: ChatMessageItemView, ASGestureReco
|
|||||||
let currentForwardInfo = self.appliedForwardInfo
|
let currentForwardInfo = self.appliedForwardInfo
|
||||||
let currentPlaying = self.appliedCurrentlyPlaying
|
let currentPlaying = self.appliedCurrentlyPlaying
|
||||||
|
|
||||||
func continueAsyncLayout(_ weakSelf: Weak<ChatMessageInstantVideoItemNode>, _ item: ChatMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: ChatMessageMerge, _ mergedBottom: ChatMessageMerge, _ dateHeaderAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation, ListViewItemApply, Bool) -> Void) {
|
func continueAsyncLayout(_ weakSelf: Weak<ChatMessageInstantVideoItemNode>, _ item: ChatMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: ChatMessageMerge, _ mergedBottom: ChatMessageMerge, _ dateHeaderAtBottom: ChatMessageHeaderSpec) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation, ListViewItemApply, Bool) -> Void) {
|
||||||
let accessibilityData = ChatMessageAccessibilityData(item: item, isSelected: nil)
|
let accessibilityData = ChatMessageAccessibilityData(item: item, isSelected: nil)
|
||||||
|
|
||||||
let layoutConstants = chatMessageItemLayoutConstants(layoutConstants, params: params, presentationData: item.presentationData)
|
let layoutConstants = chatMessageItemLayoutConstants(layoutConstants, params: params, presentationData: item.presentationData)
|
||||||
@ -391,9 +391,16 @@ public class ChatMessageInstantVideoItemNode: ChatMessageItemView, ASGestureReco
|
|||||||
}
|
}
|
||||||
|
|
||||||
var layoutInsets = layoutConstants.instantVideo.insets
|
var layoutInsets = layoutConstants.instantVideo.insets
|
||||||
if dateHeaderAtBottom {
|
if dateHeaderAtBottom.hasDate && dateHeaderAtBottom.hasTopic {
|
||||||
|
layoutInsets.top += layoutConstants.timestampDateAndTopicHeaderHeight
|
||||||
|
} else {
|
||||||
|
if dateHeaderAtBottom.hasDate {
|
||||||
layoutInsets.top += layoutConstants.timestampHeaderHeight
|
layoutInsets.top += layoutConstants.timestampHeaderHeight
|
||||||
}
|
}
|
||||||
|
if dateHeaderAtBottom.hasTopic {
|
||||||
|
layoutInsets.top += layoutConstants.timestampHeaderHeight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var deliveryFailedInset: CGFloat = 0.0
|
var deliveryFailedInset: CGFloat = 0.0
|
||||||
if isFailed {
|
if isFailed {
|
||||||
|
@ -104,6 +104,20 @@ public enum ChatMessageMerge: Int32 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public struct ChatMessageHeaderSpec: Equatable {
|
||||||
|
public var hasDate: Bool
|
||||||
|
public var hasTopic: Bool
|
||||||
|
|
||||||
|
public init(hasDate: Bool, hasTopic: Bool) {
|
||||||
|
self.hasDate = hasDate
|
||||||
|
self.hasTopic = hasTopic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public protocol ChatMessageDateHeaderNode: ListViewItemHeaderNode {
|
||||||
|
func updateItem(hasDate: Bool, hasPeer: Bool)
|
||||||
|
}
|
||||||
|
|
||||||
public protocol ChatMessageAvatarHeaderNode: ListViewItemHeaderNode {
|
public protocol ChatMessageAvatarHeaderNode: ListViewItemHeaderNode {
|
||||||
func updateSelectionState(animated: Bool)
|
func updateSelectionState(animated: Bool)
|
||||||
func updateAvatarIsHidden(isHidden: Bool, transition: ContainedViewLayoutTransition)
|
func updateAvatarIsHidden(isHidden: Bool, transition: ContainedViewLayoutTransition)
|
||||||
@ -128,7 +142,7 @@ public protocol ChatMessageItem: ListViewItem {
|
|||||||
var sending: Bool { get }
|
var sending: Bool { get }
|
||||||
var failed: Bool { get }
|
var failed: Bool { get }
|
||||||
|
|
||||||
func mergedWithItems(top: ListViewItem?, bottom: ListViewItem?, isRotated: Bool) -> (top: ChatMessageMerge, bottom: ChatMessageMerge, dateAtBottom: Bool)
|
func mergedWithItems(top: ListViewItem?, bottom: ListViewItem?, isRotated: Bool) -> (top: ChatMessageMerge, bottom: ChatMessageMerge, dateAtBottom: ChatMessageHeaderSpec)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func hasCommentButton(item: ChatMessageItem) -> Bool {
|
public func hasCommentButton(item: ChatMessageItem) -> Bool {
|
||||||
|
@ -108,6 +108,7 @@ public struct ChatMessageItemWallpaperLayoutConstants {
|
|||||||
public struct ChatMessageItemLayoutConstants {
|
public struct ChatMessageItemLayoutConstants {
|
||||||
public var avatarDiameter: CGFloat
|
public var avatarDiameter: CGFloat
|
||||||
public var timestampHeaderHeight: CGFloat
|
public var timestampHeaderHeight: CGFloat
|
||||||
|
public var timestampDateAndTopicHeaderHeight: CGFloat
|
||||||
|
|
||||||
public var bubble: ChatMessageItemBubbleLayoutConstants
|
public var bubble: ChatMessageItemBubbleLayoutConstants
|
||||||
public var image: ChatMessageItemImageLayoutConstants
|
public var image: ChatMessageItemImageLayoutConstants
|
||||||
@ -117,9 +118,10 @@ public struct ChatMessageItemLayoutConstants {
|
|||||||
public var instantVideo: ChatMessageItemInstantVideoConstants
|
public var instantVideo: ChatMessageItemInstantVideoConstants
|
||||||
public var wallpapers: ChatMessageItemWallpaperLayoutConstants
|
public var wallpapers: ChatMessageItemWallpaperLayoutConstants
|
||||||
|
|
||||||
public init(avatarDiameter: CGFloat, timestampHeaderHeight: CGFloat, bubble: ChatMessageItemBubbleLayoutConstants, image: ChatMessageItemImageLayoutConstants, video: ChatMessageItemVideoLayoutConstants, text: ChatMessageItemTextLayoutConstants, file: ChatMessageItemFileLayoutConstants, instantVideo: ChatMessageItemInstantVideoConstants, wallpapers: ChatMessageItemWallpaperLayoutConstants) {
|
public init(avatarDiameter: CGFloat, timestampHeaderHeight: CGFloat, timestampDateAndTopicHeaderHeight: CGFloat, bubble: ChatMessageItemBubbleLayoutConstants, image: ChatMessageItemImageLayoutConstants, video: ChatMessageItemVideoLayoutConstants, text: ChatMessageItemTextLayoutConstants, file: ChatMessageItemFileLayoutConstants, instantVideo: ChatMessageItemInstantVideoConstants, wallpapers: ChatMessageItemWallpaperLayoutConstants) {
|
||||||
self.avatarDiameter = avatarDiameter
|
self.avatarDiameter = avatarDiameter
|
||||||
self.timestampHeaderHeight = timestampHeaderHeight
|
self.timestampHeaderHeight = timestampHeaderHeight
|
||||||
|
self.timestampDateAndTopicHeaderHeight = timestampDateAndTopicHeaderHeight
|
||||||
self.bubble = bubble
|
self.bubble = bubble
|
||||||
self.image = image
|
self.image = image
|
||||||
self.video = video
|
self.video = video
|
||||||
@ -142,7 +144,7 @@ public struct ChatMessageItemLayoutConstants {
|
|||||||
let instantVideo = ChatMessageItemInstantVideoConstants(insets: UIEdgeInsets(top: 4.0, left: 0.0, bottom: 4.0, right: 0.0), dimensions: CGSize(width: 212.0, height: 212.0))
|
let instantVideo = ChatMessageItemInstantVideoConstants(insets: UIEdgeInsets(top: 4.0, left: 0.0, bottom: 4.0, right: 0.0), dimensions: CGSize(width: 212.0, height: 212.0))
|
||||||
let wallpapers = ChatMessageItemWallpaperLayoutConstants(maxTextWidth: 180.0)
|
let wallpapers = ChatMessageItemWallpaperLayoutConstants(maxTextWidth: 180.0)
|
||||||
|
|
||||||
return ChatMessageItemLayoutConstants(avatarDiameter: 37.0, timestampHeaderHeight: 34.0, bubble: bubble, image: image, video: video, text: text, file: file, instantVideo: instantVideo, wallpapers: wallpapers)
|
return ChatMessageItemLayoutConstants(avatarDiameter: 37.0, timestampHeaderHeight: 34.0, timestampDateAndTopicHeaderHeight: 7.0 * 2.0 + 20.0 * 2.0 + 7.0, bubble: bubble, image: image, video: video, text: text, file: file, instantVideo: instantVideo, wallpapers: wallpapers)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static var regular: ChatMessageItemLayoutConstants {
|
public static var regular: ChatMessageItemLayoutConstants {
|
||||||
@ -154,7 +156,7 @@ public struct ChatMessageItemLayoutConstants {
|
|||||||
let instantVideo = ChatMessageItemInstantVideoConstants(insets: UIEdgeInsets(top: 4.0, left: 0.0, bottom: 4.0, right: 0.0), dimensions: CGSize(width: 240.0, height: 240.0))
|
let instantVideo = ChatMessageItemInstantVideoConstants(insets: UIEdgeInsets(top: 4.0, left: 0.0, bottom: 4.0, right: 0.0), dimensions: CGSize(width: 240.0, height: 240.0))
|
||||||
let wallpapers = ChatMessageItemWallpaperLayoutConstants(maxTextWidth: 180.0)
|
let wallpapers = ChatMessageItemWallpaperLayoutConstants(maxTextWidth: 180.0)
|
||||||
|
|
||||||
return ChatMessageItemLayoutConstants(avatarDiameter: 37.0, timestampHeaderHeight: 34.0, bubble: bubble, image: image, video: video, text: text, file: file, instantVideo: instantVideo, wallpapers: wallpapers)
|
return ChatMessageItemLayoutConstants(avatarDiameter: 37.0, timestampHeaderHeight: 34.0, timestampDateAndTopicHeaderHeight: 7.0 * 2.0 + 20.0 * 2.0 + 7.0, bubble: bubble, image: image, video: video, text: text, file: file, instantVideo: instantVideo, wallpapers: wallpapers)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ import WallpaperBackgroundNode
|
|||||||
import ChatControllerInteraction
|
import ChatControllerInteraction
|
||||||
import AvatarVideoNode
|
import AvatarVideoNode
|
||||||
import ChatMessageItem
|
import ChatMessageItem
|
||||||
|
import AvatarNode
|
||||||
|
|
||||||
private let timezoneOffset: Int32 = {
|
private let timezoneOffset: Int32 = {
|
||||||
let nowTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
|
let nowTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
|
||||||
@ -28,25 +29,55 @@ private let timezoneOffset: Int32 = {
|
|||||||
private let granularity: Int32 = 60 * 60 * 24
|
private let granularity: Int32 = 60 * 60 * 24
|
||||||
|
|
||||||
public final class ChatMessageDateHeader: ListViewItemHeader {
|
public final class ChatMessageDateHeader: ListViewItemHeader {
|
||||||
|
public struct Id: Hashable {
|
||||||
|
public let roundedTimestamp: Int64?
|
||||||
|
public let separableThreadId: Int64?
|
||||||
|
|
||||||
|
public init(roundedTimestamp: Int64?, separableThreadId: Int64?) {
|
||||||
|
self.roundedTimestamp = roundedTimestamp
|
||||||
|
self.separableThreadId = separableThreadId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class PeerData {
|
||||||
|
public let peer: EnginePeer
|
||||||
|
|
||||||
|
public init(peer: EnginePeer) {
|
||||||
|
self.peer = peer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private let timestamp: Int32
|
private let timestamp: Int32
|
||||||
private let roundedTimestamp: Int32
|
private let roundedTimestamp: Int32
|
||||||
private let scheduled: Bool
|
private let scheduled: Bool
|
||||||
|
public let displayPeer: PeerData?
|
||||||
|
|
||||||
public let id: ListViewItemNode.HeaderId
|
public let id: ListViewItemNode.HeaderId
|
||||||
|
public let stackingId: ListViewItemNode.HeaderId?
|
||||||
|
public let idValue: Id
|
||||||
public let presentationData: ChatPresentationData
|
public let presentationData: ChatPresentationData
|
||||||
public let controllerInteraction: ChatControllerInteraction?
|
public let controllerInteraction: ChatControllerInteraction?
|
||||||
public let context: AccountContext
|
public let context: AccountContext
|
||||||
public let action: ((Int32, Bool) -> Void)?
|
public let action: ((Int32, Bool) -> Void)?
|
||||||
|
|
||||||
public init(timestamp: Int32, scheduled: Bool, presentationData: ChatPresentationData, controllerInteraction: ChatControllerInteraction?, context: AccountContext, action: ((Int32, Bool) -> Void)? = nil) {
|
public init(timestamp: Int32, separableThreadId: Int64?, scheduled: Bool, displayPeer: PeerData?, presentationData: ChatPresentationData, controllerInteraction: ChatControllerInteraction?, context: AccountContext, action: ((Int32, Bool) -> Void)? = nil) {
|
||||||
self.timestamp = timestamp
|
self.timestamp = timestamp
|
||||||
self.scheduled = scheduled
|
self.scheduled = scheduled
|
||||||
|
self.displayPeer = displayPeer
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
self.controllerInteraction = controllerInteraction
|
self.controllerInteraction = controllerInteraction
|
||||||
self.context = context
|
self.context = context
|
||||||
self.action = action
|
self.action = action
|
||||||
self.roundedTimestamp = dateHeaderTimestampId(timestamp: timestamp)
|
self.roundedTimestamp = dateHeaderTimestampId(timestamp: timestamp)
|
||||||
self.id = ListViewItemNode.HeaderId(space: 0, id: Int64(self.roundedTimestamp))
|
if let _ = self.displayPeer {
|
||||||
|
self.idValue = ChatMessageDateHeader.Id(roundedTimestamp: 0, separableThreadId: separableThreadId)
|
||||||
|
self.id = ListViewItemNode.HeaderId(space: 3, id: self.idValue)
|
||||||
|
self.stackingId = ListViewItemNode.HeaderId(space: 2, id: ChatMessageDateHeader.Id(roundedTimestamp: Int64(self.roundedTimestamp), separableThreadId: nil))
|
||||||
|
} else {
|
||||||
|
self.idValue = ChatMessageDateHeader.Id(roundedTimestamp: Int64(self.roundedTimestamp), separableThreadId: nil)
|
||||||
|
self.id = ListViewItemNode.HeaderId(space: 2, id: self.idValue)
|
||||||
|
self.stackingId = nil
|
||||||
|
}
|
||||||
|
|
||||||
let isRotated = controllerInteraction?.chatIsRotated ?? true
|
let isRotated = controllerInteraction?.chatIsRotated ?? true
|
||||||
|
|
||||||
@ -67,11 +98,11 @@ public final class ChatMessageDateHeader: ListViewItemHeader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func node(synchronousLoad: Bool) -> ListViewItemHeaderNode {
|
public func node(synchronousLoad: Bool) -> ListViewItemHeaderNode {
|
||||||
return ChatMessageDateHeaderNode(localTimestamp: self.roundedTimestamp, scheduled: self.scheduled, presentationData: self.presentationData, controllerInteraction: self.controllerInteraction, context: self.context, action: self.action)
|
return ChatMessageDateHeaderNodeImpl(localTimestamp: self.roundedTimestamp, scheduled: self.scheduled, displayPeer: self.displayPeer, presentationData: self.presentationData, controllerInteraction: self.controllerInteraction, context: self.context, action: self.action)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func updateNode(_ node: ListViewItemHeaderNode, previous: ListViewItemHeader?, next: ListViewItemHeader?) {
|
public func updateNode(_ node: ListViewItemHeaderNode, previous: ListViewItemHeader?, next: ListViewItemHeader?) {
|
||||||
guard let node = node as? ChatMessageDateHeaderNode, let next = next as? ChatMessageDateHeader else {
|
guard let node = node as? ChatMessageDateHeaderNodeImpl, let next = next as? ChatMessageDateHeader else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
node.updatePresentationData(next.presentationData, context: next.context)
|
node.updatePresentationData(next.presentationData, context: next.context)
|
||||||
@ -119,32 +150,71 @@ private func dateHeaderTimestampId(timestamp: Int32) -> Int32 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class ChatMessageDateHeaderNode: ListViewItemHeaderNode {
|
private final class ChatMessageDateSectionSeparatorNode: ASDisplayNode {
|
||||||
|
private let controllerInteraction: ChatControllerInteraction?
|
||||||
|
private let presentationData: ChatPresentationData
|
||||||
|
|
||||||
|
private let backgroundNode: NavigationBackgroundNode
|
||||||
|
private let patternLayer: SimpleShapeLayer
|
||||||
|
|
||||||
|
init(
|
||||||
|
controllerInteraction: ChatControllerInteraction?,
|
||||||
|
presentationData: ChatPresentationData
|
||||||
|
) {
|
||||||
|
self.controllerInteraction = controllerInteraction
|
||||||
|
self.presentationData = presentationData
|
||||||
|
|
||||||
|
self.backgroundNode = NavigationBackgroundNode(color: .clear)
|
||||||
|
self.backgroundNode.isUserInteractionEnabled = false
|
||||||
|
|
||||||
|
self.patternLayer = SimpleShapeLayer()
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
self.backgroundColor = nil
|
||||||
|
self.isOpaque = false
|
||||||
|
|
||||||
|
self.addSubnode(self.backgroundNode)
|
||||||
|
|
||||||
|
let fullTranslucency: Bool = self.controllerInteraction?.enableFullTranslucency ?? true
|
||||||
|
|
||||||
|
self.backgroundNode.updateColor(color: selectDateFillStaticColor(theme: self.presentationData.theme.theme, wallpaper: self.presentationData.theme.wallpaper), enableBlur: fullTranslucency && dateFillNeedsBlur(theme: self.presentationData.theme.theme, wallpaper: self.presentationData.theme.wallpaper), transition: .immediate)
|
||||||
|
|
||||||
|
self.patternLayer.lineWidth = 1.66
|
||||||
|
self.patternLayer.strokeColor = UIColor.white.cgColor
|
||||||
|
|
||||||
|
let linePath = CGMutablePath()
|
||||||
|
linePath.move(to: CGPoint(x: 0.0, y: self.patternLayer.lineWidth * 0.5))
|
||||||
|
linePath.addLine(to: CGPoint(x: 10000.0, y: self.patternLayer.lineWidth * 0.5))
|
||||||
|
self.patternLayer.path = linePath
|
||||||
|
self.patternLayer.lineDashPattern = [6.0 as NSNumber, 2.0 as NSNumber] as [NSNumber]
|
||||||
|
|
||||||
|
self.backgroundNode.layer.mask = self.patternLayer
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(size: CGSize, transition: ContainedViewLayoutTransition) {
|
||||||
|
let backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: 10.0))
|
||||||
|
transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame)
|
||||||
|
self.backgroundNode.update(size: backgroundFrame.size, transition: transition)
|
||||||
|
|
||||||
|
transition.updateFrame(layer: self.patternLayer, frame: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: 1.66)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class ChatMessageDateContentNode: ASDisplayNode {
|
||||||
|
private var presentationData: ChatPresentationData
|
||||||
|
private let controllerInteraction: ChatControllerInteraction?
|
||||||
|
|
||||||
public let labelNode: TextNode
|
public let labelNode: TextNode
|
||||||
public let backgroundNode: NavigationBackgroundNode
|
public let backgroundNode: NavigationBackgroundNode
|
||||||
public let stickBackgroundNode: ASImageNode
|
public let stickBackgroundNode: ASImageNode
|
||||||
|
|
||||||
private var backgroundContent: WallpaperBubbleBackgroundNode?
|
private var backgroundContent: WallpaperBubbleBackgroundNode?
|
||||||
|
private var text: String
|
||||||
|
|
||||||
private let localTimestamp: Int32
|
init(presentationData: ChatPresentationData, controllerInteraction: ChatControllerInteraction?, localTimestamp: Int32, scheduled: Bool) {
|
||||||
private var presentationData: ChatPresentationData
|
|
||||||
private let controllerInteraction: ChatControllerInteraction?
|
|
||||||
private let context: AccountContext
|
|
||||||
private let text: String
|
|
||||||
|
|
||||||
private var flashingOnScrolling = false
|
|
||||||
private var stickDistanceFactor: CGFloat = 0.0
|
|
||||||
private var action: ((Int32, Bool) -> Void)? = nil
|
|
||||||
|
|
||||||
private var absolutePosition: (CGRect, CGSize)?
|
|
||||||
|
|
||||||
public init(localTimestamp: Int32, scheduled: Bool, presentationData: ChatPresentationData, controllerInteraction: ChatControllerInteraction?, context: AccountContext, action: ((Int32, Bool) -> Void)? = nil) {
|
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
self.controllerInteraction = controllerInteraction
|
self.controllerInteraction = controllerInteraction
|
||||||
self.context = context
|
|
||||||
|
|
||||||
self.localTimestamp = localTimestamp
|
|
||||||
self.action = action
|
|
||||||
|
|
||||||
self.labelNode = TextNode()
|
self.labelNode = TextNode()
|
||||||
self.labelNode.isUserInteractionEnabled = false
|
self.labelNode.isUserInteractionEnabled = false
|
||||||
@ -195,13 +265,7 @@ public final class ChatMessageDateHeaderNode: ListViewItemHeaderNode {
|
|||||||
}
|
}
|
||||||
self.text = text
|
self.text = text
|
||||||
|
|
||||||
let isRotated = controllerInteraction?.chatIsRotated ?? true
|
super.init()
|
||||||
|
|
||||||
super.init(layerBacked: false, dynamicBounce: true, isRotated: isRotated, seeThrough: false)
|
|
||||||
|
|
||||||
if isRotated {
|
|
||||||
self.transform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
let graphics = PresentationResourcesChat.principalGraphics(theme: presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper, bubbleCorners: presentationData.chatBubbleCorners)
|
let graphics = PresentationResourcesChat.principalGraphics(theme: presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper, bubbleCorners: presentationData.chatBubbleCorners)
|
||||||
|
|
||||||
@ -228,13 +292,7 @@ public final class ChatMessageDateHeaderNode: ListViewItemHeaderNode {
|
|||||||
self.labelNode.frame = CGRect(origin: CGPoint(), size: size.size)
|
self.labelNode.frame = CGRect(origin: CGPoint(), size: size.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
override public func didLoad() {
|
func updatePresentationData(_ presentationData: ChatPresentationData, context: AccountContext) {
|
||||||
super.didLoad()
|
|
||||||
|
|
||||||
self.view.addGestureRecognizer(ListViewTapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
|
|
||||||
}
|
|
||||||
|
|
||||||
public func updatePresentationData(_ presentationData: ChatPresentationData, context: AccountContext) {
|
|
||||||
let previousPresentationData = self.presentationData
|
let previousPresentationData = self.presentationData
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
|
|
||||||
@ -256,32 +314,21 @@ public final class ChatMessageDateHeaderNode: ListViewItemHeaderNode {
|
|||||||
if presentationData.fontSize != previousPresentationData.fontSize {
|
if presentationData.fontSize != previousPresentationData.fontSize {
|
||||||
self.labelNode.bounds = CGRect(origin: CGPoint(), size: size.size)
|
self.labelNode.bounds = CGRect(origin: CGPoint(), size: size.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.setNeedsLayout()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func updateBackgroundColor(color: UIColor, enableBlur: Bool) {
|
func updateBackgroundColor(color: UIColor, enableBlur: Bool) {
|
||||||
self.backgroundNode.updateColor(color: color, enableBlur: enableBlur, transition: .immediate)
|
self.backgroundNode.updateColor(color: color, enableBlur: enableBlur, transition: .immediate)
|
||||||
}
|
}
|
||||||
|
|
||||||
override public func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
|
func update(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||||
self.absolutePosition = (rect, containerSize)
|
|
||||||
if let backgroundContent = self.backgroundContent {
|
|
||||||
var backgroundFrame = backgroundContent.frame
|
|
||||||
backgroundFrame.origin.x += rect.minX
|
|
||||||
backgroundFrame.origin.y += containerSize.height - rect.minY
|
|
||||||
backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) {
|
|
||||||
let chatDateSize: CGFloat = 20.0
|
let chatDateSize: CGFloat = 20.0
|
||||||
let chatDateInset: CGFloat = 6.0
|
let chatDateInset: CGFloat = 6.0
|
||||||
|
|
||||||
let labelSize = self.labelNode.bounds.size
|
let labelSize = self.labelNode.bounds.size
|
||||||
let backgroundSize = CGSize(width: labelSize.width + chatDateInset * 2.0, height: chatDateSize)
|
let backgroundSize = CGSize(width: labelSize.width + chatDateInset * 2.0, height: chatDateSize)
|
||||||
|
|
||||||
let backgroundFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - backgroundSize.width) / 2.0), y: (34.0 - chatDateSize) / 2.0), size: backgroundSize)
|
let backgroundFrame = CGRect(origin: CGPoint(x: leftInset + floorToScreenPixels((size.width - leftInset - rightInset - backgroundSize.width) / 2.0), y: (size.height - chatDateSize) / 2.0), size: backgroundSize)
|
||||||
|
|
||||||
transition.updateFrame(node: self.stickBackgroundNode, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size))
|
transition.updateFrame(node: self.stickBackgroundNode, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size))
|
||||||
transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame)
|
transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame)
|
||||||
self.backgroundNode.update(size: backgroundFrame.size, cornerRadius: backgroundFrame.size.height / 2.0, transition: transition)
|
self.backgroundNode.update(size: backgroundFrame.size, cornerRadius: backgroundFrame.size.height / 2.0, transition: transition)
|
||||||
@ -297,18 +344,313 @@ public final class ChatMessageDateHeaderNode: ListViewItemHeaderNode {
|
|||||||
transition.updateFrame(node: backgroundContent, frame: self.backgroundNode.frame)
|
transition.updateFrame(node: backgroundContent, frame: self.backgroundNode.frame)
|
||||||
backgroundContent.cornerRadius = backgroundFrame.size.height / 2.0
|
backgroundContent.cornerRadius = backgroundFrame.size.height / 2.0
|
||||||
|
|
||||||
if let (rect, containerSize) = self.absolutePosition {
|
/*if let (rect, containerSize) = self.absolutePosition {
|
||||||
var backgroundFrame = backgroundContent.frame
|
var backgroundFrame = backgroundContent.frame
|
||||||
backgroundFrame.origin.x += rect.minX
|
backgroundFrame.origin.x += rect.minX
|
||||||
backgroundFrame.origin.y += containerSize.height - rect.minY
|
backgroundFrame.origin.y += containerSize.height - rect.minY
|
||||||
backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: transition)
|
backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: transition)
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateStickDistanceFactor(_ factor: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||||
|
self.stickBackgroundNode.alpha = factor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class ChatMessagePeerContentNode: ASDisplayNode {
|
||||||
|
private let context: AccountContext
|
||||||
|
private var presentationData: ChatPresentationData
|
||||||
|
private let controllerInteraction: ChatControllerInteraction?
|
||||||
|
private let peer: EnginePeer
|
||||||
|
|
||||||
|
private let avatarNode: AvatarNode
|
||||||
|
public let labelNode: TextNode
|
||||||
|
public let backgroundNode: NavigationBackgroundNode
|
||||||
|
public let stickBackgroundNode: ASImageNode
|
||||||
|
|
||||||
|
private var backgroundContent: WallpaperBubbleBackgroundNode?
|
||||||
|
private var text: String
|
||||||
|
|
||||||
|
init(context: AccountContext, presentationData: ChatPresentationData, controllerInteraction: ChatControllerInteraction?, peer: EnginePeer) {
|
||||||
|
self.context = context
|
||||||
|
self.presentationData = presentationData
|
||||||
|
self.controllerInteraction = controllerInteraction
|
||||||
|
self.peer = peer
|
||||||
|
|
||||||
|
self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 8.0))
|
||||||
|
|
||||||
|
self.labelNode = TextNode()
|
||||||
|
self.labelNode.isUserInteractionEnabled = false
|
||||||
|
self.labelNode.displaysAsynchronously = !presentationData.isPreview
|
||||||
|
|
||||||
|
if controllerInteraction?.presentationContext.backgroundNode?.hasExtraBubbleBackground() == true, let backgroundContent = controllerInteraction?.presentationContext.backgroundNode?.makeBubbleBackground(for: .free) {
|
||||||
|
backgroundContent.clipsToBounds = true
|
||||||
|
self.backgroundContent = backgroundContent
|
||||||
|
}
|
||||||
|
|
||||||
|
self.backgroundNode = NavigationBackgroundNode(color: .clear)
|
||||||
|
self.backgroundNode.isUserInteractionEnabled = false
|
||||||
|
|
||||||
|
self.stickBackgroundNode = ASImageNode()
|
||||||
|
self.stickBackgroundNode.isLayerBacked = true
|
||||||
|
self.stickBackgroundNode.displayWithoutProcessing = true
|
||||||
|
self.stickBackgroundNode.displaysAsynchronously = false
|
||||||
|
|
||||||
|
let text = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
||||||
|
self.text = text
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
let graphics = PresentationResourcesChat.principalGraphics(theme: presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper, bubbleCorners: presentationData.chatBubbleCorners)
|
||||||
|
|
||||||
|
let fullTranslucency: Bool = controllerInteraction?.enableFullTranslucency ?? true
|
||||||
|
|
||||||
|
self.backgroundNode.updateColor(color: selectDateFillStaticColor(theme: presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper), enableBlur: fullTranslucency && dateFillNeedsBlur(theme: presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper), transition: .immediate)
|
||||||
|
self.stickBackgroundNode.image = graphics.dateFloatingBackground
|
||||||
|
self.stickBackgroundNode.alpha = 0.0
|
||||||
|
|
||||||
|
if let backgroundContent = self.backgroundContent {
|
||||||
|
self.addSubnode(backgroundContent)
|
||||||
|
} else {
|
||||||
|
self.addSubnode(self.backgroundNode)
|
||||||
|
}
|
||||||
|
self.addSubnode(self.avatarNode)
|
||||||
|
self.addSubnode(self.labelNode)
|
||||||
|
|
||||||
|
let titleFont = Font.medium(min(18.0, floor(presentationData.fontSize.baseDisplaySize * 13.0 / 17.0)))
|
||||||
|
|
||||||
|
let attributedString = NSAttributedString(string: text, font: titleFont, textColor: bubbleVariableColor(variableColor: presentationData.theme.theme.chat.serviceMessage.dateTextColor, wallpaper: presentationData.theme.wallpaper))
|
||||||
|
let labelLayout = TextNode.asyncLayout(self.labelNode)
|
||||||
|
|
||||||
|
let (size, apply) = labelLayout(TextNodeLayoutArguments(attributedString: attributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 320.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||||
|
let _ = apply()
|
||||||
|
self.labelNode.frame = CGRect(origin: CGPoint(), size: size.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updatePresentationData(_ presentationData: ChatPresentationData, context: AccountContext) {
|
||||||
|
let previousPresentationData = self.presentationData
|
||||||
|
self.presentationData = presentationData
|
||||||
|
|
||||||
|
let graphics = PresentationResourcesChat.principalGraphics(theme: presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper, bubbleCorners: presentationData.chatBubbleCorners)
|
||||||
|
|
||||||
|
let fullTranslucency: Bool = self.controllerInteraction?.enableFullTranslucency ?? true
|
||||||
|
|
||||||
|
self.backgroundNode.updateColor(color: selectDateFillStaticColor(theme: presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper), enableBlur: fullTranslucency && dateFillNeedsBlur(theme: presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper), transition: .immediate)
|
||||||
|
self.stickBackgroundNode.image = graphics.dateFloatingBackground
|
||||||
|
|
||||||
|
let titleFont = Font.medium(min(18.0, floor(presentationData.fontSize.baseDisplaySize * 13.0 / 17.0)))
|
||||||
|
|
||||||
|
let attributedString = NSAttributedString(string: self.text, font: titleFont, textColor: bubbleVariableColor(variableColor: presentationData.theme.theme.chat.serviceMessage.dateTextColor, wallpaper: presentationData.theme.wallpaper))
|
||||||
|
let labelLayout = TextNode.asyncLayout(self.labelNode)
|
||||||
|
|
||||||
|
let (size, apply) = labelLayout(TextNodeLayoutArguments(attributedString: attributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 320.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||||
|
let _ = apply()
|
||||||
|
|
||||||
|
if presentationData.fontSize != previousPresentationData.fontSize {
|
||||||
|
self.labelNode.bounds = CGRect(origin: CGPoint(), size: size.size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateBackgroundColor(color: UIColor, enableBlur: Bool) {
|
||||||
|
self.backgroundNode.updateColor(color: color, enableBlur: enableBlur, transition: .immediate)
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||||
|
let chatDateSize: CGFloat = 20.0
|
||||||
|
let chatDateInset: CGFloat = 6.0
|
||||||
|
|
||||||
|
let avatarDiameter: CGFloat = 16.0
|
||||||
|
let avatarInset: CGFloat = 2.0
|
||||||
|
let avatarSpacing: CGFloat = 4.0
|
||||||
|
|
||||||
|
let labelSize = self.labelNode.bounds.size
|
||||||
|
let backgroundSize = CGSize(width: avatarInset + avatarDiameter + avatarSpacing + labelSize.width + chatDateInset, height: chatDateSize)
|
||||||
|
|
||||||
|
let backgroundFrame = CGRect(origin: CGPoint(x: leftInset + floorToScreenPixels((size.width - leftInset - rightInset - backgroundSize.width) / 2.0), y: (size.height - chatDateSize) / 2.0), size: backgroundSize)
|
||||||
|
|
||||||
|
let avatarFrame = CGRect(origin: CGPoint(x: backgroundFrame.minX + avatarInset, y: backgroundFrame.origin.y + floorToScreenPixels((backgroundSize.height - avatarDiameter) / 2.0)), size: CGSize(width: avatarDiameter, height: avatarDiameter))
|
||||||
|
transition.updateFrame(node: self.avatarNode, frame: avatarFrame)
|
||||||
|
self.avatarNode.updateSize(size: avatarFrame.size)
|
||||||
|
|
||||||
|
if self.peer.smallProfileImage != nil {
|
||||||
|
self.avatarNode.setPeerV2(context: self.context, theme: self.presentationData.theme.theme, peer: self.peer, displayDimensions: avatarFrame.size)
|
||||||
|
} else {
|
||||||
|
self.avatarNode.setPeer(context: self.context, theme: self.presentationData.theme.theme, peer: self.peer, displayDimensions: avatarFrame.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
transition.updateFrame(node: self.stickBackgroundNode, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size))
|
||||||
|
transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame)
|
||||||
|
self.backgroundNode.update(size: backgroundFrame.size, cornerRadius: backgroundFrame.size.height / 2.0, transition: transition)
|
||||||
|
let labelFrame = CGRect(origin: CGPoint(x: backgroundFrame.origin.x + avatarInset + avatarDiameter + avatarSpacing, y: backgroundFrame.origin.y + floorToScreenPixels((backgroundSize.height - labelSize.height) / 2.0)), size: labelSize)
|
||||||
|
|
||||||
|
transition.updatePosition(node: self.labelNode, position: labelFrame.center)
|
||||||
|
self.labelNode.bounds = CGRect(origin: CGPoint(), size: labelFrame.size)
|
||||||
|
|
||||||
|
if let backgroundContent = self.backgroundContent {
|
||||||
|
backgroundContent.allowsGroupOpacity = true
|
||||||
|
self.backgroundNode.isHidden = true
|
||||||
|
|
||||||
|
transition.updateFrame(node: backgroundContent, frame: self.backgroundNode.frame)
|
||||||
|
backgroundContent.cornerRadius = backgroundFrame.size.height / 2.0
|
||||||
|
|
||||||
|
/*if let (rect, containerSize) = self.absolutePosition {
|
||||||
|
var backgroundFrame = backgroundContent.frame
|
||||||
|
backgroundFrame.origin.x += rect.minX
|
||||||
|
backgroundFrame.origin.y += containerSize.height - rect.minY
|
||||||
|
backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: transition)
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateStickDistanceFactor(_ factor: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||||
|
self.stickBackgroundNode.alpha = factor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class ChatMessageDateHeaderNodeImpl: ListViewItemHeaderNode, ChatMessageDateHeaderNode {
|
||||||
|
private var dateContentNode: ChatMessageDateContentNode?
|
||||||
|
private var peerContentNode: ChatMessagePeerContentNode?
|
||||||
|
|
||||||
|
private var sectionSeparator: ChatMessageDateSectionSeparatorNode?
|
||||||
|
|
||||||
|
private let context: AccountContext
|
||||||
|
private let localTimestamp: Int32
|
||||||
|
private let scheduled: Bool
|
||||||
|
private let displayPeer: ChatMessageDateHeader.PeerData?
|
||||||
|
private var presentationData: ChatPresentationData
|
||||||
|
private let controllerInteraction: ChatControllerInteraction?
|
||||||
|
|
||||||
|
private var flashingOnScrolling = false
|
||||||
|
private var stickDistanceFactor: CGFloat = 0.0
|
||||||
|
private var action: ((Int32, Bool) -> Void)? = nil
|
||||||
|
|
||||||
|
private var params: (size: CGSize, leftInset: CGFloat, rightInset: CGFloat)?
|
||||||
|
private var absolutePosition: (CGRect, CGSize)?
|
||||||
|
|
||||||
|
public init(localTimestamp: Int32, scheduled: Bool, displayPeer: ChatMessageDateHeader.PeerData?, presentationData: ChatPresentationData, controllerInteraction: ChatControllerInteraction?, context: AccountContext, action: ((Int32, Bool) -> Void)? = nil) {
|
||||||
|
self.context = context
|
||||||
|
self.presentationData = presentationData
|
||||||
|
self.controllerInteraction = controllerInteraction
|
||||||
|
|
||||||
|
self.localTimestamp = localTimestamp
|
||||||
|
self.scheduled = scheduled
|
||||||
|
self.displayPeer = displayPeer
|
||||||
|
self.action = action
|
||||||
|
|
||||||
|
let isRotated = controllerInteraction?.chatIsRotated ?? true
|
||||||
|
|
||||||
|
super.init(layerBacked: false, dynamicBounce: true, isRotated: isRotated, seeThrough: false)
|
||||||
|
|
||||||
|
if isRotated {
|
||||||
|
self.transform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let displayPeer {
|
||||||
|
if self.peerContentNode == nil {
|
||||||
|
let sectionSeparator = ChatMessageDateSectionSeparatorNode(controllerInteraction: controllerInteraction, presentationData: presentationData)
|
||||||
|
self.sectionSeparator = sectionSeparator
|
||||||
|
self.addSubnode(sectionSeparator)
|
||||||
|
|
||||||
|
let peerContentNode = ChatMessagePeerContentNode(context: self.context, presentationData: self.presentationData, controllerInteraction: self.controllerInteraction, peer: displayPeer.peer)
|
||||||
|
self.peerContentNode = peerContentNode
|
||||||
|
self.addSubnode(peerContentNode)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if self.dateContentNode == nil {
|
||||||
|
let dateContentNode = ChatMessageDateContentNode(presentationData: self.presentationData, controllerInteraction: self.controllerInteraction, localTimestamp: self.localTimestamp, scheduled: self.scheduled)
|
||||||
|
self.dateContentNode = dateContentNode
|
||||||
|
self.addSubnode(dateContentNode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override public func updateStickDistanceFactor(_ factor: CGFloat, transition: ContainedViewLayoutTransition) {
|
override public func didLoad() {
|
||||||
|
super.didLoad()
|
||||||
|
|
||||||
|
self.view.addGestureRecognizer(ListViewTapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
|
||||||
|
}
|
||||||
|
|
||||||
|
public func updateItem(hasDate: Bool, hasPeer: Bool) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public func updatePresentationData(_ presentationData: ChatPresentationData, context: AccountContext) {
|
||||||
|
if let dateContentNode = self.dateContentNode {
|
||||||
|
dateContentNode.updatePresentationData(presentationData, context: context)
|
||||||
|
}
|
||||||
|
if let peerContentNode = self.peerContentNode {
|
||||||
|
peerContentNode.updatePresentationData(presentationData, context: context)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.setNeedsLayout()
|
||||||
|
}
|
||||||
|
|
||||||
|
public func updateBackgroundColor(color: UIColor, enableBlur: Bool) {
|
||||||
|
if let dateContentNode = self.dateContentNode {
|
||||||
|
dateContentNode.updateBackgroundColor(color: color, enableBlur: enableBlur)
|
||||||
|
}
|
||||||
|
if let peerContentNode = self.peerContentNode {
|
||||||
|
peerContentNode.updateBackgroundColor(color: color, enableBlur: enableBlur)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override public func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
|
||||||
|
/*self.absolutePosition = (rect, containerSize)
|
||||||
|
if let backgroundContent = self.backgroundContent {
|
||||||
|
var backgroundFrame = backgroundContent.frame
|
||||||
|
backgroundFrame.origin.x += rect.minX
|
||||||
|
backgroundFrame.origin.y += containerSize.height - rect.minY
|
||||||
|
backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate)
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
|
||||||
|
override public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||||
|
self.params = (size, leftInset, rightInset)
|
||||||
|
|
||||||
|
var contentOffsetY: CGFloat = 0.0
|
||||||
|
var isFirst = true
|
||||||
|
if let dateContentNode = self.dateContentNode {
|
||||||
|
if isFirst {
|
||||||
|
isFirst = false
|
||||||
|
contentOffsetY += 7.0
|
||||||
|
} else {
|
||||||
|
contentOffsetY += 7.0
|
||||||
|
}
|
||||||
|
let contentFrame = CGRect(origin: CGPoint(x: 0.0, y: contentOffsetY), size: CGSize(width: size.width, height: 20.0))
|
||||||
|
transition.updateFrame(node: dateContentNode, frame: contentFrame)
|
||||||
|
dateContentNode.update(size: contentFrame.size, leftInset: leftInset, rightInset: rightInset, transition: transition)
|
||||||
|
contentOffsetY += 20.0
|
||||||
|
}
|
||||||
|
if let peerContentNode = self.peerContentNode {
|
||||||
|
if isFirst {
|
||||||
|
isFirst = false
|
||||||
|
contentOffsetY += 7.0
|
||||||
|
} else {
|
||||||
|
contentOffsetY += 7.0
|
||||||
|
}
|
||||||
|
let contentFrame = CGRect(origin: CGPoint(x: 0.0, y: contentOffsetY), size: CGSize(width: size.width, height: 20.0))
|
||||||
|
transition.updateFrame(node: peerContentNode, frame: contentFrame)
|
||||||
|
peerContentNode.update(size: contentFrame.size, leftInset: leftInset, rightInset: rightInset, transition: transition)
|
||||||
|
contentOffsetY += 20.0
|
||||||
|
|
||||||
|
if let sectionSeparator = self.sectionSeparator {
|
||||||
|
let sectionSeparatorFrame = CGRect(origin: CGPoint(x: 0.0, y: contentFrame.minY + floorToScreenPixels((contentFrame.height - 1.66) * 0.5)), size: CGSize(width: size.width, height: 1.66))
|
||||||
|
sectionSeparator.update(size: sectionSeparatorFrame.size, transition: transition)
|
||||||
|
transition.updatePosition(node: sectionSeparator, position: sectionSeparatorFrame.center)
|
||||||
|
transition.updateBounds(node: sectionSeparator, bounds: CGRect(origin: CGPoint(), size: sectionSeparatorFrame.size))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
contentOffsetY += 7.0
|
||||||
|
}
|
||||||
|
|
||||||
|
override public func updateStickDistanceFactor(_ factor: CGFloat, distance: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||||
if !self.stickDistanceFactor.isEqual(to: factor) {
|
if !self.stickDistanceFactor.isEqual(to: factor) {
|
||||||
self.stickBackgroundNode.alpha = factor
|
if let dateContentNode = self.dateContentNode {
|
||||||
|
dateContentNode.updateStickDistanceFactor(factor, transition: transition)
|
||||||
|
}
|
||||||
|
if let peerContentNode = self.peerContentNode {
|
||||||
|
peerContentNode.updateStickDistanceFactor(factor, transition: transition)
|
||||||
|
}
|
||||||
|
|
||||||
let wasZero = self.stickDistanceFactor < 0.5
|
let wasZero = self.stickDistanceFactor < 0.5
|
||||||
let isZero = factor < 0.5
|
let isZero = factor < 0.5
|
||||||
@ -322,6 +664,10 @@ public final class ChatMessageDateHeaderNode: ListViewItemHeaderNode {
|
|||||||
self.updateFlashing(animated: animated)
|
self.updateFlashing(animated: animated)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let sectionSeparator = self.sectionSeparator {
|
||||||
|
transition.updateTransform(node: sectionSeparator, transform: CGAffineTransformMakeTranslation(0.0, -distance))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override public func updateFlashingOnScrolling(_ isFlashingOnScrolling: Bool, animated: Bool) {
|
override public func updateFlashingOnScrolling(_ isFlashingOnScrolling: Bool, animated: Bool) {
|
||||||
@ -333,35 +679,83 @@ public final class ChatMessageDateHeaderNode: ListViewItemHeaderNode {
|
|||||||
let flashing = self.flashingOnScrolling || self.stickDistanceFactor < 0.5
|
let flashing = self.flashingOnScrolling || self.stickDistanceFactor < 0.5
|
||||||
|
|
||||||
let alpha: CGFloat = flashing ? 1.0 : 0.0
|
let alpha: CGFloat = flashing ? 1.0 : 0.0
|
||||||
let previousAlpha = self.backgroundNode.alpha
|
|
||||||
|
if let dateContentNode = self.dateContentNode {
|
||||||
|
let previousAlpha = dateContentNode.alpha
|
||||||
|
|
||||||
if !previousAlpha.isEqual(to: alpha) {
|
if !previousAlpha.isEqual(to: alpha) {
|
||||||
self.backgroundContent?.alpha = alpha
|
dateContentNode.alpha = alpha
|
||||||
self.backgroundNode.alpha = alpha
|
|
||||||
self.labelNode.alpha = alpha
|
|
||||||
if animated {
|
if animated {
|
||||||
let duration: Double = flashing ? 0.3 : 0.4
|
let duration: Double = flashing ? 0.3 : 0.4
|
||||||
self.backgroundContent?.layer.animateAlpha(from: previousAlpha, to: alpha, duration: duration)
|
dateContentNode.layer.animateAlpha(from: previousAlpha, to: alpha, duration: duration)
|
||||||
self.backgroundNode.layer.animateAlpha(from: previousAlpha, to: alpha, duration: duration)
|
|
||||||
self.labelNode.layer.animateAlpha(from: previousAlpha, to: alpha, duration: duration)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let peerContentNode = self.peerContentNode {
|
||||||
|
let previousAlpha = peerContentNode.alpha
|
||||||
|
|
||||||
|
if !previousAlpha.isEqual(to: alpha) {
|
||||||
|
peerContentNode.alpha = alpha
|
||||||
|
if animated {
|
||||||
|
let duration: Double = flashing ? 0.3 : 0.4
|
||||||
|
peerContentNode.layer.animateAlpha(from: previousAlpha, to: alpha, duration: duration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override public func animateRemoved(duration: Double) {
|
||||||
|
self.alpha = 0.0
|
||||||
|
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration, removeOnCompletion: false)
|
||||||
|
if self.dateContentNode != nil {
|
||||||
|
self.layer.animateScale(from: 1.0, to: 0.2, duration: duration, removeOnCompletion: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override public func animateAdded(duration: Double) {
|
||||||
|
self.layer.animateAlpha(from: 0.0, to: self.alpha, duration: 0.2)
|
||||||
|
if self.dateContentNode != nil {
|
||||||
|
self.layer.animateScale(from: 0.2, to: 1.0, duration: 0.2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override public func getEffectiveAlpha() -> CGFloat {
|
override public func getEffectiveAlpha() -> CGFloat {
|
||||||
return self.backgroundNode.alpha
|
if let dateContentNode = self.dateContentNode {
|
||||||
|
return dateContentNode.alpha
|
||||||
|
}
|
||||||
|
if let peerContentNode = self.peerContentNode {
|
||||||
|
return peerContentNode.alpha
|
||||||
|
}
|
||||||
|
return 0.0
|
||||||
}
|
}
|
||||||
|
|
||||||
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||||
if !self.bounds.contains(point) {
|
if !self.bounds.contains(point) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if self.labelNode.alpha.isZero {
|
if let dateContentNode = self.dateContentNode {
|
||||||
|
if dateContentNode.frame.contains(point) {
|
||||||
|
if dateContentNode.alpha.isZero {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if self.backgroundNode.frame.contains(point) {
|
|
||||||
|
if dateContentNode.backgroundNode.frame.contains(point.offsetBy(dx: -dateContentNode.frame.minX, dy: -dateContentNode.frame.minY)) {
|
||||||
return self.view
|
return self.view
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let peerContentNode = self.peerContentNode {
|
||||||
|
if peerContentNode.frame.contains(point) {
|
||||||
|
if peerContentNode.alpha.isZero {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if peerContentNode.backgroundNode.frame.contains(point.offsetBy(dx: -peerContentNode.frame.minX, dy: -peerContentNode.frame.minY)) {
|
||||||
|
return self.view
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -383,6 +777,7 @@ public final class ChatMessageAvatarHeader: ListViewItemHeader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public let id: ListViewItemNode.HeaderId
|
public let id: ListViewItemNode.HeaderId
|
||||||
|
public let stackingId: ListViewItemNode.HeaderId? = nil
|
||||||
public let peerId: PeerId
|
public let peerId: PeerId
|
||||||
public let peer: Peer?
|
public let peer: Peer?
|
||||||
public let messageReference: MessageReference?
|
public let messageReference: MessageReference?
|
||||||
@ -740,7 +1135,7 @@ public final class ChatMessageAvatarHeaderNodeImpl: ListViewItemHeaderNode, Chat
|
|||||||
self.avatarNode.layer.animateScale(from: 0.2, to: 1.0, duration: 0.2)
|
self.avatarNode.layer.animateScale(from: 0.2, to: 1.0, duration: 0.2)
|
||||||
}
|
}
|
||||||
|
|
||||||
override public func updateStickDistanceFactor(_ factor: CGFloat, transition: ContainedViewLayoutTransition) {
|
override public func updateStickDistanceFactor(_ factor: CGFloat, distance: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||||
}
|
}
|
||||||
|
|
||||||
override public func updateFlashingOnScrolling(_ isFlashingOnScrolling: Bool, animated: Bool) {
|
override public func updateFlashingOnScrolling(_ isFlashingOnScrolling: Bool, animated: Bool) {
|
||||||
|
@ -220,6 +220,7 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible
|
|||||||
public let additionalContent: ChatMessageItemAdditionalContent?
|
public let additionalContent: ChatMessageItemAdditionalContent?
|
||||||
|
|
||||||
let dateHeader: ChatMessageDateHeader
|
let dateHeader: ChatMessageDateHeader
|
||||||
|
let topicHeader: ChatMessageDateHeader?
|
||||||
let avatarHeader: ChatMessageAvatarHeader?
|
let avatarHeader: ChatMessageAvatarHeader?
|
||||||
|
|
||||||
public let headers: [ListViewItemHeader]
|
public let headers: [ListViewItemHeader]
|
||||||
@ -286,6 +287,8 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible
|
|||||||
var displayAuthorInfo: Bool
|
var displayAuthorInfo: Bool
|
||||||
|
|
||||||
let messagePeerId: PeerId = chatLocation.peerId ?? content.firstMessage.id.peerId
|
let messagePeerId: PeerId = chatLocation.peerId ?? content.firstMessage.id.peerId
|
||||||
|
var headerSeparableThreadId: Int64?
|
||||||
|
var headerDisplayPeer: ChatMessageDateHeader.PeerData?
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let peerId = messagePeerId
|
let peerId = messagePeerId
|
||||||
@ -320,6 +323,12 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible
|
|||||||
if let channel = content.firstMessage.peers[content.firstMessage.id.peerId] as? TelegramChannel, channel.isMonoForum {
|
if let channel = content.firstMessage.peers[content.firstMessage.id.peerId] as? TelegramChannel, channel.isMonoForum {
|
||||||
if case .replyThread = chatLocation {
|
if case .replyThread = chatLocation {
|
||||||
displayAuthorInfo = false
|
displayAuthorInfo = false
|
||||||
|
} else {
|
||||||
|
headerSeparableThreadId = content.firstMessage.threadId
|
||||||
|
|
||||||
|
if let threadId = content.firstMessage.threadId, let peer = content.firstMessage.peers[EnginePeer.Id(threadId)] {
|
||||||
|
headerDisplayPeer = ChatMessageDateHeader.PeerData(peer: EnginePeer(peer))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -332,7 +341,7 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible
|
|||||||
isScheduledMessages = true
|
isScheduledMessages = true
|
||||||
}
|
}
|
||||||
|
|
||||||
self.dateHeader = ChatMessageDateHeader(timestamp: content.index.timestamp, scheduled: isScheduledMessages, presentationData: presentationData, controllerInteraction: controllerInteraction, context: context, action: { timestamp, alreadyThere in
|
self.dateHeader = ChatMessageDateHeader(timestamp: content.index.timestamp, separableThreadId: nil, scheduled: isScheduledMessages, displayPeer: nil, presentationData: presentationData, controllerInteraction: controllerInteraction, context: context, action: { timestamp, alreadyThere in
|
||||||
var calendar = NSCalendar.current
|
var calendar = NSCalendar.current
|
||||||
calendar.timeZone = TimeZone(abbreviation: "UTC")!
|
calendar.timeZone = TimeZone(abbreviation: "UTC")!
|
||||||
let date = Date(timeIntervalSince1970: TimeInterval(timestamp))
|
let date = Date(timeIntervalSince1970: TimeInterval(timestamp))
|
||||||
@ -343,6 +352,14 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if let headerSeparableThreadId, let headerDisplayPeer {
|
||||||
|
self.topicHeader = ChatMessageDateHeader(timestamp: content.index.timestamp, separableThreadId: headerSeparableThreadId, scheduled: false, displayPeer: headerDisplayPeer, presentationData: presentationData, controllerInteraction: controllerInteraction, context: context, action: { _, _ in
|
||||||
|
controllerInteraction.updateChatLocationThread(headerSeparableThreadId)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
self.topicHeader = nil
|
||||||
|
}
|
||||||
|
|
||||||
if displayAuthorInfo {
|
if displayAuthorInfo {
|
||||||
let message = content.firstMessage
|
let message = content.firstMessage
|
||||||
var hasActionMedia = false
|
var hasActionMedia = false
|
||||||
@ -395,6 +412,9 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible
|
|||||||
var headers: [ListViewItemHeader] = []
|
var headers: [ListViewItemHeader] = []
|
||||||
if !self.disableDate {
|
if !self.disableDate {
|
||||||
headers.append(self.dateHeader)
|
headers.append(self.dateHeader)
|
||||||
|
if let topicHeader = self.topicHeader {
|
||||||
|
headers.append(topicHeader)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if case .messageOptions = associatedData.subject {
|
if case .messageOptions = associatedData.subject {
|
||||||
headers = []
|
headers = []
|
||||||
@ -499,7 +519,7 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (layout, apply) = nodeLayout(self, params, top, bottom, dateAtBottom && !disableDate)
|
let (layout, apply) = nodeLayout(self, params, top, bottom, disableDate ? ChatMessageHeaderSpec(hasDate: false, hasTopic: false) : dateAtBottom)
|
||||||
|
|
||||||
node.contentSize = layout.contentSize
|
node.contentSize = layout.contentSize
|
||||||
node.insets = layout.insets
|
node.insets = layout.insets
|
||||||
@ -526,7 +546,7 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func mergedWithItems(top: ListViewItem?, bottom: ListViewItem?, isRotated: Bool) -> (top: ChatMessageMerge, bottom: ChatMessageMerge, dateAtBottom: Bool) {
|
public func mergedWithItems(top: ListViewItem?, bottom: ListViewItem?, isRotated: Bool) -> (top: ChatMessageMerge, bottom: ChatMessageMerge, dateAtBottom: ChatMessageHeaderSpec) {
|
||||||
var top = top
|
var top = top
|
||||||
var bottom = bottom
|
var bottom = bottom
|
||||||
if !isRotated {
|
if !isRotated {
|
||||||
@ -537,7 +557,7 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible
|
|||||||
|
|
||||||
var mergedTop: ChatMessageMerge = .none
|
var mergedTop: ChatMessageMerge = .none
|
||||||
var mergedBottom: ChatMessageMerge = .none
|
var mergedBottom: ChatMessageMerge = .none
|
||||||
var dateAtBottom = false
|
var dateAtBottom = ChatMessageHeaderSpec(hasDate: false, hasTopic: false)
|
||||||
if let top = top as? ChatMessageItemImpl {
|
if let top = top as? ChatMessageItemImpl {
|
||||||
if top.dateHeader.id != self.dateHeader.id {
|
if top.dateHeader.id != self.dateHeader.id {
|
||||||
mergedBottom = .none
|
mergedBottom = .none
|
||||||
@ -548,20 +568,35 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible
|
|||||||
if let bottom = bottom as? ChatMessageItemImpl {
|
if let bottom = bottom as? ChatMessageItemImpl {
|
||||||
if bottom.dateHeader.id != self.dateHeader.id {
|
if bottom.dateHeader.id != self.dateHeader.id {
|
||||||
mergedTop = .none
|
mergedTop = .none
|
||||||
dateAtBottom = true
|
dateAtBottom.hasDate = true
|
||||||
} else {
|
}
|
||||||
|
if let topicHeader = self.topicHeader, bottom.topicHeader?.id != topicHeader.id {
|
||||||
|
mergedTop = .none
|
||||||
|
dateAtBottom.hasTopic = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !(dateAtBottom.hasDate || dateAtBottom.hasTopic) {
|
||||||
mergedTop = messagesShouldBeMerged(accountPeerId: self.context.account.peerId, bottom.message, message)
|
mergedTop = messagesShouldBeMerged(accountPeerId: self.context.account.peerId, bottom.message, message)
|
||||||
}
|
}
|
||||||
} else if let bottom = bottom as? ChatUnreadItem {
|
} else if let bottom = bottom as? ChatUnreadItem {
|
||||||
if bottom.header.id != self.dateHeader.id {
|
if bottom.header.id != self.dateHeader.id {
|
||||||
dateAtBottom = true
|
dateAtBottom.hasDate = true
|
||||||
|
}
|
||||||
|
if self.topicHeader != nil {
|
||||||
|
dateAtBottom.hasTopic = true
|
||||||
}
|
}
|
||||||
} else if let bottom = bottom as? ChatReplyCountItem {
|
} else if let bottom = bottom as? ChatReplyCountItem {
|
||||||
if bottom.header.id != self.dateHeader.id {
|
if bottom.header.id != self.dateHeader.id {
|
||||||
dateAtBottom = true
|
dateAtBottom.hasDate = true
|
||||||
|
}
|
||||||
|
if self.topicHeader != nil {
|
||||||
|
dateAtBottom.hasTopic = true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
dateAtBottom = true
|
dateAtBottom.hasDate = true
|
||||||
|
if self.topicHeader != nil {
|
||||||
|
dateAtBottom.hasTopic = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (mergedTop, mergedBottom, dateAtBottom)
|
return (mergedTop, mergedBottom, dateAtBottom)
|
||||||
@ -589,7 +624,7 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (layout, apply) = nodeLayout(self, params, top, bottom, dateAtBottom && !disableDate)
|
let (layout, apply) = nodeLayout(self, params, top, bottom, disableDate ? ChatMessageHeaderSpec(hasDate: false, hasTopic: false) : dateAtBottom)
|
||||||
Queue.mainQueue().async {
|
Queue.mainQueue().async {
|
||||||
completion(layout, { info in
|
completion(layout, { info in
|
||||||
apply(animation, info, false)
|
apply(animation, info, false)
|
||||||
|
@ -25,7 +25,7 @@ public class ChatReplyCountItem: ListViewItem {
|
|||||||
self.isComments = isComments
|
self.isComments = isComments
|
||||||
self.count = count
|
self.count = count
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
self.header = ChatMessageDateHeader(timestamp: index.timestamp, scheduled: false, presentationData: presentationData, controllerInteraction: controllerInteraction, context: context)
|
self.header = ChatMessageDateHeader(timestamp: index.timestamp, separableThreadId: nil, scheduled: false, displayPeer: nil, presentationData: presentationData, controllerInteraction: controllerInteraction, context: context)
|
||||||
self.controllerInteraction = controllerInteraction
|
self.controllerInteraction = controllerInteraction
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ public class ChatUnreadItem: ListViewItem {
|
|||||||
self.index = index
|
self.index = index
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
self.controllerInteraction = controllerInteraction
|
self.controllerInteraction = controllerInteraction
|
||||||
self.header = ChatMessageDateHeader(timestamp: index.timestamp, scheduled: false, presentationData: presentationData, controllerInteraction: controllerInteraction, context: context)
|
self.header = ChatMessageDateHeader(timestamp: index.timestamp, separableThreadId: nil, scheduled: false, displayPeer: nil, presentationData: presentationData, controllerInteraction: controllerInteraction, context: context)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
||||||
|
@ -716,7 +716,7 @@ open class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open func asyncLayout() -> (_ item: ChatMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: ChatMessageMerge, _ mergedBottom: ChatMessageMerge, _ dateHeaderAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation, ListViewItemApply, Bool) -> Void) {
|
open func asyncLayout() -> (_ item: ChatMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: ChatMessageMerge, _ mergedBottom: ChatMessageMerge, _ dateHeaderAtBottom: ChatMessageHeaderSpec) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation, ListViewItemApply, Bool) -> Void) {
|
||||||
return { _, _, _, _, _ in
|
return { _, _, _, _, _ in
|
||||||
return (ListViewItemNodeLayout(contentSize: CGSize(width: 32.0, height: 32.0), insets: UIEdgeInsets()), { _, _, _ in
|
return (ListViewItemNodeLayout(contentSize: CGSize(width: 32.0, height: 32.0), insets: UIEdgeInsets()), { _, _, _ in
|
||||||
|
|
||||||
@ -903,6 +903,8 @@ open class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol {
|
|||||||
private var attachedAvatarNodeOffset: CGFloat = 0.0
|
private var attachedAvatarNodeOffset: CGFloat = 0.0
|
||||||
private var attachedAvatarNodeIsHidden: Bool = false
|
private var attachedAvatarNodeIsHidden: Bool = false
|
||||||
|
|
||||||
|
private var attachedDateHeader: (hasDate: Bool, hasPeer: Bool) = (false, false)
|
||||||
|
|
||||||
override open func attachedHeaderNodesUpdated() {
|
override open func attachedHeaderNodesUpdated() {
|
||||||
if !self.attachedAvatarNodeOffset.isZero {
|
if !self.attachedAvatarNodeOffset.isZero {
|
||||||
self.updateAttachedAvatarNodeOffset(offset: self.attachedAvatarNodeOffset, transition: .immediate)
|
self.updateAttachedAvatarNodeOffset(offset: self.attachedAvatarNodeOffset, transition: .immediate)
|
||||||
@ -919,6 +921,12 @@ open class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol {
|
|||||||
headerNode.updateAvatarIsHidden(isHidden: self.attachedAvatarNodeIsHidden, transition: .immediate)
|
headerNode.updateAvatarIsHidden(isHidden: self.attachedAvatarNodeIsHidden, transition: .immediate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for headerNode in self.attachedHeaderNodes {
|
||||||
|
if let headerNode = headerNode as? ChatMessageDateHeaderNode {
|
||||||
|
headerNode.updateItem(hasDate: self.attachedDateHeader.hasDate, hasPeer: self.attachedDateHeader.hasPeer)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open func updateAttachedAvatarNodeOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) {
|
open func updateAttachedAvatarNodeOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||||
@ -939,6 +947,15 @@ open class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open func updateAttachedDateHeader(hasDate: Bool, hasPeer: Bool) {
|
||||||
|
self.attachedDateHeader = (hasDate, hasPeer)
|
||||||
|
for headerNode in self.attachedHeaderNodes {
|
||||||
|
if let headerNode = headerNode as? ChatMessageDateHeaderNode {
|
||||||
|
headerNode.updateItem(hasDate: hasDate, hasPeer: hasPeer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
open func unreadMessageRangeUpdated() {
|
open func unreadMessageRangeUpdated() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -417,7 +417,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override public func asyncLayout() -> (_ item: ChatMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: ChatMessageMerge, _ mergedBottom: ChatMessageMerge, _ dateHeaderAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation, ListViewItemApply, Bool) -> Void) {
|
override public func asyncLayout() -> (_ item: ChatMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: ChatMessageMerge, _ mergedBottom: ChatMessageMerge, _ dateHeaderAtBottom: ChatMessageHeaderSpec) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation, ListViewItemApply, Bool) -> Void) {
|
||||||
let displaySize = CGSize(width: 184.0, height: 184.0)
|
let displaySize = CGSize(width: 184.0, height: 184.0)
|
||||||
let telegramFile = self.telegramFile
|
let telegramFile = self.telegramFile
|
||||||
let layoutConstants = self.layoutConstants
|
let layoutConstants = self.layoutConstants
|
||||||
@ -435,7 +435,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
|
|||||||
let currentShareButtonNode = self.shareButtonNode
|
let currentShareButtonNode = self.shareButtonNode
|
||||||
let currentForwardInfo = self.appliedForwardInfo
|
let currentForwardInfo = self.appliedForwardInfo
|
||||||
|
|
||||||
func continueAsyncLayout(_ weakSelf: Weak<ChatMessageStickerItemNode>, _ item: ChatMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: ChatMessageMerge, _ mergedBottom: ChatMessageMerge, _ dateHeaderAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation, ListViewItemApply, Bool) -> Void) {
|
func continueAsyncLayout(_ weakSelf: Weak<ChatMessageStickerItemNode>, _ item: ChatMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: ChatMessageMerge, _ mergedBottom: ChatMessageMerge, _ dateHeaderAtBottom: ChatMessageHeaderSpec) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation, ListViewItemApply, Bool) -> Void) {
|
||||||
let accessibilityData = ChatMessageAccessibilityData(item: item, isSelected: nil)
|
let accessibilityData = ChatMessageAccessibilityData(item: item, isSelected: nil)
|
||||||
|
|
||||||
let layoutConstants = chatMessageItemLayoutConstants(layoutConstants, params: params, presentationData: item.presentationData)
|
let layoutConstants = chatMessageItemLayoutConstants(layoutConstants, params: params, presentationData: item.presentationData)
|
||||||
@ -567,9 +567,16 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var layoutInsets = UIEdgeInsets(top: mergedTop.merged ? layoutConstants.bubble.mergedSpacing : layoutConstants.bubble.defaultSpacing, left: 0.0, bottom: mergedBottom.merged ? layoutConstants.bubble.mergedSpacing : layoutConstants.bubble.defaultSpacing, right: 0.0)
|
var layoutInsets = UIEdgeInsets(top: mergedTop.merged ? layoutConstants.bubble.mergedSpacing : layoutConstants.bubble.defaultSpacing, left: 0.0, bottom: mergedBottom.merged ? layoutConstants.bubble.mergedSpacing : layoutConstants.bubble.defaultSpacing, right: 0.0)
|
||||||
if dateHeaderAtBottom {
|
if dateHeaderAtBottom.hasDate && dateHeaderAtBottom.hasTopic {
|
||||||
|
layoutInsets.top += layoutConstants.timestampDateAndTopicHeaderHeight
|
||||||
|
} else {
|
||||||
|
if dateHeaderAtBottom.hasDate {
|
||||||
layoutInsets.top += layoutConstants.timestampHeaderHeight
|
layoutInsets.top += layoutConstants.timestampHeaderHeight
|
||||||
}
|
}
|
||||||
|
if dateHeaderAtBottom.hasTopic {
|
||||||
|
layoutInsets.top += layoutConstants.timestampHeaderHeight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var deliveryFailedInset: CGFloat = 0.0
|
var deliveryFailedInset: CGFloat = 0.0
|
||||||
if isFailed {
|
if isFailed {
|
||||||
@ -964,6 +971,8 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
|
|||||||
strongSelf.appliedForwardInfo = (forwardSource, forwardAuthorSignature)
|
strongSelf.appliedForwardInfo = (forwardSource, forwardAuthorSignature)
|
||||||
strongSelf.updateAccessibilityData(accessibilityData)
|
strongSelf.updateAccessibilityData(accessibilityData)
|
||||||
|
|
||||||
|
strongSelf.updateAttachedDateHeader(hasDate: dateHeaderAtBottom.hasDate, hasPeer: dateHeaderAtBottom.hasTopic)
|
||||||
|
|
||||||
transition.updateFrame(node: strongSelf.imageNode, frame: updatedImageFrame)
|
transition.updateFrame(node: strongSelf.imageNode, frame: updatedImageFrame)
|
||||||
strongSelf.enableSynchronousImageApply = true
|
strongSelf.enableSynchronousImageApply = true
|
||||||
imageApply()
|
imageApply()
|
||||||
|
@ -734,7 +734,21 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
spoilerTextColor: messageTheme.primaryTextColor,
|
spoilerTextColor: messageTheme.primaryTextColor,
|
||||||
spoilerEffectColor: messageTheme.secondaryTextColor,
|
spoilerEffectColor: messageTheme.secondaryTextColor,
|
||||||
areContentAnimationsEnabled: item.context.sharedContext.energyUsageSettings.loopEmoji,
|
areContentAnimationsEnabled: item.context.sharedContext.energyUsageSettings.loopEmoji,
|
||||||
spoilerExpandRect: spoilerExpandRect
|
spoilerExpandRect: spoilerExpandRect,
|
||||||
|
crossfadeContents: { [weak strongSelf] sourceView in
|
||||||
|
guard let strongSelf else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let textNodeContainer = strongSelf.textNode.textNode.view.superview {
|
||||||
|
sourceView.frame = CGRect(origin: strongSelf.textNode.textNode.frame.origin, size: sourceView.bounds.size)
|
||||||
|
textNodeContainer.addSubview(sourceView)
|
||||||
|
|
||||||
|
sourceView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.12, removeOnCompletion: false, completion: { [weak sourceView] _ in
|
||||||
|
sourceView?.removeFromSuperview()
|
||||||
|
})
|
||||||
|
strongSelf.textNode.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1)
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
))
|
))
|
||||||
animation.animator.updateFrame(layer: strongSelf.textNode.textNode.layer, frame: textFrame, completion: nil)
|
animation.animator.updateFrame(layer: strongSelf.textNode.textNode.layer, frame: textFrame, completion: nil)
|
||||||
|
@ -239,6 +239,7 @@ public final class ChatSideTopicsPanel: Component {
|
|||||||
avatarNode = current
|
avatarNode = current
|
||||||
} else {
|
} else {
|
||||||
avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 11.0))
|
avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 11.0))
|
||||||
|
avatarNode.isUserInteractionEnabled = false
|
||||||
self.avatarNode = avatarNode
|
self.avatarNode = avatarNode
|
||||||
self.containerButton.addSubview(avatarNode.view)
|
self.containerButton.addSubview(avatarNode.view)
|
||||||
}
|
}
|
||||||
@ -597,6 +598,18 @@ public final class ChatSideTopicsPanel: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func topicIndex(threadId: Int64?) -> Int? {
|
||||||
|
if let threadId {
|
||||||
|
if let value = self.items.firstIndex(where: { $0.id == .chatList(PeerId(threadId)) }) {
|
||||||
|
return value + 1
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func update(component: ChatSideTopicsPanel, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
|
func update(component: ChatSideTopicsPanel, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
|
||||||
self.isUpdating = true
|
self.isUpdating = true
|
||||||
defer {
|
defer {
|
||||||
|
@ -1099,19 +1099,22 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn
|
|||||||
public let spoilerEffectColor: UIColor
|
public let spoilerEffectColor: UIColor
|
||||||
public let areContentAnimationsEnabled: Bool
|
public let areContentAnimationsEnabled: Bool
|
||||||
public let spoilerExpandRect: CGRect?
|
public let spoilerExpandRect: CGRect?
|
||||||
|
public var crossfadeContents: ((UIView) -> Void)?
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
animation: ListViewItemUpdateAnimation,
|
animation: ListViewItemUpdateAnimation,
|
||||||
spoilerTextColor: UIColor,
|
spoilerTextColor: UIColor,
|
||||||
spoilerEffectColor: UIColor,
|
spoilerEffectColor: UIColor,
|
||||||
areContentAnimationsEnabled: Bool,
|
areContentAnimationsEnabled: Bool,
|
||||||
spoilerExpandRect: CGRect?
|
spoilerExpandRect: CGRect?,
|
||||||
|
crossfadeContents: ((UIView) -> Void)? = nil
|
||||||
) {
|
) {
|
||||||
self.animation = animation
|
self.animation = animation
|
||||||
self.spoilerTextColor = spoilerTextColor
|
self.spoilerTextColor = spoilerTextColor
|
||||||
self.spoilerEffectColor = spoilerEffectColor
|
self.spoilerEffectColor = spoilerEffectColor
|
||||||
self.areContentAnimationsEnabled = areContentAnimationsEnabled
|
self.areContentAnimationsEnabled = areContentAnimationsEnabled
|
||||||
self.spoilerExpandRect = spoilerExpandRect
|
self.spoilerExpandRect = spoilerExpandRect
|
||||||
|
self.crossfadeContents = crossfadeContents
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -216,9 +216,14 @@ public final class InteractiveTextNodeWithEntities {
|
|||||||
return (layout, { applyArguments in
|
return (layout, { applyArguments in
|
||||||
let animation: ListViewItemUpdateAnimation = applyArguments.applyArguments.animation
|
let animation: ListViewItemUpdateAnimation = applyArguments.applyArguments.animation
|
||||||
|
|
||||||
|
var crossfadeSourceView: UIView?
|
||||||
|
if let maybeNode, applyArguments.applyArguments.animation.transition.isAnimated, let animator = applyArguments.applyArguments.animation.animator as? ControlledTransition.LegacyAnimator, animator.transition.isAnimated, maybeNode.textNode.bounds.size != layout.size {
|
||||||
|
crossfadeSourceView = maybeNode.textNode.view.snapshotView(afterScreenUpdates: false)
|
||||||
|
}
|
||||||
|
|
||||||
let result = apply(applyArguments.applyArguments)
|
let result = apply(applyArguments.applyArguments)
|
||||||
|
|
||||||
if let maybeNode = maybeNode {
|
if let maybeNode {
|
||||||
maybeNode.attributedString = arguments.attributedString
|
maybeNode.attributedString = arguments.attributedString
|
||||||
|
|
||||||
maybeNode.updateInteractiveContents(
|
maybeNode.updateInteractiveContents(
|
||||||
@ -234,6 +239,10 @@ public final class InteractiveTextNodeWithEntities {
|
|||||||
applyArguments: applyArguments.applyArguments
|
applyArguments: applyArguments.applyArguments
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if let crossfadeSourceView {
|
||||||
|
applyArguments.applyArguments.crossfadeContents?(crossfadeSourceView)
|
||||||
|
}
|
||||||
|
|
||||||
return maybeNode
|
return maybeNode
|
||||||
} else {
|
} else {
|
||||||
let resultNode = InteractiveTextNodeWithEntities(textNode: result)
|
let resultNode = InteractiveTextNodeWithEntities(textNode: result)
|
||||||
|
@ -338,7 +338,7 @@ final class PeerNameColorChatPreviewItemNode: ListViewItemNode {
|
|||||||
headerNode.item = header
|
headerNode.item = header
|
||||||
}
|
}
|
||||||
headerNode.updateLayoutInternal(size: headerFrame.size, leftInset: leftInset, rightInset: rightInset, transition: .immediate)
|
headerNode.updateLayoutInternal(size: headerFrame.size, leftInset: leftInset, rightInset: rightInset, transition: .immediate)
|
||||||
headerNode.updateStickDistanceFactor(stickLocationDistanceFactor, transition: .immediate)
|
headerNode.updateStickDistanceFactor(stickLocationDistanceFactor, distance: 0.0, transition: .immediate)
|
||||||
} else {
|
} else {
|
||||||
headerNode = header.node(synchronousLoad: true)
|
headerNode = header.node(synchronousLoad: true)
|
||||||
if headerNode.item !== header {
|
if headerNode.item !== header {
|
||||||
@ -350,7 +350,7 @@ final class PeerNameColorChatPreviewItemNode: ListViewItemNode {
|
|||||||
strongSelf.itemHeaderNodes[id] = headerNode
|
strongSelf.itemHeaderNodes[id] = headerNode
|
||||||
|
|
||||||
strongSelf.containerNode.addSubnode(headerNode)
|
strongSelf.containerNode.addSubnode(headerNode)
|
||||||
headerNode.updateStickDistanceFactor(stickLocationDistanceFactor, transition: .immediate)
|
headerNode.updateStickDistanceFactor(stickLocationDistanceFactor, distance: 0.0, transition: .immediate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -404,9 +404,13 @@ extension ChatControllerImpl {
|
|||||||
messageOptionsTitleInfo = .single(nil)
|
messageOptionsTitleInfo = .single(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var isFirstTimeValue = true
|
||||||
self.titleDisposable.set((combineLatest(queue: Queue.mainQueue(), peerView.get(), onlineMemberCount, displayedCountSignal, subtitleTextSignal, self.presentationInterfaceStatePromise.get(), hasPeerInfo, messageOptionsTitleInfo)
|
self.titleDisposable.set((combineLatest(queue: Queue.mainQueue(), peerView.get(), onlineMemberCount, displayedCountSignal, subtitleTextSignal, self.presentationInterfaceStatePromise.get(), hasPeerInfo, messageOptionsTitleInfo)
|
||||||
|> deliverOnMainQueue).startStrict(next: { [weak self] peerView, onlineMemberCount, displayedCount, subtitleText, presentationInterfaceState, hasPeerInfo, messageOptionsTitleInfo in
|
|> deliverOnMainQueue).startStrict(next: { [weak self] peerView, onlineMemberCount, displayedCount, subtitleText, presentationInterfaceState, hasPeerInfo, messageOptionsTitleInfo in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
|
let isFirstTime = isFirstTimeValue
|
||||||
|
isFirstTimeValue = false
|
||||||
|
|
||||||
var isScheduledMessages = false
|
var isScheduledMessages = false
|
||||||
if case .scheduledMessages = presentationInterfaceState.subject {
|
if case .scheduledMessages = presentationInterfaceState.subject {
|
||||||
isScheduledMessages = true
|
isScheduledMessages = true
|
||||||
@ -446,6 +450,8 @@ extension ChatControllerImpl {
|
|||||||
} else if let peer = peerViewMainPeer(peerView) {
|
} else if let peer = peerViewMainPeer(peerView) {
|
||||||
if case .pinnedMessages = presentationInterfaceState.subject {
|
if case .pinnedMessages = presentationInterfaceState.subject {
|
||||||
strongSelf.chatTitleView?.titleContent = .custom(presentationInterfaceState.strings.Chat_TitlePinnedMessages(Int32(displayedCount ?? 1)), nil, false)
|
strongSelf.chatTitleView?.titleContent = .custom(presentationInterfaceState.strings.Chat_TitlePinnedMessages(Int32(displayedCount ?? 1)), nil, false)
|
||||||
|
} else if let channel = peer as? TelegramChannel, channel.isMonoForum {
|
||||||
|
strongSelf.chatTitleView?.titleContent = .custom(channel.debugDisplayTitle, nil, false)
|
||||||
} else {
|
} else {
|
||||||
strongSelf.chatTitleView?.titleContent = .peer(peerView: ChatTitleContent.PeerData(peerView: peerView), customTitle: nil, onlineMemberCount: onlineMemberCount, isScheduledMessages: isScheduledMessages, isMuted: nil, customMessageCount: nil, isEnabled: hasPeerInfo)
|
strongSelf.chatTitleView?.titleContent = .peer(peerView: ChatTitleContent.PeerData(peerView: peerView), customTitle: nil, onlineMemberCount: onlineMemberCount, isScheduledMessages: isScheduledMessages, isMuted: nil, customMessageCount: nil, isEnabled: hasPeerInfo)
|
||||||
let imageOverride: AvatarNodeImageOverride?
|
let imageOverride: AvatarNodeImageOverride?
|
||||||
@ -460,7 +466,7 @@ extension ChatControllerImpl {
|
|||||||
} else {
|
} else {
|
||||||
imageOverride = nil
|
imageOverride = nil
|
||||||
}
|
}
|
||||||
(strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.setPeer(context: strongSelf.context, theme: strongSelf.presentationData.theme, peer: EnginePeer(peer), overrideImage: imageOverride)
|
(strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.setPeer(context: strongSelf.context, theme: strongSelf.presentationData.theme, peer: EnginePeer(peer), overrideImage: imageOverride, synchronousLoad: isFirstTime)
|
||||||
if case .standard(.previewing) = strongSelf.mode {
|
if case .standard(.previewing) = strongSelf.mode {
|
||||||
(strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.contextActionIsEnabled = false
|
(strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.contextActionIsEnabled = false
|
||||||
} else {
|
} else {
|
||||||
@ -767,6 +773,11 @@ extension ChatControllerImpl {
|
|||||||
autoremoveTimeout = value?.effectiveValue
|
autoremoveTimeout = value?.effectiveValue
|
||||||
}
|
}
|
||||||
} else if let cachedChannelData = peerView.cachedData as? CachedChannelData {
|
} else if let cachedChannelData = peerView.cachedData as? CachedChannelData {
|
||||||
|
if let channel = peer as? TelegramChannel, channel.isMonoForum {
|
||||||
|
if case let .known(value) = cachedChannelData.linkedMonoforumPeerId {
|
||||||
|
currentSendAsPeerId = value
|
||||||
|
}
|
||||||
|
} else {
|
||||||
currentSendAsPeerId = cachedChannelData.sendAsPeerId
|
currentSendAsPeerId = cachedChannelData.sendAsPeerId
|
||||||
if let channel = peer as? TelegramChannel, case .group = channel.info {
|
if let channel = peer as? TelegramChannel, case .group = channel.info {
|
||||||
if !cachedChannelData.botInfos.isEmpty {
|
if !cachedChannelData.botInfos.isEmpty {
|
||||||
@ -779,6 +790,7 @@ extension ChatControllerImpl {
|
|||||||
hasBotCommands = true
|
hasBotCommands = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if case let .known(value) = cachedChannelData.autoremoveTimeout {
|
if case let .known(value) = cachedChannelData.autoremoveTimeout {
|
||||||
autoremoveTimeout = value?.effectiveValue
|
autoremoveTimeout = value?.effectiveValue
|
||||||
}
|
}
|
||||||
@ -1139,22 +1151,39 @@ extension ChatControllerImpl {
|
|||||||
savedMessagesPeerId = nil
|
savedMessagesPeerId = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
let savedMessagesPeer: Signal<(peer: EnginePeer?, messageCount: Int)?, NoError>
|
let savedMessagesPeer: Signal<(peer: EnginePeer?, messageCount: Int, presence: EnginePeer.Presence?)?, NoError>
|
||||||
if let savedMessagesPeerId {
|
if let savedMessagesPeerId {
|
||||||
let threadPeerId = savedMessagesPeerId
|
let threadPeerId = savedMessagesPeerId
|
||||||
let basicPeerKey: PostboxViewKey = .basicPeer(threadPeerId)
|
let basicPeerKey: PostboxViewKey = .peer(peerId: threadPeerId, components: [])
|
||||||
let countViewKey: PostboxViewKey = .historyTagSummaryView(tag: MessageTags(), peerId: peerId, threadId: savedMessagesPeerId.toInt64(), namespace: Namespaces.Message.Cloud, customTag: nil)
|
let countViewKey: PostboxViewKey = .historyTagSummaryView(tag: MessageTags(), peerId: peerId, threadId: savedMessagesPeerId.toInt64(), namespace: Namespaces.Message.Cloud, customTag: nil)
|
||||||
savedMessagesPeer = context.account.postbox.combinedView(keys: [basicPeerKey, countViewKey])
|
savedMessagesPeer = context.account.postbox.combinedView(keys: [basicPeerKey, countViewKey])
|
||||||
|> map { views -> (peer: EnginePeer?, messageCount: Int)? in
|
|> map { views -> (peer: EnginePeer?, messageCount: Int, presence: EnginePeer.Presence?)? in
|
||||||
let peer = ((views.views[basicPeerKey] as? BasicPeerView)?.peer).flatMap(EnginePeer.init)
|
var peer: EnginePeer?
|
||||||
|
var presence: EnginePeer.Presence?
|
||||||
|
if let peerView = views.views[basicPeerKey] as? PeerView {
|
||||||
|
peer = peerViewMainPeer(peerView).flatMap(EnginePeer.init)
|
||||||
|
presence = peerView.peerPresences[threadPeerId].flatMap(EnginePeer.Presence.init)
|
||||||
|
}
|
||||||
|
|
||||||
var messageCount = 0
|
var messageCount = 0
|
||||||
if let summaryView = views.views[countViewKey] as? MessageHistoryTagSummaryView, let count = summaryView.count {
|
if let summaryView = views.views[countViewKey] as? MessageHistoryTagSummaryView, let count = summaryView.count {
|
||||||
messageCount += Int(count)
|
messageCount += Int(count)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (peer, messageCount)
|
return (peer, messageCount, presence)
|
||||||
}
|
}
|
||||||
|
|> distinctUntilChanged(isEqual: { lhs, rhs in
|
||||||
|
if lhs?.peer != rhs?.peer {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs?.messageCount != rhs?.messageCount {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs?.presence != rhs?.presence {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
savedMessagesPeer = .single(nil)
|
savedMessagesPeer = .single(nil)
|
||||||
}
|
}
|
||||||
@ -1261,6 +1290,7 @@ extension ChatControllerImpl {
|
|||||||
let globalPrivacySettings = context.engine.data.get(TelegramEngine.EngineData.Item.Configuration.GlobalPrivacy())
|
let globalPrivacySettings = context.engine.data.get(TelegramEngine.EngineData.Item.Configuration.GlobalPrivacy())
|
||||||
|
|
||||||
self.titleDisposable.set(nil)
|
self.titleDisposable.set(nil)
|
||||||
|
var isFirstTimeValue = true
|
||||||
self.peerDisposable.set((combineLatest(queue: Queue.mainQueue(),
|
self.peerDisposable.set((combineLatest(queue: Queue.mainQueue(),
|
||||||
peerView,
|
peerView,
|
||||||
messageAndTopic,
|
messageAndTopic,
|
||||||
@ -1275,6 +1305,9 @@ extension ChatControllerImpl {
|
|||||||
)
|
)
|
||||||
|> deliverOnMainQueue).startStrict(next: { [weak self] peerView, messageAndTopic, savedMessagesPeer, onlineMemberCount, hasScheduledMessages, hasSearchTags, hasSavedChats, isPremiumRequiredForMessaging, managingBot, globalPrivacySettings in
|
|> deliverOnMainQueue).startStrict(next: { [weak self] peerView, messageAndTopic, savedMessagesPeer, onlineMemberCount, hasScheduledMessages, hasSearchTags, hasSavedChats, isPremiumRequiredForMessaging, managingBot, globalPrivacySettings in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
|
let isFirstTime = isFirstTimeValue
|
||||||
|
isFirstTimeValue = false
|
||||||
|
|
||||||
strongSelf.hasScheduledMessages = hasScheduledMessages
|
strongSelf.hasScheduledMessages = hasScheduledMessages
|
||||||
|
|
||||||
var renderedPeer: RenderedPeer?
|
var renderedPeer: RenderedPeer?
|
||||||
@ -1334,16 +1367,29 @@ extension ChatControllerImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let savedMessagesPeerId {
|
if let savedMessagesPeerId {
|
||||||
|
var peerPresences: [PeerId: PeerPresence] = [:]
|
||||||
|
if let presence = savedMessagesPeer?.presence {
|
||||||
|
peerPresences[savedMessagesPeerId] = presence._asPresence()
|
||||||
|
}
|
||||||
let mappedPeerData = ChatTitleContent.PeerData(
|
let mappedPeerData = ChatTitleContent.PeerData(
|
||||||
peerId: savedMessagesPeerId,
|
peerId: savedMessagesPeerId,
|
||||||
peer: savedMessagesPeer?.peer?._asPeer(),
|
peer: savedMessagesPeer?.peer?._asPeer(),
|
||||||
isContact: true,
|
isContact: true,
|
||||||
isSavedMessages: true,
|
isSavedMessages: true,
|
||||||
notificationSettings: nil,
|
notificationSettings: nil,
|
||||||
peerPresences: [:],
|
peerPresences: peerPresences,
|
||||||
cachedData: nil
|
cachedData: nil
|
||||||
)
|
)
|
||||||
strongSelf.chatTitleView?.titleContent = .peer(peerView: mappedPeerData, customTitle: nil, onlineMemberCount: (nil, nil), isScheduledMessages: false, isMuted: false, customMessageCount: savedMessagesPeer?.messageCount ?? 0, isEnabled: true)
|
|
||||||
|
var customMessageCount: Int?
|
||||||
|
if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, peer.isMonoForum {
|
||||||
|
} else {
|
||||||
|
customMessageCount = savedMessagesPeer?.messageCount ?? 0
|
||||||
|
}
|
||||||
|
|
||||||
|
strongSelf.chatTitleView?.titleContent = .peer(peerView: mappedPeerData, customTitle: nil, onlineMemberCount: (nil, nil), isScheduledMessages: false, isMuted: false, customMessageCount: customMessageCount, isEnabled: true)
|
||||||
|
|
||||||
|
if selfController.rightNavigationButton != button {
|
||||||
|
|
||||||
strongSelf.peerView = peerView
|
strongSelf.peerView = peerView
|
||||||
|
|
||||||
@ -1374,7 +1420,7 @@ extension ChatControllerImpl {
|
|||||||
.updatedHasScheduledMessages(hasScheduledMessages)
|
.updatedHasScheduledMessages(hasScheduledMessages)
|
||||||
})
|
})
|
||||||
|
|
||||||
(strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.setPeer(context: strongSelf.context, theme: strongSelf.presentationData.theme, peer: savedMessagesPeer?.peer, overrideImage: imageOverride)
|
(strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.setPeer(context: strongSelf.context, theme: strongSelf.presentationData.theme, peer: savedMessagesPeer?.peer, overrideImage: imageOverride, synchronousLoad: isFirstTime)
|
||||||
(strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.contextActionIsEnabled = false
|
(strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.contextActionIsEnabled = false
|
||||||
strongSelf.chatInfoNavigationButton?.buttonItem.accessibilityLabel = strongSelf.presentationData.strings.Conversation_ContextMenuOpenProfile
|
strongSelf.chatInfoNavigationButton?.buttonItem.accessibilityLabel = strongSelf.presentationData.strings.Conversation_ContextMenuOpenProfile
|
||||||
} else {
|
} else {
|
||||||
@ -1464,6 +1510,11 @@ extension ChatControllerImpl {
|
|||||||
var peerGeoLocation: PeerGeoLocation?
|
var peerGeoLocation: PeerGeoLocation?
|
||||||
var currentSendAsPeerId: PeerId?
|
var currentSendAsPeerId: PeerId?
|
||||||
if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, let cachedData = peerView.cachedData as? CachedChannelData {
|
if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, let cachedData = peerView.cachedData as? CachedChannelData {
|
||||||
|
if peer.isMonoForum {
|
||||||
|
if case let .known(value) = cachedData.linkedMonoforumPeerId {
|
||||||
|
currentSendAsPeerId = value
|
||||||
|
}
|
||||||
|
} else {
|
||||||
currentSendAsPeerId = cachedData.sendAsPeerId
|
currentSendAsPeerId = cachedData.sendAsPeerId
|
||||||
if case .group = peer.info {
|
if case .group = peer.info {
|
||||||
peerGeoLocation = cachedData.peerGeoLocation
|
peerGeoLocation = cachedData.peerGeoLocation
|
||||||
@ -1472,6 +1523,7 @@ extension ChatControllerImpl {
|
|||||||
peerDiscussionId = value
|
peerDiscussionId = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var isNotAccessible: Bool = false
|
var isNotAccessible: Bool = false
|
||||||
if let cachedChannelData = peerView.cachedData as? CachedChannelData {
|
if let cachedChannelData = peerView.cachedData as? CachedChannelData {
|
||||||
@ -1685,12 +1737,16 @@ extension ChatControllerImpl {
|
|||||||
self.chatTitleView?.titleContent = .custom(" ", nil, false)
|
self.chatTitleView?.titleContent = .custom(" ", nil, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var isFirstTimeValue = true
|
||||||
self.peerDisposable.set((peerView
|
self.peerDisposable.set((peerView
|
||||||
|> deliverOnMainQueue).startStrict(next: { [weak self] peerView in
|
|> deliverOnMainQueue).startStrict(next: { [weak self] peerView in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let isFirstTime = isFirstTimeValue
|
||||||
|
isFirstTimeValue = false
|
||||||
|
|
||||||
var renderedPeer: RenderedPeer?
|
var renderedPeer: RenderedPeer?
|
||||||
if let peerView, let peer = peerView.peers[peerView.peerId] {
|
if let peerView, let peer = peerView.peers[peerView.peerId] {
|
||||||
var peers = SimpleDictionary<PeerId, Peer>()
|
var peers = SimpleDictionary<PeerId, Peer>()
|
||||||
@ -1700,7 +1756,7 @@ extension ChatControllerImpl {
|
|||||||
}
|
}
|
||||||
renderedPeer = RenderedPeer(peerId: peer.id, peers: peers, associatedMedia: peerView.media)
|
renderedPeer = RenderedPeer(peerId: peer.id, peers: peers, associatedMedia: peerView.media)
|
||||||
|
|
||||||
(self.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.setPeer(context: self.context, theme: self.presentationData.theme, peer: EnginePeer(peer), overrideImage: nil)
|
(self.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.setPeer(context: self.context, theme: self.presentationData.theme, peer: EnginePeer(peer), overrideImage: nil, synchronousLoad: isFirstTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.peerView = peerView
|
self.peerView = peerView
|
||||||
@ -2327,6 +2383,10 @@ extension ChatControllerImpl {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let channel = peerViewMainPeer(peerView) as? TelegramChannel, channel.isMonoForum {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
let isPremium = strongSelf.presentationInterfaceState.isPremium
|
let isPremium = strongSelf.presentationInterfaceState.isPremium
|
||||||
|
|
||||||
var allPeers: [SendAsPeer]?
|
var allPeers: [SendAsPeer]?
|
||||||
|
@ -23,6 +23,8 @@ func updateChatPresentationInterfaceStateImpl(
|
|||||||
_ f: (ChatPresentationInterfaceState) -> ChatPresentationInterfaceState,
|
_ f: (ChatPresentationInterfaceState) -> ChatPresentationInterfaceState,
|
||||||
completion externalCompletion: @escaping (ContainedViewLayoutTransition) -> Void
|
completion externalCompletion: @escaping (ContainedViewLayoutTransition) -> Void
|
||||||
) {
|
) {
|
||||||
|
let previousChatLocation = selfController.chatDisplayNode.historyNode.chatLocation
|
||||||
|
|
||||||
var completion = externalCompletion
|
var completion = externalCompletion
|
||||||
var temporaryChatPresentationInterfaceState = f(selfController.presentationInterfaceState)
|
var temporaryChatPresentationInterfaceState = f(selfController.presentationInterfaceState)
|
||||||
|
|
||||||
@ -434,6 +436,11 @@ func updateChatPresentationInterfaceStateImpl(
|
|||||||
|
|
||||||
selfController.presentationInterfaceState = updatedChatPresentationInterfaceState
|
selfController.presentationInterfaceState = updatedChatPresentationInterfaceState
|
||||||
|
|
||||||
|
if selfController.chatDisplayNode.chatLocation != selfController.presentationInterfaceState.chatLocation {
|
||||||
|
let tabSwitchDirection = selfController.chatDisplayNode.chatLocationTabSwitchDirection(from: selfController.chatDisplayNode.chatLocation, to: selfController.presentationInterfaceState.chatLocation)
|
||||||
|
selfController.chatDisplayNode.updateChatLocation(chatLocation: selfController.presentationInterfaceState.chatLocation, transition: transition, tabSwitchDirection: tabSwitchDirection)
|
||||||
|
}
|
||||||
|
|
||||||
selfController.updateSlowmodeStatus()
|
selfController.updateSlowmodeStatus()
|
||||||
|
|
||||||
switch updatedChatPresentationInterfaceState.inputMode {
|
switch updatedChatPresentationInterfaceState.inputMode {
|
||||||
@ -489,6 +496,8 @@ func updateChatPresentationInterfaceStateImpl(
|
|||||||
selfController.leftNavigationButton = nil
|
selfController.leftNavigationButton = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let channel = presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isForumOrMonoForum {
|
||||||
|
} else {
|
||||||
var buttonsAnimated = transition.isAnimated
|
var buttonsAnimated = transition.isAnimated
|
||||||
if let button = rightNavigationButtonForChatInterfaceState(context: selfController.context, presentationInterfaceState: updatedChatPresentationInterfaceState, strings: updatedChatPresentationInterfaceState.strings, currentButton: selfController.rightNavigationButton, target: selfController, selector: #selector(selfController.rightNavigationButtonAction), chatInfoNavigationButton: selfController.chatInfoNavigationButton, moreInfoNavigationButton: selfController.moreInfoNavigationButton) {
|
if let button = rightNavigationButtonForChatInterfaceState(context: selfController.context, presentationInterfaceState: updatedChatPresentationInterfaceState, strings: updatedChatPresentationInterfaceState.strings, currentButton: selfController.rightNavigationButton, target: selfController, selector: #selector(selfController.rightNavigationButtonAction), chatInfoNavigationButton: selfController.chatInfoNavigationButton, moreInfoNavigationButton: selfController.moreInfoNavigationButton) {
|
||||||
if selfController.rightNavigationButton != button {
|
if selfController.rightNavigationButton != button {
|
||||||
@ -498,11 +507,15 @@ func updateChatPresentationInterfaceStateImpl(
|
|||||||
if case .replyThread = selfController.chatLocation {
|
if case .replyThread = selfController.chatLocation {
|
||||||
buttonsAnimated = false
|
buttonsAnimated = false
|
||||||
}
|
}
|
||||||
|
if let channel = updatedChatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum {
|
||||||
|
buttonsAnimated = false
|
||||||
|
}
|
||||||
selfController.rightNavigationButton = button
|
selfController.rightNavigationButton = button
|
||||||
}
|
}
|
||||||
} else if let _ = selfController.rightNavigationButton {
|
} else if let _ = selfController.rightNavigationButton {
|
||||||
selfController.rightNavigationButton = nil
|
selfController.rightNavigationButton = nil
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let button = secondaryRightNavigationButtonForChatInterfaceState(context: selfController.context, presentationInterfaceState: updatedChatPresentationInterfaceState, strings: updatedChatPresentationInterfaceState.strings, currentButton: selfController.secondaryRightNavigationButton, target: selfController, selector: #selector(selfController.secondaryRightNavigationButtonAction), chatInfoNavigationButton: selfController.chatInfoNavigationButton, moreInfoNavigationButton: selfController.moreInfoNavigationButton) {
|
if let button = secondaryRightNavigationButtonForChatInterfaceState(context: selfController.context, presentationInterfaceState: updatedChatPresentationInterfaceState, strings: updatedChatPresentationInterfaceState.strings, currentButton: selfController.secondaryRightNavigationButton, target: selfController, selector: #selector(selfController.secondaryRightNavigationButtonAction), chatInfoNavigationButton: selfController.chatInfoNavigationButton, moreInfoNavigationButton: selfController.moreInfoNavigationButton) {
|
||||||
if selfController.secondaryRightNavigationButton != button {
|
if selfController.secondaryRightNavigationButton != button {
|
||||||
@ -587,12 +600,10 @@ func updateChatPresentationInterfaceStateImpl(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if selfController.chatDisplayNode.historyNode.chatLocation != selfController.presentationInterfaceState.chatLocation {
|
if previousChatLocation != selfController.presentationInterfaceState.chatLocation {
|
||||||
selfController.chatLocation = selfController.presentationInterfaceState.chatLocation
|
selfController.chatLocation = selfController.presentationInterfaceState.chatLocation
|
||||||
selfController.chatDisplayNode.chatLocation = selfController.presentationInterfaceState.chatLocation
|
|
||||||
selfController.reloadChatLocation()
|
selfController.reloadChatLocation()
|
||||||
selfController.reloadCachedData()
|
selfController.reloadCachedData()
|
||||||
selfController.chatDisplayNode.historyNode.updateChatLocation(chatLocation: selfController.presentationInterfaceState.chatLocation)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
selfController.updateDownButtonVisibility()
|
selfController.updateDownButtonVisibility()
|
||||||
|
@ -842,7 +842,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
return true
|
return true
|
||||||
case let .quickReplyMessageInput(_, shortcutType):
|
case let .quickReplyMessageInput(_, shortcutType):
|
||||||
if let historyView = strongSelf.chatDisplayNode.historyNode.originalHistoryView, historyView.entries.isEmpty {
|
if let historyView = strongSelf.chatDisplayNode.historyNode.originalHistoryView, historyView.entries.isEmpty {
|
||||||
|
|
||||||
let titleString: String
|
let titleString: String
|
||||||
let textString: String
|
let textString: String
|
||||||
switch shortcutType {
|
switch shortcutType {
|
||||||
@ -7919,6 +7918,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
attributes.append(OutgoingScheduleInfoMessageAttribute(scheduleTime: scheduleTime))
|
attributes.append(OutgoingScheduleInfoMessageAttribute(scheduleTime: scheduleTime))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let channel = self.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum {
|
||||||
|
attributes.removeAll(where: { $0 is SendAsMessageAttribute })
|
||||||
|
if channel.adminRights != nil, let sendAsPeerId = self.presentationInterfaceState.currentSendAsPeerId {
|
||||||
|
attributes.append(SendAsMessageAttribute(peerId: sendAsPeerId))
|
||||||
|
}
|
||||||
|
}
|
||||||
if let sendAsPeerId = self.presentationInterfaceState.currentSendAsPeerId {
|
if let sendAsPeerId = self.presentationInterfaceState.currentSendAsPeerId {
|
||||||
if attributes.first(where: { $0 is SendAsMessageAttribute }) == nil {
|
if attributes.first(where: { $0 is SendAsMessageAttribute }) == nil {
|
||||||
attributes.append(SendAsMessageAttribute(peerId: sendAsPeerId))
|
attributes.append(SendAsMessageAttribute(peerId: sendAsPeerId))
|
||||||
|
@ -135,7 +135,8 @@ class HistoryNodeContainer: ASDisplayNode {
|
|||||||
|
|
||||||
class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
var chatLocation: ChatLocation
|
private(set) var chatLocation: ChatLocation
|
||||||
|
private let chatLocationContextHolder: Atomic<ChatLocationContextHolder?>
|
||||||
let controllerInteraction: ChatControllerInteraction
|
let controllerInteraction: ChatControllerInteraction
|
||||||
private weak var controller: ChatControllerImpl?
|
private weak var controller: ChatControllerImpl?
|
||||||
|
|
||||||
@ -158,7 +159,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
|||||||
let contentContainerNode: ChatNodeContainer
|
let contentContainerNode: ChatNodeContainer
|
||||||
let contentDimNode: ASDisplayNode
|
let contentDimNode: ASDisplayNode
|
||||||
let backgroundNode: WallpaperBackgroundNode
|
let backgroundNode: WallpaperBackgroundNode
|
||||||
let historyNode: ChatHistoryListNodeImpl
|
var historyNode: ChatHistoryListNodeImpl
|
||||||
var blurredHistoryNode: ASImageNode?
|
var blurredHistoryNode: ASImageNode?
|
||||||
let historyNodeContainer: HistoryNodeContainer
|
let historyNodeContainer: HistoryNodeContainer
|
||||||
let loadingNode: ChatLoadingNode
|
let loadingNode: ChatLoadingNode
|
||||||
@ -183,6 +184,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
|||||||
|
|
||||||
private(set) var validLayout: (ContainerViewLayout, CGFloat)?
|
private(set) var validLayout: (ContainerViewLayout, CGFloat)?
|
||||||
private var visibleAreaInset = UIEdgeInsets()
|
private var visibleAreaInset = UIEdgeInsets()
|
||||||
|
private var currentListViewLayout: (size: CGSize, insets: UIEdgeInsets, scrollIndicatorInsets: UIEdgeInsets)?
|
||||||
|
|
||||||
private(set) var searchNavigationNode: ChatSearchNavigationContentNode?
|
private(set) var searchNavigationNode: ChatSearchNavigationContentNode?
|
||||||
|
|
||||||
@ -293,7 +295,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
|||||||
|
|
||||||
private var dropDimNode: ASDisplayNode?
|
private var dropDimNode: ASDisplayNode?
|
||||||
|
|
||||||
let messageTransitionNode: ChatMessageTransitionNodeImpl
|
var messageTransitionNode: ChatMessageTransitionNodeImpl
|
||||||
|
|
||||||
private let presentationContextMarker = ASDisplayNode()
|
private let presentationContextMarker = ASDisplayNode()
|
||||||
|
|
||||||
@ -405,6 +407,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
|||||||
init(context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>, subject: ChatControllerSubject?, controllerInteraction: ChatControllerInteraction, chatPresentationInterfaceState: ChatPresentationInterfaceState, automaticMediaDownloadSettings: MediaAutoDownloadSettings, navigationBar: NavigationBar?, statusBar: StatusBar?, backgroundNode: WallpaperBackgroundNode, controller: ChatControllerImpl?) {
|
init(context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>, subject: ChatControllerSubject?, controllerInteraction: ChatControllerInteraction, chatPresentationInterfaceState: ChatPresentationInterfaceState, automaticMediaDownloadSettings: MediaAutoDownloadSettings, navigationBar: NavigationBar?, statusBar: StatusBar?, backgroundNode: WallpaperBackgroundNode, controller: ChatControllerImpl?) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.chatLocation = chatLocation
|
self.chatLocation = chatLocation
|
||||||
|
self.chatLocationContextHolder = chatLocationContextHolder
|
||||||
self.controllerInteraction = controllerInteraction
|
self.controllerInteraction = controllerInteraction
|
||||||
self.chatPresentationInterfaceState = chatPresentationInterfaceState
|
self.chatPresentationInterfaceState = chatPresentationInterfaceState
|
||||||
self.automaticMediaDownloadSettings = automaticMediaDownloadSettings
|
self.automaticMediaDownloadSettings = automaticMediaDownloadSettings
|
||||||
@ -762,34 +765,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
|||||||
|
|
||||||
assert(Queue.mainQueue().isCurrent())
|
assert(Queue.mainQueue().isCurrent())
|
||||||
|
|
||||||
self.historyNode.setLoadStateUpdated { [weak self] loadState, animated in
|
self.setupHistoryNode()
|
||||||
if let strongSelf = self {
|
|
||||||
let wasLoading = strongSelf.isLoadingValue
|
|
||||||
if case let .loading(earlier) = loadState {
|
|
||||||
strongSelf.updateIsLoading(isLoading: true, earlier: earlier, animated: animated)
|
|
||||||
} else {
|
|
||||||
strongSelf.updateIsLoading(isLoading: false, earlier: false, animated: animated)
|
|
||||||
}
|
|
||||||
|
|
||||||
var emptyType: ChatHistoryNodeLoadState.EmptyType?
|
|
||||||
if case let .empty(type) = loadState {
|
|
||||||
if case .botInfo = type {
|
|
||||||
} else {
|
|
||||||
emptyType = type
|
|
||||||
if case .joined = type {
|
|
||||||
if strongSelf.didDisplayEmptyGreeting {
|
|
||||||
emptyType = .generic
|
|
||||||
} else {
|
|
||||||
strongSelf.didDisplayEmptyGreeting = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if case .messages = loadState {
|
|
||||||
strongSelf.didDisplayEmptyGreeting = true
|
|
||||||
}
|
|
||||||
strongSelf.updateIsEmpty(emptyType, wasLoading: wasLoading, animated: animated)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.interactiveEmojisDisposable = (self.context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration])
|
self.interactiveEmojisDisposable = (self.context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration])
|
||||||
|> map { preferencesView -> InteractiveEmojiConfiguration in
|
|> map { preferencesView -> InteractiveEmojiConfiguration in
|
||||||
@ -802,31 +778,6 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
var backgroundColors: [UInt32] = []
|
|
||||||
switch chatPresentationInterfaceState.chatWallpaper {
|
|
||||||
case let .file(file):
|
|
||||||
if file.isPattern {
|
|
||||||
backgroundColors = file.settings.colors
|
|
||||||
}
|
|
||||||
case let .gradient(gradient):
|
|
||||||
backgroundColors = gradient.colors
|
|
||||||
case let .color(color):
|
|
||||||
backgroundColors = [color]
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if !backgroundColors.isEmpty {
|
|
||||||
let averageColor = UIColor.average(of: backgroundColors.map(UIColor.init(rgb:)))
|
|
||||||
if averageColor.hsb.b >= 0.3 {
|
|
||||||
self.historyNode.verticalScrollIndicatorColor = UIColor(white: 0.0, alpha: 0.3)
|
|
||||||
} else {
|
|
||||||
self.historyNode.verticalScrollIndicatorColor = UIColor(white: 1.0, alpha: 0.3)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.historyNode.verticalScrollIndicatorColor = UIColor(white: 0.5, alpha: 0.8)
|
|
||||||
}
|
|
||||||
self.historyNode.enableExtractedBackgrounds = true
|
|
||||||
|
|
||||||
self.addSubnode(self.wrappingNode)
|
self.addSubnode(self.wrappingNode)
|
||||||
self.wrappingNode.contentNode.addSubnode(self.contentContainerNode)
|
self.wrappingNode.contentNode.addSubnode(self.contentContainerNode)
|
||||||
self.contentContainerNode.contentNode.addSubnode(self.backgroundNode)
|
self.contentContainerNode.contentNode.addSubnode(self.backgroundNode)
|
||||||
@ -866,8 +817,6 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
|||||||
|
|
||||||
self.navigationBar?.additionalContentNode.addSubnode(self.titleAccessoryPanelContainer)
|
self.navigationBar?.additionalContentNode.addSubnode(self.titleAccessoryPanelContainer)
|
||||||
|
|
||||||
self.historyNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
|
|
||||||
|
|
||||||
self.textInputPanelNode = ChatTextInputPanelNode(context: context, presentationInterfaceState: chatPresentationInterfaceState, presentationContext: ChatPresentationContext(context: context, backgroundNode: backgroundNode), presentController: { [weak self] controller in
|
self.textInputPanelNode = ChatTextInputPanelNode(context: context, presentationInterfaceState: chatPresentationInterfaceState, presentationContext: ChatPresentationContext(context: context, backgroundNode: backgroundNode), presentController: { [weak self] controller in
|
||||||
self?.interfaceInteraction?.presentController(controller, nil)
|
self?.interfaceInteraction?.presentController(controller, nil)
|
||||||
})
|
})
|
||||||
@ -1014,44 +963,6 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
|||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
self.displayVideoUnmuteTipDisposable = (combineLatest(queue: Queue.mainQueue(), ApplicationSpecificNotice.getVolumeButtonToUnmute(accountManager: self.context.sharedContext.accountManager), self.historyNode.hasVisiblePlayableItemNodes, self.historyNode.isInteractivelyScrolling)
|
|
||||||
|> mapToSignal { notice, hasVisiblePlayableItemNodes, isInteractivelyScrolling -> Signal<Bool, NoError> in
|
|
||||||
let display = !notice && hasVisiblePlayableItemNodes && !isInteractivelyScrolling
|
|
||||||
if display {
|
|
||||||
return .complete()
|
|
||||||
|> delay(2.5, queue: Queue.mainQueue())
|
|
||||||
|> then(
|
|
||||||
.single(display)
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
return .single(display)
|
|
||||||
}
|
|
||||||
}).startStrict(next: { [weak self] display in
|
|
||||||
if let strongSelf = self, let interfaceInteraction = strongSelf.interfaceInteraction {
|
|
||||||
if display {
|
|
||||||
var nodes: [(CGFloat, ChatMessageItemView, ASDisplayNode)] = []
|
|
||||||
var skip = false
|
|
||||||
strongSelf.historyNode.forEachVisibleItemNode { itemNode in
|
|
||||||
if let itemNode = itemNode as? ChatMessageItemView, let (_, soundEnabled, isVideoMessage, _, badgeNode) = itemNode.playMediaWithSound(), let node = badgeNode {
|
|
||||||
if soundEnabled {
|
|
||||||
skip = true
|
|
||||||
} else if !skip && !isVideoMessage, case let .visible(fraction, _) = itemNode.visibility {
|
|
||||||
nodes.insert((fraction, itemNode, node), at: 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (fraction, _, badgeNode) in nodes {
|
|
||||||
if fraction > 0.7 {
|
|
||||||
interfaceInteraction.displayVideoUnmuteTip(badgeNode.view.convert(badgeNode.view.bounds, to: strongSelf.view).origin.offsetBy(dx: 42.0, dy: -1.0))
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
interfaceInteraction.displayVideoUnmuteTip(nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateIsEmpty(_ emptyType: ChatHistoryNodeLoadState.EmptyType?, wasLoading: Bool, animated: Bool) {
|
private func updateIsEmpty(_ emptyType: ChatHistoryNodeLoadState.EmptyType?, wasLoading: Bool, animated: Bool) {
|
||||||
@ -2277,6 +2188,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.currentListViewLayout = (contentBounds.size, insets: listInsets, scrollIndicatorInsets: listScrollIndicatorInsets)
|
||||||
listViewTransaction(ListViewUpdateSizeAndInsets(size: contentBounds.size, insets: listInsets, scrollIndicatorInsets: listScrollIndicatorInsets, duration: duration, curve: curve, ensureTopInsetForOverlayHighlightedItems: ensureTopInsetForOverlayHighlightedItems, customAnimationTransition: customListAnimationTransition), additionalScrollDistance, scrollToTop, { [weak self] in
|
listViewTransaction(ListViewUpdateSizeAndInsets(size: contentBounds.size, insets: listInsets, scrollIndicatorInsets: listScrollIndicatorInsets, duration: duration, curve: curve, ensureTopInsetForOverlayHighlightedItems: ensureTopInsetForOverlayHighlightedItems, customAnimationTransition: customListAnimationTransition), additionalScrollDistance, scrollToTop, { [weak self] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.notifyTransitionCompletionListeners(transition: transition)
|
strongSelf.notifyTransitionCompletionListeners(transition: transition)
|
||||||
@ -4881,4 +4793,199 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
|||||||
transition.updateSublayerTransformOffset(layer: inputPanelNode.layer, offset: CGPoint(x: 0.0, y: overscrollNode == nil ? 0.0 : -5.0))
|
transition.updateSublayerTransformOffset(layer: inputPanelNode.layer, offset: CGPoint(x: 0.0, y: overscrollNode == nil ? 0.0 : -5.0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func setupHistoryNode() {
|
||||||
|
var backgroundColors: [UInt32] = []
|
||||||
|
switch self.chatPresentationInterfaceState.chatWallpaper {
|
||||||
|
case let .file(file):
|
||||||
|
if file.isPattern {
|
||||||
|
backgroundColors = file.settings.colors
|
||||||
|
}
|
||||||
|
case let .gradient(gradient):
|
||||||
|
backgroundColors = gradient.colors
|
||||||
|
case let .color(color):
|
||||||
|
backgroundColors = [color]
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if !backgroundColors.isEmpty {
|
||||||
|
let averageColor = UIColor.average(of: backgroundColors.map(UIColor.init(rgb:)))
|
||||||
|
if averageColor.hsb.b >= 0.3 {
|
||||||
|
self.historyNode.verticalScrollIndicatorColor = UIColor(white: 0.0, alpha: 0.3)
|
||||||
|
} else {
|
||||||
|
self.historyNode.verticalScrollIndicatorColor = UIColor(white: 1.0, alpha: 0.3)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.historyNode.verticalScrollIndicatorColor = UIColor(white: 0.5, alpha: 0.8)
|
||||||
|
}
|
||||||
|
self.historyNode.enableExtractedBackgrounds = true
|
||||||
|
|
||||||
|
self.historyNode.setLoadStateUpdated { [weak self] loadState, animated in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let wasLoading = strongSelf.isLoadingValue
|
||||||
|
if case let .loading(earlier) = loadState {
|
||||||
|
strongSelf.updateIsLoading(isLoading: true, earlier: earlier, animated: animated)
|
||||||
|
} else {
|
||||||
|
strongSelf.updateIsLoading(isLoading: false, earlier: false, animated: animated)
|
||||||
|
}
|
||||||
|
|
||||||
|
var emptyType: ChatHistoryNodeLoadState.EmptyType?
|
||||||
|
if case let .empty(type) = loadState {
|
||||||
|
if case .botInfo = type {
|
||||||
|
} else {
|
||||||
|
emptyType = type
|
||||||
|
if case .joined = type {
|
||||||
|
if strongSelf.didDisplayEmptyGreeting {
|
||||||
|
emptyType = .generic
|
||||||
|
} else {
|
||||||
|
strongSelf.didDisplayEmptyGreeting = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if case .messages = loadState {
|
||||||
|
strongSelf.didDisplayEmptyGreeting = true
|
||||||
|
}
|
||||||
|
strongSelf.updateIsEmpty(emptyType, wasLoading: wasLoading, animated: animated)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.historyNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
|
||||||
|
|
||||||
|
self.displayVideoUnmuteTipDisposable?.dispose()
|
||||||
|
self.displayVideoUnmuteTipDisposable = (combineLatest(queue: Queue.mainQueue(), ApplicationSpecificNotice.getVolumeButtonToUnmute(accountManager: self.context.sharedContext.accountManager), self.historyNode.hasVisiblePlayableItemNodes, self.historyNode.isInteractivelyScrolling)
|
||||||
|
|> mapToSignal { notice, hasVisiblePlayableItemNodes, isInteractivelyScrolling -> Signal<Bool, NoError> in
|
||||||
|
let display = !notice && hasVisiblePlayableItemNodes && !isInteractivelyScrolling
|
||||||
|
if display {
|
||||||
|
return .complete()
|
||||||
|
|> delay(2.5, queue: Queue.mainQueue())
|
||||||
|
|> then(
|
||||||
|
.single(display)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return .single(display)
|
||||||
|
}
|
||||||
|
}).startStrict(next: { [weak self] display in
|
||||||
|
if let strongSelf = self, let interfaceInteraction = strongSelf.interfaceInteraction {
|
||||||
|
if display {
|
||||||
|
var nodes: [(CGFloat, ChatMessageItemView, ASDisplayNode)] = []
|
||||||
|
var skip = false
|
||||||
|
strongSelf.historyNode.forEachVisibleItemNode { itemNode in
|
||||||
|
if let itemNode = itemNode as? ChatMessageItemView, let (_, soundEnabled, isVideoMessage, _, badgeNode) = itemNode.playMediaWithSound(), let node = badgeNode {
|
||||||
|
if soundEnabled {
|
||||||
|
skip = true
|
||||||
|
} else if !skip && !isVideoMessage, case let .visible(fraction, _) = itemNode.visibility {
|
||||||
|
nodes.insert((fraction, itemNode, node), at: 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (fraction, _, badgeNode) in nodes {
|
||||||
|
if fraction > 0.7 {
|
||||||
|
interfaceInteraction.displayVideoUnmuteTip(badgeNode.view.convert(badgeNode.view.bounds, to: strongSelf.view).origin.offsetBy(dx: 42.0, dy: -1.0))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
interfaceInteraction.displayVideoUnmuteTip(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func chatLocationTabSwitchDirection(from fromLocation: ChatLocation, to toLocation: ChatLocation) -> Bool? {
|
||||||
|
var leftIndex: Int?
|
||||||
|
var rightIndex: Int?
|
||||||
|
if let titleTopicsAccessoryPanelNode = self.titleTopicsAccessoryPanelNode {
|
||||||
|
leftIndex = titleTopicsAccessoryPanelNode.topicIndex(threadId: fromLocation.threadId)
|
||||||
|
rightIndex = titleTopicsAccessoryPanelNode.topicIndex(threadId: toLocation.threadId)
|
||||||
|
} else if let leftPanelView = self.leftPanel?.view.view as? ChatSideTopicsPanel.View {
|
||||||
|
leftIndex = leftPanelView.topicIndex(threadId: fromLocation.threadId)
|
||||||
|
rightIndex = leftPanelView.topicIndex(threadId: toLocation.threadId)
|
||||||
|
}
|
||||||
|
guard let leftIndex, let rightIndex else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return leftIndex < rightIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateChatLocation(chatLocation: ChatLocation, transition: ContainedViewLayoutTransition, tabSwitchDirection: Bool?) {
|
||||||
|
if chatLocation == self.chatLocation {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.chatLocation = chatLocation
|
||||||
|
|
||||||
|
let historyNode = ChatHistoryListNodeImpl(
|
||||||
|
context: self.context,
|
||||||
|
updatedPresentationData: self.controller?.updatedPresentationData ?? (self.context.sharedContext.currentPresentationData.with({ $0 }), self.context.sharedContext.presentationData),
|
||||||
|
chatLocation: chatLocation,
|
||||||
|
chatLocationContextHolder: self.chatLocationContextHolder,
|
||||||
|
tag: nil,
|
||||||
|
source: .default,
|
||||||
|
subject: nil,
|
||||||
|
controllerInteraction: self.controllerInteraction,
|
||||||
|
selectedMessages: self.selectedMessagesPromise.get(),
|
||||||
|
rotated: self.controllerInteraction.chatIsRotated,
|
||||||
|
isChatPreview: false,
|
||||||
|
messageTransitionNode: { [weak self] in
|
||||||
|
return self?.messageTransitionNode
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var getContentAreaInScreenSpaceImpl: (() -> CGRect)?
|
||||||
|
var onTransitionEventImpl: ((ContainedViewLayoutTransition) -> Void)?
|
||||||
|
let messageTransitionNode = ChatMessageTransitionNodeImpl(listNode: historyNode, getContentAreaInScreenSpace: {
|
||||||
|
return getContentAreaInScreenSpaceImpl?() ?? CGRect()
|
||||||
|
}, onTransitionEvent: { transition in
|
||||||
|
onTransitionEventImpl?(transition)
|
||||||
|
})
|
||||||
|
|
||||||
|
getContentAreaInScreenSpaceImpl = { [weak self] in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return CGRect()
|
||||||
|
}
|
||||||
|
|
||||||
|
return strongSelf.view.convert(strongSelf.frameForVisibleArea(), to: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
onTransitionEventImpl = { [weak self] transition in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (strongSelf.context.sharedContext.currentPresentationData.with({ $0 })).reduceMotion {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if strongSelf.context.sharedContext.energyUsageSettings.fullTranslucency {
|
||||||
|
strongSelf.backgroundNode.animateEvent(transition: transition, extendAnimation: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.wrappingNode.contentNode.insertSubnode(messageTransitionNode, aboveSubnode: self.messageTransitionNode)
|
||||||
|
self.messageTransitionNode.removeFromSupernode()
|
||||||
|
self.messageTransitionNode = messageTransitionNode
|
||||||
|
|
||||||
|
let previousHistoryNode = self.historyNode
|
||||||
|
previousHistoryNode.supernode?.insertSubnode(historyNode, aboveSubnode: previousHistoryNode)
|
||||||
|
self.historyNode = historyNode
|
||||||
|
|
||||||
|
self.setupHistoryNode()
|
||||||
|
|
||||||
|
historyNode.position = previousHistoryNode.position
|
||||||
|
historyNode.bounds = previousHistoryNode.bounds
|
||||||
|
historyNode.transform = previousHistoryNode.transform
|
||||||
|
|
||||||
|
if let currentListViewLayout = self.currentListViewLayout {
|
||||||
|
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: currentListViewLayout.size, insets: currentListViewLayout.insets, scrollIndicatorInsets: currentListViewLayout.scrollIndicatorInsets, duration: 0.0, curve: .Default(duration: nil), ensureTopInsetForOverlayHighlightedItems: nil, customAnimationTransition: nil)
|
||||||
|
historyNode.updateLayout(transition: .immediate, updateSizeAndInsets: updateSizeAndInsets, additionalScrollDistance: 0.0, scrollToTop: false, completion: {})
|
||||||
|
}
|
||||||
|
|
||||||
|
if let validLayout = self.validLayout, transition.isAnimated, let tabSwitchDirection {
|
||||||
|
let offsetMultiplier: CGFloat = tabSwitchDirection ? 1.0 : -1.0
|
||||||
|
transition.animatePosition(layer: historyNode.layer, from: CGPoint(x: offsetMultiplier * validLayout.0.size.width, y: 0.0), to: CGPoint(), removeOnCompletion: true, additive: true)
|
||||||
|
transition.animatePosition(layer: previousHistoryNode.layer, from: CGPoint(), to: CGPoint(x: -offsetMultiplier * validLayout.0.size.width, y: 0.0), removeOnCompletion: false, additive: true, completion: { [weak previousHistoryNode] _ in
|
||||||
|
previousHistoryNode?.removeFromSupernode()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
previousHistoryNode.removeFromSupernode()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1233,14 +1233,6 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
|
|||||||
self.beginChatHistoryTransitions(resetScrolling: true, switchedToAnotherSource: false)
|
self.beginChatHistoryTransitions(resetScrolling: true, switchedToAnotherSource: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func updateChatLocation(chatLocation: ChatLocation) {
|
|
||||||
if self.chatLocation == chatLocation {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
self.chatLocation = chatLocation
|
|
||||||
self.beginChatHistoryTransitions(resetScrolling: false, switchedToAnotherSource: true)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func beginAdMessageManagement(adMessages: Signal<(interPostInterval: Int32?, messages: [Message]), NoError>) {
|
private func beginAdMessageManagement(adMessages: Signal<(interPostInterval: Int32?, messages: [Message]), NoError>) {
|
||||||
self.adMessagesDisposable = (adMessages
|
self.adMessagesDisposable = (adMessages
|
||||||
|> deliverOnMainQueue).startStrict(next: { [weak self] interPostInterval, messages in
|
|> deliverOnMainQueue).startStrict(next: { [weak self] interPostInterval, messages in
|
||||||
@ -2362,7 +2354,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
|
|||||||
strongSelf.dynamicBounceEnabled = false
|
strongSelf.dynamicBounceEnabled = false
|
||||||
|
|
||||||
strongSelf.forEachItemHeaderNode { itemHeaderNode in
|
strongSelf.forEachItemHeaderNode { itemHeaderNode in
|
||||||
if let dateNode = itemHeaderNode as? ChatMessageDateHeaderNode {
|
if let dateNode = itemHeaderNode as? ChatMessageDateHeaderNodeImpl {
|
||||||
dateNode.updatePresentationData(chatPresentationData, context: strongSelf.context)
|
dateNode.updatePresentationData(chatPresentationData, context: strongSelf.context)
|
||||||
} else if let avatarNode = itemHeaderNode as? ChatMessageAvatarHeaderNodeImpl {
|
} else if let avatarNode = itemHeaderNode as? ChatMessageAvatarHeaderNodeImpl {
|
||||||
avatarNode.updatePresentationData(chatPresentationData, context: strongSelf.context)
|
avatarNode.updatePresentationData(chatPresentationData, context: strongSelf.context)
|
||||||
|
@ -231,8 +231,8 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
|
|||||||
|
|
||||||
if channel.flags.contains(.isMonoforum) {
|
if channel.flags.contains(.isMonoforum) {
|
||||||
if channel.adminRights != nil, case .peer = chatPresentationInterfaceState.chatLocation {
|
if channel.adminRights != nil, case .peer = chatPresentationInterfaceState.chatLocation {
|
||||||
|
if chatPresentationInterfaceState.interfaceState.replyMessageSubject == nil {
|
||||||
displayInputTextPanel = false
|
displayInputTextPanel = false
|
||||||
|
|
||||||
if let currentPanel = (currentPanel as? ChatRestrictedInputPanelNode) ?? (currentSecondaryPanel as? ChatRestrictedInputPanelNode) {
|
if let currentPanel = (currentPanel as? ChatRestrictedInputPanelNode) ?? (currentSecondaryPanel as? ChatRestrictedInputPanelNode) {
|
||||||
return (currentPanel, nil)
|
return (currentPanel, nil)
|
||||||
} else {
|
} else {
|
||||||
@ -241,6 +241,7 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
|
|||||||
panel.interfaceInteraction = interfaceInteraction
|
panel.interfaceInteraction = interfaceInteraction
|
||||||
return (panel, nil)
|
return (panel, nil)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
displayInputTextPanel = true
|
displayInputTextPanel = true
|
||||||
}
|
}
|
||||||
|
@ -90,8 +90,7 @@ final class ChatRestrictedInputPanelNode: ChatInputPanelNode {
|
|||||||
accountFreezeConfiguration = AccountFreezeConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
|
accountFreezeConfiguration = AccountFreezeConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
|
||||||
}
|
}
|
||||||
if let channel = interfaceState.renderedPeer?.chatMainPeer as? TelegramChannel, channel.isMonoForum {
|
if let channel = interfaceState.renderedPeer?.chatMainPeer as? TelegramChannel, channel.isMonoForum {
|
||||||
//TODO:localize
|
self.textNode.attributedText = NSAttributedString(string: interfaceState.strings.Chat_PanelForumModeReplyText, font: Font.regular(15.0), textColor: interfaceState.theme.chat.inputPanel.secondaryTextColor)
|
||||||
self.textNode.attributedText = NSAttributedString(string: "Choose a thread to reply", font: Font.regular(13.0), textColor: interfaceState.theme.chat.inputPanel.secondaryTextColor)
|
|
||||||
} else if let _ = accountFreezeConfiguration?.freezeUntilDate {
|
} else if let _ = accountFreezeConfiguration?.freezeUntilDate {
|
||||||
self.textNode.attributedText = NSAttributedString(string: interfaceState.strings.Chat_PanelFrozenAccount_Title, font: Font.semibold(15.0), textColor: interfaceState.theme.list.itemDestructiveColor)
|
self.textNode.attributedText = NSAttributedString(string: interfaceState.strings.Chat_PanelFrozenAccount_Title, font: Font.semibold(15.0), textColor: interfaceState.theme.list.itemDestructiveColor)
|
||||||
self.subtitleNode.attributedText = NSAttributedString(string: interfaceState.strings.Chat_PanelFrozenAccount_Text, font: Font.regular(13.0), textColor: interfaceState.theme.chat.inputPanel.secondaryTextColor)
|
self.subtitleNode.attributedText = NSAttributedString(string: interfaceState.strings.Chat_PanelFrozenAccount_Text, font: Font.regular(13.0), textColor: interfaceState.theme.chat.inputPanel.secondaryTextColor)
|
||||||
|
@ -868,4 +868,16 @@ final class ChatTopicListTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, C
|
|||||||
transition.setTransform(view: tabItemView, transform: CATransform3DMakeTranslation(0.0, -globalOffset, 0.0))
|
transition.setTransform(view: tabItemView, transform: CATransform3DMakeTranslation(0.0, -globalOffset, 0.0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func topicIndex(threadId: Int64?) -> Int? {
|
||||||
|
if let threadId {
|
||||||
|
if let value = self.items.firstIndex(where: { $0.id == .chatList(PeerId(threadId)) }) {
|
||||||
|
return value + 1
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2409,7 +2409,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func makeChatMessageDateHeaderItem(context: AccountContext, timestamp: Int32, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, dateTimeFormat: PresentationDateTimeFormat, nameOrder: PresentationPersonNameOrder) -> ListViewItemHeader {
|
public func makeChatMessageDateHeaderItem(context: AccountContext, timestamp: Int32, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, dateTimeFormat: PresentationDateTimeFormat, nameOrder: PresentationPersonNameOrder) -> ListViewItemHeader {
|
||||||
return ChatMessageDateHeader(timestamp: timestamp, scheduled: false, 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), controllerInteraction: nil, context: context)
|
return ChatMessageDateHeader(timestamp: timestamp, separableThreadId: nil, scheduled: false, displayPeer: nil, 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), controllerInteraction: nil, context: context)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func makeChatMessageAvatarHeaderItem(context: AccountContext, timestamp: Int32, peer: Peer, message: Message, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, dateTimeFormat: PresentationDateTimeFormat, nameOrder: PresentationPersonNameOrder) -> ListViewItemHeader {
|
public func makeChatMessageAvatarHeaderItem(context: AccountContext, timestamp: Int32, peer: Peer, message: Message, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, dateTimeFormat: PresentationDateTimeFormat, nameOrder: PresentationPersonNameOrder) -> ListViewItemHeader {
|
||||||
|
@ -297,7 +297,7 @@ final class PeerNameColorChatPreviewItemNode: ListViewItemNode {
|
|||||||
headerNode.item = header
|
headerNode.item = header
|
||||||
}
|
}
|
||||||
headerNode.updateLayoutInternal(size: headerFrame.size, leftInset: leftInset, rightInset: rightInset, transition: .immediate)
|
headerNode.updateLayoutInternal(size: headerFrame.size, leftInset: leftInset, rightInset: rightInset, transition: .immediate)
|
||||||
headerNode.updateStickDistanceFactor(stickLocationDistanceFactor, transition: .immediate)
|
headerNode.updateStickDistanceFactor(stickLocationDistanceFactor, distance: 0.0, transition: .immediate)
|
||||||
} else {
|
} else {
|
||||||
headerNode = header.node(synchronousLoad: true)
|
headerNode = header.node(synchronousLoad: true)
|
||||||
if headerNode.item !== header {
|
if headerNode.item !== header {
|
||||||
@ -309,7 +309,7 @@ final class PeerNameColorChatPreviewItemNode: ListViewItemNode {
|
|||||||
strongSelf.itemHeaderNodes[id] = headerNode
|
strongSelf.itemHeaderNodes[id] = headerNode
|
||||||
|
|
||||||
strongSelf.containerNode.addSubnode(headerNode)
|
strongSelf.containerNode.addSubnode(headerNode)
|
||||||
headerNode.updateStickDistanceFactor(stickLocationDistanceFactor, transition: .immediate)
|
headerNode.updateStickDistanceFactor(stickLocationDistanceFactor, distance: 0.0, transition: .immediate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"app": "11.11",
|
"app": "11.11",
|
||||||
"xcode": "16.2",
|
"xcode": "16.3",
|
||||||
"bazel": "8.2.1:22ff65b05869f6160e5157b1b425a14a62085d71d8baef571f462b8fe5a703a3",
|
"bazel": "8.2.1:22ff65b05869f6160e5157b1b425a14a62085d71d8baef571f462b8fe5a703a3",
|
||||||
"macos": "15"
|
"macos": "15"
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user