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 let id: ListViewItemNode.HeaderId
|
||||
public let stackingId: ListViewItemNode.HeaderId? = nil
|
||||
public let type: ChatListSearchItemHeaderType
|
||||
public let stickDirection: ListViewItemHeaderStickDirection = .top
|
||||
public let stickOverInsets: Bool = true
|
||||
|
@ -6,6 +6,7 @@ import ListSectionHeaderNode
|
||||
|
||||
final class ContactListNameIndexHeader: Equatable, ListViewItemHeader {
|
||||
let id: ListViewItemNode.HeaderId
|
||||
let stackingId: ListViewItemNode.HeaderId? = nil
|
||||
let theme: PresentationTheme
|
||||
let letter: unichar
|
||||
let stickDirection: ListViewItemHeaderStickDirection = .top
|
||||
|
@ -9,6 +9,11 @@ private let insertionAnimationDuration: Double = 0.4
|
||||
private struct VisibleHeaderNodeId: Hashable {
|
||||
var id: ListViewItemNode.HeaderId
|
||||
var affinity: Int
|
||||
|
||||
init(id: ListViewItemNode.HeaderId, affinity: Int) {
|
||||
self.id = id
|
||||
self.affinity = affinity
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
let itemHeaderHeight: CGFloat = item.height
|
||||
|
||||
let headerFrame: CGRect
|
||||
let stickLocationDistanceFactor: CGFloat
|
||||
let stickLocationDistance: CGFloat
|
||||
var insertItemBelowOtherHeaders = false
|
||||
var offsetByHeaderNodeId: ListViewItemNode.HeaderId?
|
||||
var didOffsetByHeaderNode = false
|
||||
|
||||
var headerFrame: CGRect
|
||||
let naturalY: CGFloat
|
||||
var stickLocationDistanceFactor: CGFloat = 0.0
|
||||
var stickLocationDistance: CGFloat
|
||||
switch item.stickDirection {
|
||||
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))
|
||||
stickLocationDistance = headerFrame.minY - upperBound
|
||||
stickLocationDistanceFactor = max(0.0, min(1.0, stickLocationDistance / itemHeaderHeight))
|
||||
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))
|
||||
stickLocationDistance = headerFrame.minY - upperBoundEdge + itemHeaderHeight
|
||||
stickLocationDistanceFactor = max(0.0, min(1.0, stickLocationDistance / itemHeaderHeight))
|
||||
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))
|
||||
stickLocationDistance = lowerBound - headerFrame.maxY
|
||||
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)
|
||||
|
||||
let initialHeaderNodeAlpha = self.itemHeaderNodesAlpha
|
||||
@ -3896,7 +3962,14 @@ open class ListView: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDel
|
||||
headerNode = current
|
||||
switch transition.0 {
|
||||
case .immediate:
|
||||
let previousFrame = headerNode.frame
|
||||
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):
|
||||
let previousFrame = headerNode.frame
|
||||
headerNode.updateFrame(headerFrame, within: self.visibleSize)
|
||||
@ -3927,7 +4000,7 @@ open class ListView: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDel
|
||||
item.updateNode(headerNode, previous: nil, next: nil)
|
||||
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.internalStickLocationDistance = stickLocationDistance
|
||||
if !hasValidNodes && !headerNode.alpha.isZero {
|
||||
@ -3940,7 +4013,7 @@ open class ListView: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDel
|
||||
headerNode.animateAdded(duration: 0.2)
|
||||
}
|
||||
}
|
||||
headerNode.updateStickDistanceFactor(stickLocationDistanceFactor, transition: transition.0)
|
||||
headerNode.updateStickDistanceFactor(stickLocationDistanceFactor, distance: stickLocationDistance, transition: transition.0)
|
||||
} else {
|
||||
headerNode = item.node(synchronousLoad: synchronousLoad)
|
||||
headerNode.alpha = initialHeaderNodeAlpha
|
||||
@ -3950,10 +4023,28 @@ open class ListView: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDel
|
||||
}
|
||||
headerNode.updateFlashingOnScrolling(flashing, animated: false)
|
||||
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)
|
||||
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)
|
||||
} else {
|
||||
self.addSubnode(headerNode)
|
||||
@ -3962,8 +4053,10 @@ open class ListView: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDel
|
||||
headerNode.alpha = initialHeaderNodeAlpha
|
||||
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)?
|
||||
for i in upperIndex ... lowerIndex {
|
||||
let itemNode = self.itemNodes[i]
|
||||
@ -4008,54 +4101,66 @@ 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)] = [:]
|
||||
|
||||
for i in 0 ..< self.itemNodes.count {
|
||||
let itemNode = self.itemNodes[i]
|
||||
let itemFrame = itemNode.apparentFrame
|
||||
let itemTopInset = itemNode.insets.top
|
||||
var validItemHeaderSpaces: [AnyHashable] = []
|
||||
if let itemHeaders = itemNode.headers() {
|
||||
for itemHeader in itemHeaders {
|
||||
guard let affinity = itemNode.headerSpaceAffinities[itemHeader.id] else {
|
||||
assertionFailure()
|
||||
for phase in 0 ..< 2 {
|
||||
for i in 0 ..< self.itemNodes.count {
|
||||
let itemNode = self.itemNodes[i]
|
||||
let itemFrame = itemNode.apparentFrame
|
||||
let itemTopInset = itemNode.insets.top
|
||||
var validItemHeaderSpaces: [AnyHashable] = []
|
||||
if let itemHeaders = itemNode.headers() {
|
||||
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 {
|
||||
assertionFailure()
|
||||
continue
|
||||
}
|
||||
|
||||
let headerId = VisibleHeaderNodeId(id: itemHeader.id, affinity: affinity)
|
||||
|
||||
validItemHeaderSpaces.append(itemHeader.id.space)
|
||||
|
||||
var itemMaxY: CGFloat
|
||||
if itemHeader.stickOverInsets {
|
||||
itemMaxY = itemFrame.maxY
|
||||
} else {
|
||||
itemMaxY = itemFrame.maxY - (self.rotated ? itemNode.insets.top : itemNode.insets.bottom)
|
||||
}
|
||||
|
||||
if let (previousHeaderId, previousUpperBound, previousUpperIndex, previousUpperBoundEdge, previousLowerBound, previousLowerIndex, previousHeaderItem, hasValidNodes) = previousHeaderBySpace[itemHeader.id.space] {
|
||||
if previousHeaderId == headerId {
|
||||
previousHeaderBySpace[itemHeader.id.space] = (previousHeaderId, previousUpperBound, previousUpperIndex, previousUpperBoundEdge, itemMaxY, i, previousHeaderItem, hasValidNodes || itemNode.index != nil)
|
||||
} else {
|
||||
addHeader(id: previousHeaderId, upperBound: previousUpperBound, upperIndex: previousUpperIndex, upperBoundEdge: previousUpperBoundEdge, lowerBound: previousLowerBound, lowerIndex: previousLowerIndex, item: previousHeaderItem, hasValidNodes: hasValidNodes)
|
||||
|
||||
previousHeaderBySpace[itemHeader.id.space] = (headerId, itemFrame.minY, i, itemFrame.minY + itemTopInset, itemMaxY, i, itemHeader, itemNode.index != nil)
|
||||
}
|
||||
} else {
|
||||
previousHeaderBySpace[itemHeader.id.space] = (headerId, itemFrame.minY, i, itemFrame.minY + itemTopInset, itemMaxY, i, itemHeader, itemNode.index != nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (space, previousHeader) in previousHeaderBySpace {
|
||||
if validItemHeaderSpaces.contains(space) {
|
||||
continue
|
||||
}
|
||||
|
||||
let headerId = VisibleHeaderNodeId(id: itemHeader.id, affinity: affinity)
|
||||
let (previousHeaderId, previousUpperBound, previousUpperIndex, previousUpperBoundEdge, previousLowerBound, previousLowerIndex, previousHeaderItem, hasValidNodes) = previousHeader
|
||||
|
||||
validItemHeaderSpaces.append(itemHeader.id.space)
|
||||
addHeader(id: previousHeaderId, upperBound: previousUpperBound, upperIndex: previousUpperIndex, upperBoundEdge: previousUpperBoundEdge, lowerBound: previousLowerBound, lowerIndex: previousLowerIndex, item: previousHeaderItem, hasValidNodes: hasValidNodes)
|
||||
|
||||
let itemMaxY: CGFloat
|
||||
if itemHeader.stickOverInsets {
|
||||
itemMaxY = itemFrame.maxY
|
||||
} else {
|
||||
itemMaxY = itemFrame.maxY - (self.rotated ? itemNode.insets.top : itemNode.insets.bottom)
|
||||
}
|
||||
|
||||
if let (previousHeaderId, previousUpperBound, previousUpperIndex, previousUpperBoundEdge, previousLowerBound, previousLowerIndex, previousHeaderItem, hasValidNodes) = previousHeaderBySpace[itemHeader.id.space] {
|
||||
if previousHeaderId == headerId {
|
||||
previousHeaderBySpace[itemHeader.id.space] = (previousHeaderId, previousUpperBound, previousUpperIndex, previousUpperBoundEdge, itemMaxY, i, previousHeaderItem, hasValidNodes || itemNode.index != nil)
|
||||
} else {
|
||||
addHeader(id: previousHeaderId, upperBound: previousUpperBound, upperIndex: previousUpperIndex, upperBoundEdge: previousUpperBoundEdge, lowerBound: previousLowerBound, lowerIndex: previousLowerIndex, item: previousHeaderItem, hasValidNodes: hasValidNodes)
|
||||
|
||||
previousHeaderBySpace[itemHeader.id.space] = (headerId, itemFrame.minY, i, itemFrame.minY + itemTopInset, itemMaxY, i, itemHeader, itemNode.index != nil)
|
||||
}
|
||||
} else {
|
||||
previousHeaderBySpace[itemHeader.id.space] = (headerId, itemFrame.minY, i, itemFrame.minY + itemTopInset, itemMaxY, i, itemHeader, itemNode.index != nil)
|
||||
}
|
||||
previousHeaderBySpace.removeValue(forKey: space)
|
||||
}
|
||||
}
|
||||
|
||||
for (space, previousHeader) in previousHeaderBySpace {
|
||||
if validItemHeaderSpaces.contains(space) {
|
||||
continue
|
||||
}
|
||||
|
||||
let (previousHeaderId, previousUpperBound, previousUpperIndex, previousUpperBoundEdge, previousLowerBound, previousLowerIndex, previousHeaderItem, hasValidNodes) = previousHeader
|
||||
|
||||
addHeader(id: previousHeaderId, upperBound: previousUpperBound, upperIndex: previousUpperIndex, upperBoundEdge: previousUpperBoundEdge, lowerBound: previousLowerBound, lowerIndex: previousLowerIndex, item: previousHeaderItem, hasValidNodes: hasValidNodes)
|
||||
|
||||
previousHeaderBySpace.removeValue(forKey: space)
|
||||
}
|
||||
}
|
||||
|
||||
for (space, previousHeader) in previousHeaderBySpace {
|
||||
|
@ -10,6 +10,7 @@ public enum ListViewItemHeaderStickDirection {
|
||||
|
||||
public protocol ListViewItemHeader: AnyObject {
|
||||
var id: ListViewItemNode.HeaderId { get }
|
||||
var stackingId: ListViewItemNode.HeaderId? { get }
|
||||
var stickDirection: ListViewItemHeaderStickDirection { get }
|
||||
var height: CGFloat { get }
|
||||
var stickOverInsets: Bool { get }
|
||||
@ -29,6 +30,9 @@ open class ListViewItemHeaderNode: ASDisplayNode {
|
||||
private var isFlashingOnScrolling = false
|
||||
weak var attachedToItemNode: ListViewItemNode?
|
||||
|
||||
var offsetByHeaderNodeId: ListViewItemNode.HeaderId?
|
||||
var naturalOriginY: CGFloat?
|
||||
|
||||
public var item: ListViewItemHeader?
|
||||
|
||||
func updateInternalStickLocationDistanceFactor(_ factor: CGFloat, animated: Bool) {
|
||||
@ -61,7 +65,7 @@ open class ListViewItemHeaderNode: ASDisplayNode {
|
||||
self.isLayerBacked = layerBacked
|
||||
}
|
||||
|
||||
open func updateStickDistanceFactor(_ factor: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
open func updateStickDistanceFactor(_ factor: CGFloat, distance: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
}
|
||||
|
||||
final func addScrollingOffset(_ scrollingOffset: CGFloat) {
|
||||
|
@ -2045,6 +2045,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
|
||||
|
||||
public final class ItemListPeerItemHeader: ListViewItemHeader {
|
||||
public let id: ListViewItemNode.HeaderId
|
||||
public let stackingId: ListViewItemNode.HeaderId? = nil
|
||||
public let context: AccountContext
|
||||
public let text: NSAttributedString
|
||||
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)
|
||||
}
|
||||
|
||||
override public func updateStickDistanceFactor(_ factor: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
override public func updateStickDistanceFactor(_ factor: CGFloat, distance: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
if self.stickDistanceFactor == factor {
|
||||
return
|
||||
}
|
||||
|
@ -42,6 +42,7 @@ final class ListMessageDateHeader: ListViewItemHeader {
|
||||
private let year: Int32
|
||||
|
||||
let id: ListViewItemNode.HeaderId
|
||||
let stackingId: ListViewItemNode.HeaderId? = nil
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
let fontSize: PresentationFontSize
|
||||
|
@ -2776,6 +2776,10 @@ final class MessageHistoryTable: Table {
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -498,6 +498,7 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch message {
|
||||
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) {
|
||||
|
@ -583,9 +583,6 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee
|
||||
}
|
||||
}
|
||||
} 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))
|
||||
|> map(Optional.init)
|
||||
|> `catch` { error -> Signal<Api.messages.ChatFull?, NoError> in
|
||||
@ -594,10 +591,17 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee
|
||||
}
|
||||
return .single(nil)
|
||||
}
|
||||
let participantSignal = network.request(Api.functions.channels.getParticipant(channel: inputChannel, participant: .inputPeerSelf))
|
||||
|> map(Optional.init)
|
||||
|> `catch` { error -> Signal<Api.channels.ChannelParticipant?, NoError> in
|
||||
return .single(nil)
|
||||
|
||||
|
||||
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)
|
||||
|> `catch` { error -> Signal<Api.channels.ChannelParticipant?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
}
|
||||
|
||||
return combineLatest(fullChannelSignal, participantSignal)
|
||||
|
@ -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)
|
||||
let telegramFile = self.telegramFile
|
||||
let emojiFile = self.emojiFile
|
||||
@ -823,7 +823,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
|
||||
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 layoutConstants = chatMessageItemLayoutConstants(layoutConstants, params: params, presentationData: item.presentationData)
|
||||
let incoming = item.content.effectivelyIncoming(item.context.account.peerId, associatedData: item.associatedData)
|
||||
@ -1005,8 +1005,15 @@ 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)
|
||||
if dateHeaderAtBottom {
|
||||
layoutInsets.top += layoutConstants.timestampHeaderHeight
|
||||
if dateHeaderAtBottom.hasDate && dateHeaderAtBottom.hasTopic {
|
||||
layoutInsets.top += layoutConstants.timestampDateAndTopicHeaderHeight
|
||||
} else {
|
||||
if dateHeaderAtBottom.hasDate {
|
||||
layoutInsets.top += layoutConstants.timestampHeaderHeight
|
||||
}
|
||||
if dateHeaderAtBottom.hasTopic {
|
||||
layoutInsets.top += layoutConstants.timestampHeaderHeight
|
||||
}
|
||||
}
|
||||
|
||||
var deliveryFailedInset: CGFloat = 0.0
|
||||
@ -1377,6 +1384,8 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
strongSelf.appliedForwardInfo = (forwardSource, forwardAuthorSignature)
|
||||
strongSelf.updateAccessibilityData(accessibilityData)
|
||||
|
||||
strongSelf.updateAttachedDateHeader(hasDate: dateHeaderAtBottom.hasDate, hasPeer: dateHeaderAtBottom.hasTopic)
|
||||
|
||||
strongSelf.messageAccessibilityArea.frame = CGRect(origin: CGPoint(), size: layoutSize)
|
||||
strongSelf.containerNode.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)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -728,7 +728,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
|
||||
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 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))))] = []
|
||||
for contentNode in self.contentNodes {
|
||||
if let message = contentNode.item?.message {
|
||||
@ -1431,7 +1431,13 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
|
||||
return { item, params, mergedTop, mergedBottom, dateHeaderAtBottom in
|
||||
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,
|
||||
authorNameLayout: authorNameLayout,
|
||||
viaMeasureLayout: viaMeasureLayout,
|
||||
@ -1456,11 +1462,11 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
|
||||
private static func beginLayout(
|
||||
selfReference: Weak<ChatMessageBubbleItemNode>,
|
||||
_ item: ChatMessageItem,
|
||||
_ params: ListViewItemLayoutParams,
|
||||
_ mergedTop: ChatMessageMerge,
|
||||
_ mergedBottom: ChatMessageMerge,
|
||||
_ dateHeaderAtBottom: Bool,
|
||||
item: ChatMessageItem,
|
||||
params: ListViewItemLayoutParams,
|
||||
mergedTop: ChatMessageMerge,
|
||||
mergedBottom: ChatMessageMerge,
|
||||
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))))],
|
||||
authorNameLayout: (TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode),
|
||||
viaMeasureLayout: (TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode),
|
||||
@ -2620,7 +2626,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
}
|
||||
|
||||
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
|
||||
} 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 {
|
||||
@ -3219,11 +3225,18 @@ 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)
|
||||
if dateHeaderAtBottom {
|
||||
layoutInsets.top += layoutConstants.timestampHeaderHeight
|
||||
}
|
||||
if isAd {
|
||||
layoutInsets.top += 4.0
|
||||
if dateHeaderAtBottom.hasDate && dateHeaderAtBottom.hasTopic {
|
||||
layoutInsets.top += layoutConstants.timestampDateAndTopicHeaderHeight
|
||||
} else {
|
||||
if dateHeaderAtBottom.hasDate {
|
||||
layoutInsets.top += layoutConstants.timestampHeaderHeight
|
||||
}
|
||||
if dateHeaderAtBottom.hasTopic {
|
||||
layoutInsets.top += layoutConstants.timestampHeaderHeight
|
||||
}
|
||||
if isAd {
|
||||
layoutInsets.top += 4.0
|
||||
}
|
||||
}
|
||||
|
||||
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.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())
|
||||
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 makeVideoLayout = self.interactiveVideoNode.asyncLayout()
|
||||
@ -296,7 +296,7 @@ public class ChatMessageInstantVideoItemNode: ChatMessageItemView, ASGestureReco
|
||||
let currentForwardInfo = self.appliedForwardInfo
|
||||
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 layoutConstants = chatMessageItemLayoutConstants(layoutConstants, params: params, presentationData: item.presentationData)
|
||||
@ -391,8 +391,15 @@ public class ChatMessageInstantVideoItemNode: ChatMessageItemView, ASGestureReco
|
||||
}
|
||||
|
||||
var layoutInsets = layoutConstants.instantVideo.insets
|
||||
if dateHeaderAtBottom {
|
||||
layoutInsets.top += layoutConstants.timestampHeaderHeight
|
||||
if dateHeaderAtBottom.hasDate && dateHeaderAtBottom.hasTopic {
|
||||
layoutInsets.top += layoutConstants.timestampDateAndTopicHeaderHeight
|
||||
} else {
|
||||
if dateHeaderAtBottom.hasDate {
|
||||
layoutInsets.top += layoutConstants.timestampHeaderHeight
|
||||
}
|
||||
if dateHeaderAtBottom.hasTopic {
|
||||
layoutInsets.top += layoutConstants.timestampHeaderHeight
|
||||
}
|
||||
}
|
||||
|
||||
var deliveryFailedInset: CGFloat = 0.0
|
||||
|
@ -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 {
|
||||
func updateSelectionState(animated: Bool)
|
||||
func updateAvatarIsHidden(isHidden: Bool, transition: ContainedViewLayoutTransition)
|
||||
@ -128,7 +142,7 @@ public protocol ChatMessageItem: ListViewItem {
|
||||
var sending: 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 {
|
||||
|
@ -108,6 +108,7 @@ public struct ChatMessageItemWallpaperLayoutConstants {
|
||||
public struct ChatMessageItemLayoutConstants {
|
||||
public var avatarDiameter: CGFloat
|
||||
public var timestampHeaderHeight: CGFloat
|
||||
public var timestampDateAndTopicHeaderHeight: CGFloat
|
||||
|
||||
public var bubble: ChatMessageItemBubbleLayoutConstants
|
||||
public var image: ChatMessageItemImageLayoutConstants
|
||||
@ -117,9 +118,10 @@ public struct ChatMessageItemLayoutConstants {
|
||||
public var instantVideo: ChatMessageItemInstantVideoConstants
|
||||
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.timestampHeaderHeight = timestampHeaderHeight
|
||||
self.timestampDateAndTopicHeaderHeight = timestampDateAndTopicHeaderHeight
|
||||
self.bubble = bubble
|
||||
self.image = image
|
||||
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 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 {
|
||||
@ -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 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 AvatarVideoNode
|
||||
import ChatMessageItem
|
||||
import AvatarNode
|
||||
|
||||
private let timezoneOffset: Int32 = {
|
||||
let nowTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
|
||||
@ -28,25 +29,55 @@ private let timezoneOffset: Int32 = {
|
||||
private let granularity: Int32 = 60 * 60 * 24
|
||||
|
||||
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 roundedTimestamp: Int32
|
||||
private let scheduled: Bool
|
||||
public let displayPeer: PeerData?
|
||||
|
||||
public let id: ListViewItemNode.HeaderId
|
||||
public let stackingId: ListViewItemNode.HeaderId?
|
||||
public let idValue: Id
|
||||
public let presentationData: ChatPresentationData
|
||||
public let controllerInteraction: ChatControllerInteraction?
|
||||
public let context: AccountContext
|
||||
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.scheduled = scheduled
|
||||
self.displayPeer = displayPeer
|
||||
self.presentationData = presentationData
|
||||
self.controllerInteraction = controllerInteraction
|
||||
self.context = context
|
||||
self.action = action
|
||||
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
|
||||
|
||||
@ -67,11 +98,11 @@ public final class ChatMessageDateHeader: ListViewItemHeader {
|
||||
}
|
||||
|
||||
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?) {
|
||||
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
|
||||
}
|
||||
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 backgroundNode: NavigationBackgroundNode
|
||||
public let stickBackgroundNode: ASImageNode
|
||||
|
||||
private var backgroundContent: WallpaperBubbleBackgroundNode?
|
||||
private var text: String
|
||||
|
||||
private let localTimestamp: Int32
|
||||
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) {
|
||||
init(presentationData: ChatPresentationData, controllerInteraction: ChatControllerInteraction?, localTimestamp: Int32, scheduled: Bool) {
|
||||
self.presentationData = presentationData
|
||||
self.controllerInteraction = controllerInteraction
|
||||
self.context = context
|
||||
|
||||
self.localTimestamp = localTimestamp
|
||||
self.action = action
|
||||
|
||||
self.labelNode = TextNode()
|
||||
self.labelNode.isUserInteractionEnabled = false
|
||||
@ -195,13 +265,7 @@ public final class ChatMessageDateHeaderNode: ListViewItemHeaderNode {
|
||||
}
|
||||
self.text = text
|
||||
|
||||
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)
|
||||
}
|
||||
super.init()
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
override public func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
self.view.addGestureRecognizer(ListViewTapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
|
||||
}
|
||||
|
||||
public func updatePresentationData(_ presentationData: ChatPresentationData, context: AccountContext) {
|
||||
func updatePresentationData(_ presentationData: ChatPresentationData, context: AccountContext) {
|
||||
let previousPresentationData = self.presentationData
|
||||
self.presentationData = presentationData
|
||||
|
||||
@ -256,32 +314,21 @@ public final class ChatMessageDateHeaderNode: ListViewItemHeaderNode {
|
||||
if presentationData.fontSize != previousPresentationData.fontSize {
|
||||
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)
|
||||
}
|
||||
|
||||
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) {
|
||||
func update(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
let chatDateSize: CGFloat = 20.0
|
||||
let chatDateInset: CGFloat = 6.0
|
||||
|
||||
let labelSize = self.labelNode.bounds.size
|
||||
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.backgroundNode, frame: backgroundFrame)
|
||||
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)
|
||||
backgroundContent.cornerRadius = backgroundFrame.size.height / 2.0
|
||||
|
||||
if let (rect, containerSize) = self.absolutePosition {
|
||||
/*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
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
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 isZero = factor < 0.5
|
||||
@ -322,6 +664,10 @@ public final class ChatMessageDateHeaderNode: ListViewItemHeaderNode {
|
||||
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) {
|
||||
@ -333,34 +679,82 @@ public final class ChatMessageDateHeaderNode: ListViewItemHeaderNode {
|
||||
let flashing = self.flashingOnScrolling || self.stickDistanceFactor < 0.5
|
||||
|
||||
let alpha: CGFloat = flashing ? 1.0 : 0.0
|
||||
let previousAlpha = self.backgroundNode.alpha
|
||||
|
||||
if !previousAlpha.isEqual(to: alpha) {
|
||||
self.backgroundContent?.alpha = alpha
|
||||
self.backgroundNode.alpha = alpha
|
||||
self.labelNode.alpha = alpha
|
||||
if animated {
|
||||
let duration: Double = flashing ? 0.3 : 0.4
|
||||
self.backgroundContent?.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 dateContentNode = self.dateContentNode {
|
||||
let previousAlpha = dateContentNode.alpha
|
||||
|
||||
if !previousAlpha.isEqual(to: alpha) {
|
||||
dateContentNode.alpha = alpha
|
||||
if animated {
|
||||
let duration: Double = flashing ? 0.3 : 0.4
|
||||
dateContentNode.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 {
|
||||
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? {
|
||||
if !self.bounds.contains(point) {
|
||||
return nil
|
||||
}
|
||||
if self.labelNode.alpha.isZero {
|
||||
return nil
|
||||
if let dateContentNode = self.dateContentNode {
|
||||
if dateContentNode.frame.contains(point) {
|
||||
if dateContentNode.alpha.isZero {
|
||||
return nil
|
||||
}
|
||||
|
||||
if dateContentNode.backgroundNode.frame.contains(point.offsetBy(dx: -dateContentNode.frame.minX, dy: -dateContentNode.frame.minY)) {
|
||||
return self.view
|
||||
}
|
||||
}
|
||||
}
|
||||
if self.backgroundNode.frame.contains(point) {
|
||||
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
|
||||
}
|
||||
@ -383,6 +777,7 @@ public final class ChatMessageAvatarHeader: ListViewItemHeader {
|
||||
}
|
||||
|
||||
public let id: ListViewItemNode.HeaderId
|
||||
public let stackingId: ListViewItemNode.HeaderId? = nil
|
||||
public let peerId: PeerId
|
||||
public let peer: Peer?
|
||||
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)
|
||||
}
|
||||
|
||||
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) {
|
||||
|
@ -220,6 +220,7 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible
|
||||
public let additionalContent: ChatMessageItemAdditionalContent?
|
||||
|
||||
let dateHeader: ChatMessageDateHeader
|
||||
let topicHeader: ChatMessageDateHeader?
|
||||
let avatarHeader: ChatMessageAvatarHeader?
|
||||
|
||||
public let headers: [ListViewItemHeader]
|
||||
@ -286,6 +287,8 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible
|
||||
var displayAuthorInfo: Bool
|
||||
|
||||
let messagePeerId: PeerId = chatLocation.peerId ?? content.firstMessage.id.peerId
|
||||
var headerSeparableThreadId: Int64?
|
||||
var headerDisplayPeer: ChatMessageDateHeader.PeerData?
|
||||
|
||||
do {
|
||||
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 case .replyThread = chatLocation {
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
calendar.timeZone = TimeZone(abbreviation: "UTC")!
|
||||
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 {
|
||||
let message = content.firstMessage
|
||||
var hasActionMedia = false
|
||||
@ -395,6 +412,9 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible
|
||||
var headers: [ListViewItemHeader] = []
|
||||
if !self.disableDate {
|
||||
headers.append(self.dateHeader)
|
||||
if let topicHeader = self.topicHeader {
|
||||
headers.append(topicHeader)
|
||||
}
|
||||
}
|
||||
if case .messageOptions = associatedData.subject {
|
||||
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.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 bottom = bottom
|
||||
if !isRotated {
|
||||
@ -537,7 +557,7 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible
|
||||
|
||||
var mergedTop: ChatMessageMerge = .none
|
||||
var mergedBottom: ChatMessageMerge = .none
|
||||
var dateAtBottom = false
|
||||
var dateAtBottom = ChatMessageHeaderSpec(hasDate: false, hasTopic: false)
|
||||
if let top = top as? ChatMessageItemImpl {
|
||||
if top.dateHeader.id != self.dateHeader.id {
|
||||
mergedBottom = .none
|
||||
@ -548,20 +568,35 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible
|
||||
if let bottom = bottom as? ChatMessageItemImpl {
|
||||
if bottom.dateHeader.id != self.dateHeader.id {
|
||||
mergedTop = .none
|
||||
dateAtBottom = true
|
||||
} else {
|
||||
dateAtBottom.hasDate = true
|
||||
}
|
||||
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)
|
||||
}
|
||||
} else if let bottom = bottom as? ChatUnreadItem {
|
||||
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 {
|
||||
if bottom.header.id != self.dateHeader.id {
|
||||
dateAtBottom = true
|
||||
dateAtBottom.hasDate = true
|
||||
}
|
||||
if self.topicHeader != nil {
|
||||
dateAtBottom.hasTopic = true
|
||||
}
|
||||
} else {
|
||||
dateAtBottom = true
|
||||
dateAtBottom.hasDate = true
|
||||
if self.topicHeader != nil {
|
||||
dateAtBottom.hasTopic = true
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
completion(layout, { info in
|
||||
apply(animation, info, false)
|
||||
|
@ -25,7 +25,7 @@ public class ChatReplyCountItem: ListViewItem {
|
||||
self.isComments = isComments
|
||||
self.count = count
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,7 @@ public class ChatUnreadItem: ListViewItem {
|
||||
self.index = index
|
||||
self.presentationData = presentationData
|
||||
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) {
|
||||
|
@ -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 (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 attachedAvatarNodeIsHidden: Bool = false
|
||||
|
||||
private var attachedDateHeader: (hasDate: Bool, hasPeer: Bool) = (false, false)
|
||||
|
||||
override open func attachedHeaderNodesUpdated() {
|
||||
if !self.attachedAvatarNodeOffset.isZero {
|
||||
self.updateAttachedAvatarNodeOffset(offset: self.attachedAvatarNodeOffset, transition: .immediate)
|
||||
@ -919,6 +921,12 @@ open class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol {
|
||||
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) {
|
||||
@ -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() {
|
||||
}
|
||||
|
||||
|
@ -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 telegramFile = self.telegramFile
|
||||
let layoutConstants = self.layoutConstants
|
||||
@ -435,7 +435,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
let currentShareButtonNode = self.shareButtonNode
|
||||
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 layoutConstants = chatMessageItemLayoutConstants(layoutConstants, params: params, presentationData: item.presentationData)
|
||||
@ -567,8 +567,15 @@ 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)
|
||||
if dateHeaderAtBottom {
|
||||
layoutInsets.top += layoutConstants.timestampHeaderHeight
|
||||
if dateHeaderAtBottom.hasDate && dateHeaderAtBottom.hasTopic {
|
||||
layoutInsets.top += layoutConstants.timestampDateAndTopicHeaderHeight
|
||||
} else {
|
||||
if dateHeaderAtBottom.hasDate {
|
||||
layoutInsets.top += layoutConstants.timestampHeaderHeight
|
||||
}
|
||||
if dateHeaderAtBottom.hasTopic {
|
||||
layoutInsets.top += layoutConstants.timestampHeaderHeight
|
||||
}
|
||||
}
|
||||
|
||||
var deliveryFailedInset: CGFloat = 0.0
|
||||
@ -964,6 +971,8 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
strongSelf.appliedForwardInfo = (forwardSource, forwardAuthorSignature)
|
||||
strongSelf.updateAccessibilityData(accessibilityData)
|
||||
|
||||
strongSelf.updateAttachedDateHeader(hasDate: dateHeaderAtBottom.hasDate, hasPeer: dateHeaderAtBottom.hasTopic)
|
||||
|
||||
transition.updateFrame(node: strongSelf.imageNode, frame: updatedImageFrame)
|
||||
strongSelf.enableSynchronousImageApply = true
|
||||
imageApply()
|
||||
|
@ -734,7 +734,21 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
spoilerTextColor: messageTheme.primaryTextColor,
|
||||
spoilerEffectColor: messageTheme.secondaryTextColor,
|
||||
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)
|
||||
|
@ -239,6 +239,7 @@ public final class ChatSideTopicsPanel: Component {
|
||||
avatarNode = current
|
||||
} else {
|
||||
avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 11.0))
|
||||
avatarNode.isUserInteractionEnabled = false
|
||||
self.avatarNode = avatarNode
|
||||
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 {
|
||||
self.isUpdating = true
|
||||
defer {
|
||||
|
@ -1099,19 +1099,22 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn
|
||||
public let spoilerEffectColor: UIColor
|
||||
public let areContentAnimationsEnabled: Bool
|
||||
public let spoilerExpandRect: CGRect?
|
||||
public var crossfadeContents: ((UIView) -> Void)?
|
||||
|
||||
public init(
|
||||
animation: ListViewItemUpdateAnimation,
|
||||
spoilerTextColor: UIColor,
|
||||
spoilerEffectColor: UIColor,
|
||||
areContentAnimationsEnabled: Bool,
|
||||
spoilerExpandRect: CGRect?
|
||||
spoilerExpandRect: CGRect?,
|
||||
crossfadeContents: ((UIView) -> Void)? = nil
|
||||
) {
|
||||
self.animation = animation
|
||||
self.spoilerTextColor = spoilerTextColor
|
||||
self.spoilerEffectColor = spoilerEffectColor
|
||||
self.areContentAnimationsEnabled = areContentAnimationsEnabled
|
||||
self.spoilerExpandRect = spoilerExpandRect
|
||||
self.crossfadeContents = crossfadeContents
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -216,9 +216,14 @@ public final class InteractiveTextNodeWithEntities {
|
||||
return (layout, { applyArguments in
|
||||
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)
|
||||
|
||||
if let maybeNode = maybeNode {
|
||||
if let maybeNode {
|
||||
maybeNode.attributedString = arguments.attributedString
|
||||
|
||||
maybeNode.updateInteractiveContents(
|
||||
@ -234,6 +239,10 @@ public final class InteractiveTextNodeWithEntities {
|
||||
applyArguments: applyArguments.applyArguments
|
||||
)
|
||||
|
||||
if let crossfadeSourceView {
|
||||
applyArguments.applyArguments.crossfadeContents?(crossfadeSourceView)
|
||||
}
|
||||
|
||||
return maybeNode
|
||||
} else {
|
||||
let resultNode = InteractiveTextNodeWithEntities(textNode: result)
|
||||
|
@ -338,7 +338,7 @@ final class PeerNameColorChatPreviewItemNode: ListViewItemNode {
|
||||
headerNode.item = header
|
||||
}
|
||||
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 {
|
||||
headerNode = header.node(synchronousLoad: true)
|
||||
if headerNode.item !== header {
|
||||
@ -350,7 +350,7 @@ final class PeerNameColorChatPreviewItemNode: ListViewItemNode {
|
||||
strongSelf.itemHeaderNodes[id] = 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)
|
||||
}
|
||||
|
||||
var isFirstTimeValue = true
|
||||
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
|
||||
if let strongSelf = self {
|
||||
let isFirstTime = isFirstTimeValue
|
||||
isFirstTimeValue = false
|
||||
|
||||
var isScheduledMessages = false
|
||||
if case .scheduledMessages = presentationInterfaceState.subject {
|
||||
isScheduledMessages = true
|
||||
@ -446,6 +450,8 @@ extension ChatControllerImpl {
|
||||
} else if let peer = peerViewMainPeer(peerView) {
|
||||
if case .pinnedMessages = presentationInterfaceState.subject {
|
||||
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 {
|
||||
strongSelf.chatTitleView?.titleContent = .peer(peerView: ChatTitleContent.PeerData(peerView: peerView), customTitle: nil, onlineMemberCount: onlineMemberCount, isScheduledMessages: isScheduledMessages, isMuted: nil, customMessageCount: nil, isEnabled: hasPeerInfo)
|
||||
let imageOverride: AvatarNodeImageOverride?
|
||||
@ -460,7 +466,7 @@ extension ChatControllerImpl {
|
||||
} else {
|
||||
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 {
|
||||
(strongSelf.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.contextActionIsEnabled = false
|
||||
} else {
|
||||
@ -767,16 +773,22 @@ extension ChatControllerImpl {
|
||||
autoremoveTimeout = value?.effectiveValue
|
||||
}
|
||||
} else if let cachedChannelData = peerView.cachedData as? CachedChannelData {
|
||||
currentSendAsPeerId = cachedChannelData.sendAsPeerId
|
||||
if let channel = peer as? TelegramChannel, case .group = channel.info {
|
||||
if !cachedChannelData.botInfos.isEmpty {
|
||||
hasBots = true
|
||||
if let channel = peer as? TelegramChannel, channel.isMonoForum {
|
||||
if case let .known(value) = cachedChannelData.linkedMonoforumPeerId {
|
||||
currentSendAsPeerId = value
|
||||
}
|
||||
let botCommands = cachedChannelData.botInfos.reduce(into: [], { result, info in
|
||||
result.append(contentsOf: info.botInfo.commands)
|
||||
})
|
||||
if !botCommands.isEmpty {
|
||||
hasBotCommands = true
|
||||
} else {
|
||||
currentSendAsPeerId = cachedChannelData.sendAsPeerId
|
||||
if let channel = peer as? TelegramChannel, case .group = channel.info {
|
||||
if !cachedChannelData.botInfos.isEmpty {
|
||||
hasBots = true
|
||||
}
|
||||
let botCommands = cachedChannelData.botInfos.reduce(into: [], { result, info in
|
||||
result.append(contentsOf: info.botInfo.commands)
|
||||
})
|
||||
if !botCommands.isEmpty {
|
||||
hasBotCommands = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if case let .known(value) = cachedChannelData.autoremoveTimeout {
|
||||
@ -1139,22 +1151,39 @@ extension ChatControllerImpl {
|
||||
savedMessagesPeerId = nil
|
||||
}
|
||||
|
||||
let savedMessagesPeer: Signal<(peer: EnginePeer?, messageCount: Int)?, NoError>
|
||||
let savedMessagesPeer: Signal<(peer: EnginePeer?, messageCount: Int, presence: EnginePeer.Presence?)?, NoError>
|
||||
if let 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)
|
||||
savedMessagesPeer = context.account.postbox.combinedView(keys: [basicPeerKey, countViewKey])
|
||||
|> map { views -> (peer: EnginePeer?, messageCount: Int)? in
|
||||
let peer = ((views.views[basicPeerKey] as? BasicPeerView)?.peer).flatMap(EnginePeer.init)
|
||||
|> map { views -> (peer: EnginePeer?, messageCount: Int, presence: EnginePeer.Presence?)? in
|
||||
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
|
||||
if let summaryView = views.views[countViewKey] as? MessageHistoryTagSummaryView, let count = summaryView.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 {
|
||||
savedMessagesPeer = .single(nil)
|
||||
}
|
||||
@ -1261,6 +1290,7 @@ extension ChatControllerImpl {
|
||||
let globalPrivacySettings = context.engine.data.get(TelegramEngine.EngineData.Item.Configuration.GlobalPrivacy())
|
||||
|
||||
self.titleDisposable.set(nil)
|
||||
var isFirstTimeValue = true
|
||||
self.peerDisposable.set((combineLatest(queue: Queue.mainQueue(),
|
||||
peerView,
|
||||
messageAndTopic,
|
||||
@ -1275,6 +1305,9 @@ extension ChatControllerImpl {
|
||||
)
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] peerView, messageAndTopic, savedMessagesPeer, onlineMemberCount, hasScheduledMessages, hasSearchTags, hasSavedChats, isPremiumRequiredForMessaging, managingBot, globalPrivacySettings in
|
||||
if let strongSelf = self {
|
||||
let isFirstTime = isFirstTimeValue
|
||||
isFirstTimeValue = false
|
||||
|
||||
strongSelf.hasScheduledMessages = hasScheduledMessages
|
||||
|
||||
var renderedPeer: RenderedPeer?
|
||||
@ -1334,16 +1367,29 @@ extension ChatControllerImpl {
|
||||
}
|
||||
|
||||
if let savedMessagesPeerId {
|
||||
var peerPresences: [PeerId: PeerPresence] = [:]
|
||||
if let presence = savedMessagesPeer?.presence {
|
||||
peerPresences[savedMessagesPeerId] = presence._asPresence()
|
||||
}
|
||||
let mappedPeerData = ChatTitleContent.PeerData(
|
||||
peerId: savedMessagesPeerId,
|
||||
peer: savedMessagesPeer?.peer?._asPeer(),
|
||||
isContact: true,
|
||||
isSavedMessages: true,
|
||||
notificationSettings: nil,
|
||||
peerPresences: [:],
|
||||
peerPresences: peerPresences,
|
||||
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
|
||||
|
||||
@ -1374,7 +1420,7 @@ extension ChatControllerImpl {
|
||||
.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.accessibilityLabel = strongSelf.presentationData.strings.Conversation_ContextMenuOpenProfile
|
||||
} else {
|
||||
@ -1464,12 +1510,18 @@ extension ChatControllerImpl {
|
||||
var peerGeoLocation: PeerGeoLocation?
|
||||
var currentSendAsPeerId: PeerId?
|
||||
if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, let cachedData = peerView.cachedData as? CachedChannelData {
|
||||
currentSendAsPeerId = cachedData.sendAsPeerId
|
||||
if case .group = peer.info {
|
||||
peerGeoLocation = cachedData.peerGeoLocation
|
||||
}
|
||||
if case let .known(value) = cachedData.linkedDiscussionPeerId {
|
||||
peerDiscussionId = value
|
||||
if peer.isMonoForum {
|
||||
if case let .known(value) = cachedData.linkedMonoforumPeerId {
|
||||
currentSendAsPeerId = value
|
||||
}
|
||||
} else {
|
||||
currentSendAsPeerId = cachedData.sendAsPeerId
|
||||
if case .group = peer.info {
|
||||
peerGeoLocation = cachedData.peerGeoLocation
|
||||
}
|
||||
if case let .known(value) = cachedData.linkedDiscussionPeerId {
|
||||
peerDiscussionId = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1685,12 +1737,16 @@ extension ChatControllerImpl {
|
||||
self.chatTitleView?.titleContent = .custom(" ", nil, false)
|
||||
}
|
||||
|
||||
var isFirstTimeValue = true
|
||||
self.peerDisposable.set((peerView
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] peerView in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
let isFirstTime = isFirstTimeValue
|
||||
isFirstTimeValue = false
|
||||
|
||||
var renderedPeer: RenderedPeer?
|
||||
if let peerView, let peer = peerView.peers[peerView.peerId] {
|
||||
var peers = SimpleDictionary<PeerId, Peer>()
|
||||
@ -1700,7 +1756,7 @@ extension ChatControllerImpl {
|
||||
}
|
||||
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
|
||||
@ -2327,6 +2383,10 @@ extension ChatControllerImpl {
|
||||
return
|
||||
}
|
||||
|
||||
if let channel = peerViewMainPeer(peerView) as? TelegramChannel, channel.isMonoForum {
|
||||
return
|
||||
}
|
||||
|
||||
let isPremium = strongSelf.presentationInterfaceState.isPremium
|
||||
|
||||
var allPeers: [SendAsPeer]?
|
||||
|
@ -23,6 +23,8 @@ func updateChatPresentationInterfaceStateImpl(
|
||||
_ f: (ChatPresentationInterfaceState) -> ChatPresentationInterfaceState,
|
||||
completion externalCompletion: @escaping (ContainedViewLayoutTransition) -> Void
|
||||
) {
|
||||
let previousChatLocation = selfController.chatDisplayNode.historyNode.chatLocation
|
||||
|
||||
var completion = externalCompletion
|
||||
var temporaryChatPresentationInterfaceState = f(selfController.presentationInterfaceState)
|
||||
|
||||
@ -434,6 +436,11 @@ func updateChatPresentationInterfaceStateImpl(
|
||||
|
||||
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()
|
||||
|
||||
switch updatedChatPresentationInterfaceState.inputMode {
|
||||
@ -489,19 +496,25 @@ func updateChatPresentationInterfaceStateImpl(
|
||||
selfController.leftNavigationButton = nil
|
||||
}
|
||||
|
||||
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 selfController.rightNavigationButton != button {
|
||||
if let currentButton = selfController.rightNavigationButton?.action, currentButton == button.action {
|
||||
buttonsAnimated = false
|
||||
if let channel = presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isForumOrMonoForum {
|
||||
} else {
|
||||
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 selfController.rightNavigationButton != button {
|
||||
if let currentButton = selfController.rightNavigationButton?.action, currentButton == button.action {
|
||||
buttonsAnimated = false
|
||||
}
|
||||
if case .replyThread = selfController.chatLocation {
|
||||
buttonsAnimated = false
|
||||
}
|
||||
if let channel = updatedChatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum {
|
||||
buttonsAnimated = false
|
||||
}
|
||||
selfController.rightNavigationButton = button
|
||||
}
|
||||
if case .replyThread = selfController.chatLocation {
|
||||
buttonsAnimated = false
|
||||
}
|
||||
selfController.rightNavigationButton = button
|
||||
} else if let _ = selfController.rightNavigationButton {
|
||||
selfController.rightNavigationButton = nil
|
||||
}
|
||||
} else if let _ = selfController.rightNavigationButton {
|
||||
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) {
|
||||
@ -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.chatDisplayNode.chatLocation = selfController.presentationInterfaceState.chatLocation
|
||||
selfController.reloadChatLocation()
|
||||
selfController.reloadCachedData()
|
||||
selfController.chatDisplayNode.historyNode.updateChatLocation(chatLocation: selfController.presentationInterfaceState.chatLocation)
|
||||
}
|
||||
|
||||
selfController.updateDownButtonVisibility()
|
||||
|
@ -842,7 +842,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
return true
|
||||
case let .quickReplyMessageInput(_, shortcutType):
|
||||
if let historyView = strongSelf.chatDisplayNode.historyNode.originalHistoryView, historyView.entries.isEmpty {
|
||||
|
||||
let titleString: String
|
||||
let textString: String
|
||||
switch shortcutType {
|
||||
@ -7919,6 +7918,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
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 attributes.first(where: { $0 is SendAsMessageAttribute }) == nil {
|
||||
attributes.append(SendAsMessageAttribute(peerId: sendAsPeerId))
|
||||
|
@ -135,7 +135,8 @@ class HistoryNodeContainer: ASDisplayNode {
|
||||
|
||||
class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||
let context: AccountContext
|
||||
var chatLocation: ChatLocation
|
||||
private(set) var chatLocation: ChatLocation
|
||||
private let chatLocationContextHolder: Atomic<ChatLocationContextHolder?>
|
||||
let controllerInteraction: ChatControllerInteraction
|
||||
private weak var controller: ChatControllerImpl?
|
||||
|
||||
@ -158,7 +159,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||
let contentContainerNode: ChatNodeContainer
|
||||
let contentDimNode: ASDisplayNode
|
||||
let backgroundNode: WallpaperBackgroundNode
|
||||
let historyNode: ChatHistoryListNodeImpl
|
||||
var historyNode: ChatHistoryListNodeImpl
|
||||
var blurredHistoryNode: ASImageNode?
|
||||
let historyNodeContainer: HistoryNodeContainer
|
||||
let loadingNode: ChatLoadingNode
|
||||
@ -183,6 +184,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||
|
||||
private(set) var validLayout: (ContainerViewLayout, CGFloat)?
|
||||
private var visibleAreaInset = UIEdgeInsets()
|
||||
private var currentListViewLayout: (size: CGSize, insets: UIEdgeInsets, scrollIndicatorInsets: UIEdgeInsets)?
|
||||
|
||||
private(set) var searchNavigationNode: ChatSearchNavigationContentNode?
|
||||
|
||||
@ -293,7 +295,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||
|
||||
private var dropDimNode: ASDisplayNode?
|
||||
|
||||
let messageTransitionNode: ChatMessageTransitionNodeImpl
|
||||
var messageTransitionNode: ChatMessageTransitionNodeImpl
|
||||
|
||||
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?) {
|
||||
self.context = context
|
||||
self.chatLocation = chatLocation
|
||||
self.chatLocationContextHolder = chatLocationContextHolder
|
||||
self.controllerInteraction = controllerInteraction
|
||||
self.chatPresentationInterfaceState = chatPresentationInterfaceState
|
||||
self.automaticMediaDownloadSettings = automaticMediaDownloadSettings
|
||||
@ -762,34 +765,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||
|
||||
assert(Queue.mainQueue().isCurrent())
|
||||
|
||||
self.historyNode.setLoadStateUpdated { [weak self] loadState, animated in
|
||||
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.setupHistoryNode()
|
||||
|
||||
self.interactiveEmojisDisposable = (self.context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration])
|
||||
|> 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.wrappingNode.contentNode.addSubnode(self.contentContainerNode)
|
||||
self.contentContainerNode.contentNode.addSubnode(self.backgroundNode)
|
||||
@ -866,8 +817,6 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||
|
||||
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?.interfaceInteraction?.presentController(controller, nil)
|
||||
})
|
||||
@ -1014,44 +963,6 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||
|
||||
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) {
|
||||
@ -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
|
||||
if let strongSelf = self {
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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>) {
|
||||
self.adMessagesDisposable = (adMessages
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] interPostInterval, messages in
|
||||
@ -2362,7 +2354,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
|
||||
strongSelf.dynamicBounceEnabled = false
|
||||
|
||||
strongSelf.forEachItemHeaderNode { itemHeaderNode in
|
||||
if let dateNode = itemHeaderNode as? ChatMessageDateHeaderNode {
|
||||
if let dateNode = itemHeaderNode as? ChatMessageDateHeaderNodeImpl {
|
||||
dateNode.updatePresentationData(chatPresentationData, context: strongSelf.context)
|
||||
} else if let avatarNode = itemHeaderNode as? ChatMessageAvatarHeaderNodeImpl {
|
||||
avatarNode.updatePresentationData(chatPresentationData, context: strongSelf.context)
|
||||
|
@ -231,15 +231,16 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
|
||||
|
||||
if channel.flags.contains(.isMonoforum) {
|
||||
if channel.adminRights != nil, case .peer = chatPresentationInterfaceState.chatLocation {
|
||||
displayInputTextPanel = false
|
||||
|
||||
if let currentPanel = (currentPanel as? ChatRestrictedInputPanelNode) ?? (currentSecondaryPanel as? ChatRestrictedInputPanelNode) {
|
||||
return (currentPanel, nil)
|
||||
} else {
|
||||
let panel = ChatRestrictedInputPanelNode()
|
||||
panel.context = context
|
||||
panel.interfaceInteraction = interfaceInteraction
|
||||
return (panel, nil)
|
||||
if chatPresentationInterfaceState.interfaceState.replyMessageSubject == nil {
|
||||
displayInputTextPanel = false
|
||||
if let currentPanel = (currentPanel as? ChatRestrictedInputPanelNode) ?? (currentSecondaryPanel as? ChatRestrictedInputPanelNode) {
|
||||
return (currentPanel, nil)
|
||||
} else {
|
||||
let panel = ChatRestrictedInputPanelNode()
|
||||
panel.context = context
|
||||
panel.interfaceInteraction = interfaceInteraction
|
||||
return (panel, nil)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
displayInputTextPanel = true
|
||||
|
@ -90,8 +90,7 @@ final class ChatRestrictedInputPanelNode: ChatInputPanelNode {
|
||||
accountFreezeConfiguration = AccountFreezeConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
|
||||
}
|
||||
if let channel = interfaceState.renderedPeer?.chatMainPeer as? TelegramChannel, channel.isMonoForum {
|
||||
//TODO:localize
|
||||
self.textNode.attributedText = NSAttributedString(string: "Choose a thread to reply", font: Font.regular(13.0), textColor: interfaceState.theme.chat.inputPanel.secondaryTextColor)
|
||||
self.textNode.attributedText = NSAttributedString(string: interfaceState.strings.Chat_PanelForumModeReplyText, font: Font.regular(15.0), textColor: interfaceState.theme.chat.inputPanel.secondaryTextColor)
|
||||
} 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.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))
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
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 {
|
||||
|
@ -297,7 +297,7 @@ final class PeerNameColorChatPreviewItemNode: ListViewItemNode {
|
||||
headerNode.item = header
|
||||
}
|
||||
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 {
|
||||
headerNode = header.node(synchronousLoad: true)
|
||||
if headerNode.item !== header {
|
||||
@ -309,7 +309,7 @@ final class PeerNameColorChatPreviewItemNode: ListViewItemNode {
|
||||
strongSelf.itemHeaderNodes[id] = 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",
|
||||
"xcode": "16.2",
|
||||
"xcode": "16.3",
|
||||
"bazel": "8.2.1:22ff65b05869f6160e5157b1b425a14a62085d71d8baef571f462b8fe5a703a3",
|
||||
"macos": "15"
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user