[WIP] Monoforums

This commit is contained in:
Isaac 2025-05-10 18:02:55 +01:00
parent 0e119dd6f1
commit 140efd6cfd
70 changed files with 666 additions and 669 deletions

View file

@ -1166,7 +1166,6 @@ public enum ChatCustomContentsKind: Equatable {
case quickReplyMessageInput(shortcut: String, shortcutType: ChatQuickReplyShortcutType)
case businessLinkSetup(link: TelegramBusinessChatLinks.Link)
case hashTagSearch(publicPosts: Bool)
case postSuggestions(price: StarsAmount)
}
public protocol ChatCustomContentsProtocol: AnyObject {

View file

@ -175,6 +175,7 @@ public final class BrowserBookmarksScreen: ViewController {
}, forceUpdateWarpContents: {
}, playShakeAnimation: {
}, displayQuickShare: { _, _ ,_ in
}, updateChatLocationThread: { _ in
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: context, backgroundNode: nil))

View file

@ -287,7 +287,7 @@ public final class ChatListSearchItemHeaderNode: ListViewItemHeaderNode {
}
}
override public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) {
override public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) {
self.validLayout = (size, leftInset, rightInset)
self.sectionHeaderNode.frame = CGRect(origin: CGPoint(), size: size)
self.sectionHeaderNode.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset)

View file

@ -66,7 +66,7 @@ final class ContactListNameIndexHeaderNode: ListViewItemHeaderNode {
self.sectionHeaderNode.updateTheme(theme: theme)
}
override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) {
override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) {
self.sectionHeaderNode.frame = CGRect(origin: CGPoint(), size: size)
self.sectionHeaderNode.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset)
}

View file

@ -453,6 +453,14 @@ public extension CALayer {
self.animate(from: from as NSNumber, to: to as NSNumber, keyPath: "bounds.origin.y", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: duration, mediaTimingFunction: mediaTimingFunction, additive: true)
}
func animateBoundsOriginAdditive(from: CGPoint, to: CGPoint, duration: Double, mediaTimingFunction: CAMediaTimingFunction) {
self.animate(from: NSValue(cgPoint: from), to: NSValue(cgPoint: to), keyPath: "bounds.origin", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: duration, mediaTimingFunction: mediaTimingFunction, additive: true)
}
func animateBoundsOriginAdditive(from: CGPoint, to: CGPoint, duration: Double, timingFunction: String = CAMediaTimingFunctionName.easeInEaseOut.rawValue, mediaTimingFunction: CAMediaTimingFunction? = nil) {
self.animate(from: NSValue(cgPoint: from), to: NSValue(cgPoint: to), keyPath: "bounds.origin", timingFunction: timingFunction, duration: duration, mediaTimingFunction: mediaTimingFunction, additive: true)
}
func animateShapeLineWidth(from: CGFloat, to: CGFloat, duration: Double, delay: Double = 0.0, timingFunction: String = CAMediaTimingFunctionName.easeInEaseOut.rawValue, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "lineWidth", timingFunction: timingFunction, duration: duration, delay: delay, mediaTimingFunction: mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: additive, completion: completion)
}

View file

@ -651,6 +651,15 @@ public extension ContainedViewLayoutTransition {
}
}
func animateOffsetAdditive(node: ASDisplayNode, offset: CGPoint) {
switch self {
case .immediate:
break
case let .animated(duration, curve):
node.layer.animateBoundsOriginAdditive(from: offset, to: CGPoint(), duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction)
}
}
func animateHorizontalOffsetAdditive(node: ASDisplayNode, offset: CGFloat, completion: (() -> Void)? = nil) {
switch self {
case .immediate:

View file

@ -1740,18 +1740,30 @@ open class ListView: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDel
}
}
private func nodeForItem(synchronous: Bool, synchronousLoads: Bool, item: ListViewItem, previousNode: QueueLocalObject<ListViewItemNode>?, index: Int, previousItem: ListViewItem?, nextItem: ListViewItem?, params: ListViewItemLayoutParams, updateAnimationIsAnimated: Bool, updateAnimationIsCrossfade: Bool, completion: @escaping (QueueLocalObject<ListViewItemNode>, ListViewItemNodeLayout, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
private func nodeForItem(synchronous: Bool, synchronousLoads: Bool, item: ListViewItem, previousNode: QueueLocalObject<ListViewItemNode>?, index: Int, previousItem: ListViewItem?, nextItem: ListViewItem?, params: ListViewItemLayoutParams, updateAnimationIsAnimated: Bool, updateAnimationIsCrossfade: Bool, customAnimationTransition: ControlledTransition?, completion: @escaping (QueueLocalObject<ListViewItemNode>, ListViewItemNodeLayout, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
if let previousNode = previousNode {
var controlledTransition: ControlledTransition?
let updateAnimation: ListViewItemUpdateAnimation
if updateAnimationIsCrossfade {
updateAnimation = .Crossfade
} else if updateAnimationIsAnimated {
let transition = ControlledTransition(duration: insertionAnimationDuration * UIView.animationDurationFactor(), curve: .spring, interactive: true)
controlledTransition = transition
updateAnimation = .System(duration: insertionAnimationDuration * UIView.animationDurationFactor(), transition: transition)
if let customAnimationTransition {
controlledTransition = customAnimationTransition
var duration: Double = insertionAnimationDuration
switch customAnimationTransition.legacyAnimator.transition {
case .immediate:
duration = 0.0
case let .animated(durationValue, _):
duration = durationValue
}
updateAnimation = .System(duration: duration * UIView.animationDurationFactor(), transition: customAnimationTransition)
} else {
updateAnimation = .None
if updateAnimationIsCrossfade {
updateAnimation = .Crossfade
} else if updateAnimationIsAnimated {
let transition = ControlledTransition(duration: insertionAnimationDuration * UIView.animationDurationFactor(), curve: .spring, interactive: true)
controlledTransition = transition
updateAnimation = .System(duration: insertionAnimationDuration * UIView.animationDurationFactor(), transition: transition)
} else {
updateAnimation = .None
}
}
if let controlledTransition = controlledTransition {
@ -1846,7 +1858,7 @@ open class ListView: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDel
self.transactionQueue.addTransaction({ [weak self] transactionCompletion in
if let strongSelf = self {
strongSelf.transactionOffset = 0.0
strongSelf.deleteAndInsertItemsTransaction(deleteIndices: deleteIndices, insertIndicesAndItems: insertIndicesAndItems, updateIndicesAndItems: updateIndicesAndItems, options: options, scrollToItem: scrollToItem, additionalScrollDistance: additionalScrollDistance, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: stationaryItemRange, updateOpaqueState: updateOpaqueState, completion: { [weak strongSelf] in
strongSelf.deleteAndInsertItemsTransaction(deleteIndices: deleteIndices, insertIndicesAndItems: insertIndicesAndItems, updateIndicesAndItems: updateIndicesAndItems, options: options, scrollToItem: scrollToItem, additionalScrollDistance: additionalScrollDistance, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: stationaryItemRange, updateOpaqueState: updateOpaqueState, customAnimationTransition: updateSizeAndInsets?.customAnimationTransition, completion: { [weak strongSelf] in
completion(strongSelf?.immediateDisplayedItemRange() ?? ListViewDisplayedItemRange(loadedRange: nil, visibleRange: nil))
transactionCompletion()
@ -1855,7 +1867,7 @@ open class ListView: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDel
})
}
private func deleteAndInsertItemsTransaction(deleteIndices: [ListViewDeleteItem], insertIndicesAndItems: [ListViewInsertItem], updateIndicesAndItems: [ListViewUpdateItem], options: ListViewDeleteAndInsertOptions, scrollToItem: ListViewScrollToItem?, additionalScrollDistance: CGFloat, updateSizeAndInsets: ListViewUpdateSizeAndInsets?, stationaryItemRange: (Int, Int)?, updateOpaqueState: Any?, completion: @escaping () -> Void) {
private func deleteAndInsertItemsTransaction(deleteIndices: [ListViewDeleteItem], insertIndicesAndItems: [ListViewInsertItem], updateIndicesAndItems: [ListViewUpdateItem], options: ListViewDeleteAndInsertOptions, scrollToItem: ListViewScrollToItem?, additionalScrollDistance: CGFloat, updateSizeAndInsets: ListViewUpdateSizeAndInsets?, stationaryItemRange: (Int, Int)?, updateOpaqueState: Any?, customAnimationTransition: ControlledTransition?, completion: @escaping () -> Void) {
if deleteIndices.isEmpty && insertIndicesAndItems.isEmpty && updateIndicesAndItems.isEmpty && scrollToItem == nil {
if let updateSizeAndInsets = updateSizeAndInsets, (self.items.count == 0 || (updateSizeAndInsets.size == self.visibleSize && updateSizeAndInsets.insets == self.insets && !options.contains(.ForceUpdate))) {
self.visibleSize = updateSizeAndInsets.size
@ -2096,7 +2108,7 @@ open class ListView: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDel
print("deleteAndInsertItemsTransaction prepare \((CACurrentMediaTime() - startTime) * 1000.0) ms")
}
self.fillMissingNodes(synchronous: options.contains(.Synchronous), synchronousLoads: options.contains(.PreferSynchronousResourceLoading), animated: animated, inputAnimatedInsertIndices: animated ? insertedIndexSet : Set<Int>(), insertDirectionHints: insertDirectionHints, inputState: state, inputPreviousNodes: previousNodes, inputOperations: operations, inputCompletion: { updatedState, operations in
self.fillMissingNodes(synchronous: options.contains(.Synchronous), synchronousLoads: options.contains(.PreferSynchronousResourceLoading), animated: animated, customAnimationTransition: updateSizeAndInsets?.customAnimationTransition, inputAnimatedInsertIndices: animated ? insertedIndexSet : Set<Int>(), insertDirectionHints: insertDirectionHints, inputState: state, inputPreviousNodes: previousNodes, inputOperations: operations, inputCompletion: { updatedState, operations in
if self.debugInfo {
print("fillMissingNodes completion \((CACurrentMediaTime() - startTime) * 1000.0) ms")
@ -2119,8 +2131,8 @@ open class ListView: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDel
updateIndices.subtract(explicitelyUpdateIndices)
self.updateNodes(synchronous: options.contains(.Synchronous), synchronousLoads: options.contains(.PreferSynchronousResourceLoading), crossfade: options.contains(.AnimateCrossfade), animated: animated, updateIndicesAndItems: updateIndicesAndItems, inputState: updatedState, previousNodes: previousNodes, inputOperations: operations, completion: { updatedState, operations in
self.updateAdjacent(synchronous: options.contains(.Synchronous), animated: animated, state: updatedState, updateAdjacentItemsIndices: updateIndices, operations: operations, completion: { state, operations in
self.updateNodes(synchronous: options.contains(.Synchronous), synchronousLoads: options.contains(.PreferSynchronousResourceLoading), crossfade: options.contains(.AnimateCrossfade), customAnimationTransition: customAnimationTransition, animated: animated, updateIndicesAndItems: updateIndicesAndItems, inputState: updatedState, previousNodes: previousNodes, inputOperations: operations, completion: { updatedState, operations in
self.updateAdjacent(synchronous: options.contains(.Synchronous), animated: animated, customAnimationTransition: customAnimationTransition, state: updatedState, updateAdjacentItemsIndices: updateIndices, operations: operations, completion: { state, operations in
var updatedState = state
var updatedOperations = operations
updatedState.removeInvisibleNodes(&updatedOperations)
@ -2154,7 +2166,7 @@ open class ListView: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDel
let beginReplay = { [weak self] in
if let strongSelf = self {
strongSelf.replayOperations(animated: animated, animateAlpha: options.contains(.AnimateAlpha), animateCrossfade: options.contains(.AnimateCrossfade), animateFullTransition: options.contains(.AnimateFullTransition), synchronous: options.contains(.Synchronous), synchronousLoads: options.contains(.PreferSynchronousResourceLoading), animateTopItemVerticalOrigin: options.contains(.AnimateTopItemPosition), operations: updatedOperations, requestItemInsertionAnimationsIndices: options.contains(.RequestItemInsertionAnimations) ? insertedIndexSet : Set(), scrollToItem: scrollToItem, additionalScrollDistance: additionalScrollDistance, updateSizeAndInsets: updateSizeAndInsets, stationaryItemIndex: stationaryItemIndex, updateOpaqueState: updateOpaqueState, forceInvertOffsetDirection: options.contains(.InvertOffsetDirection), completion: {
strongSelf.replayOperations(animated: animated, animateAlpha: options.contains(.AnimateAlpha), animateCrossfade: options.contains(.AnimateCrossfade), animateFullTransition: options.contains(.AnimateFullTransition), customAnimationTransition: updateSizeAndInsets?.customAnimationTransition, synchronous: options.contains(.Synchronous), synchronousLoads: options.contains(.PreferSynchronousResourceLoading), animateTopItemVerticalOrigin: options.contains(.AnimateTopItemPosition), operations: updatedOperations, requestItemInsertionAnimationsIndices: options.contains(.RequestItemInsertionAnimations) ? insertedIndexSet : Set(), scrollToItem: scrollToItem, additionalScrollDistance: additionalScrollDistance, updateSizeAndInsets: updateSizeAndInsets, stationaryItemIndex: stationaryItemIndex, updateOpaqueState: updateOpaqueState, forceInvertOffsetDirection: options.contains(.InvertOffsetDirection), completion: {
if options.contains(.PreferSynchronousDrawing) {
self?.recursivelyEnsureDisplaySynchronously(true)
}
@ -2199,7 +2211,7 @@ open class ListView: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDel
}
}
private func updateAdjacent(synchronous: Bool, animated: Bool, state: ListViewState, updateAdjacentItemsIndices: Set<Int>, operations: [ListViewStateOperation], completion: @escaping (ListViewState, [ListViewStateOperation]) -> Void) {
private func updateAdjacent(synchronous: Bool, animated: Bool, customAnimationTransition: ControlledTransition?, state: ListViewState, updateAdjacentItemsIndices: Set<Int>, operations: [ListViewStateOperation], completion: @escaping (ListViewState, [ListViewStateOperation]) -> Void) {
if updateAdjacentItemsIndices.isEmpty {
completion(state, operations)
} else {
@ -2217,12 +2229,24 @@ open class ListView: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDel
continueWithoutNode = false
var controlledTransition: ControlledTransition?
let updateAnimation: ListViewItemUpdateAnimation
if animated {
let transition = ControlledTransition(duration: insertionAnimationDuration * UIView.animationDurationFactor(), curve: .spring, interactive: true)
controlledTransition = transition
updateAnimation = .System(duration: insertionAnimationDuration * UIView.animationDurationFactor(), transition: transition)
if let customAnimationTransition {
controlledTransition = customAnimationTransition
var duration: Double = insertionAnimationDuration
switch customAnimationTransition.legacyAnimator.transition {
case .immediate:
duration = 0.0
case let .animated(durationValue, _):
duration = durationValue
}
updateAnimation = .System(duration: duration * UIView.animationDurationFactor(), transition: customAnimationTransition)
} else {
updateAnimation = .None
if animated {
let transition = ControlledTransition(duration: insertionAnimationDuration * UIView.animationDurationFactor(), curve: .spring, interactive: true)
controlledTransition = transition
updateAnimation = .System(duration: insertionAnimationDuration * UIView.animationDurationFactor(), transition: transition)
} else {
updateAnimation = .None
}
}
if let controlledTransition = controlledTransition {
@ -2264,7 +2288,7 @@ open class ListView: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDel
}
}
self.updateAdjacent(synchronous: synchronous, animated: animated, state: updatedState, updateAdjacentItemsIndices: updatedUpdateAdjacentItemsIndices, operations: updatedOperations, completion: completion)
self.updateAdjacent(synchronous: synchronous, animated: animated, customAnimationTransition: customAnimationTransition, state: updatedState, updateAdjacentItemsIndices: updatedUpdateAdjacentItemsIndices, operations: updatedOperations, completion: completion)
})
}
break
@ -2273,12 +2297,12 @@ open class ListView: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDel
}
if continueWithoutNode {
updateAdjacent(synchronous: synchronous, animated: animated, state: state, updateAdjacentItemsIndices: updatedUpdateAdjacentItemsIndices, operations: operations, completion: completion)
updateAdjacent(synchronous: synchronous, animated: animated, customAnimationTransition: customAnimationTransition, state: state, updateAdjacentItemsIndices: updatedUpdateAdjacentItemsIndices, operations: operations, completion: completion)
}
}
}
private func fillMissingNodes(synchronous: Bool, synchronousLoads: Bool, animated: Bool, inputAnimatedInsertIndices: Set<Int>, insertDirectionHints: [Int: ListViewItemOperationDirectionHint], inputState: ListViewState, inputPreviousNodes: [Int: QueueLocalObject<ListViewItemNode>], inputOperations: [ListViewStateOperation], inputCompletion: @escaping (ListViewState, [ListViewStateOperation]) -> Void) {
private func fillMissingNodes(synchronous: Bool, synchronousLoads: Bool, animated: Bool, customAnimationTransition: ControlledTransition?, inputAnimatedInsertIndices: Set<Int>, insertDirectionHints: [Int: ListViewItemOperationDirectionHint], inputState: ListViewState, inputPreviousNodes: [Int: QueueLocalObject<ListViewItemNode>], inputOperations: [ListViewStateOperation], inputCompletion: @escaping (ListViewState, [ListViewStateOperation]) -> Void) {
let animatedInsertIndices = inputAnimatedInsertIndices
var state = inputState
let previousNodes = inputPreviousNodes
@ -2312,7 +2336,7 @@ open class ListView: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDel
let index = insertionItemIndexAndDirection.0
let threadId = pthread_self()
var tailRecurse = false
self.nodeForItem(synchronous: synchronous, synchronousLoads: synchronousLoads, item: self.items[index], previousNode: previousNodes[index], index: index, previousItem: index == 0 ? nil : self.items[index - 1], nextItem: self.items.count == index + 1 ? nil : self.items[index + 1], params: ListViewItemLayoutParams(width: state.visibleSize.width, leftInset: state.insets.left, rightInset: state.insets.right, availableHeight: state.visibleSize.height - state.insets.top - state.insets.bottom), updateAnimationIsAnimated: animated, updateAnimationIsCrossfade: false, completion: { (node, layout, apply) in
self.nodeForItem(synchronous: synchronous, synchronousLoads: synchronousLoads, item: self.items[index], previousNode: previousNodes[index], index: index, previousItem: index == 0 ? nil : self.items[index - 1], nextItem: self.items.count == index + 1 ? nil : self.items[index + 1], params: ListViewItemLayoutParams(width: state.visibleSize.width, leftInset: state.insets.left, rightInset: state.insets.right, availableHeight: state.visibleSize.height - state.insets.top - state.insets.bottom), updateAnimationIsAnimated: animated, updateAnimationIsCrossfade: false, customAnimationTransition: customAnimationTransition, completion: { (node, layout, apply) in
if pthread_equal(pthread_self(), threadId) != 0 && !tailRecurse {
tailRecurse = true
state.insertNode(index, node: node, layout: layout, apply: apply, offsetDirection: insertionItemIndexAndDirection.1, animated: animated && animatedInsertIndices.contains(index), operations: &operations, itemCount: self.items.count)
@ -2320,7 +2344,7 @@ open class ListView: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDel
var updatedState = state
var updatedOperations = operations
updatedState.insertNode(index, node: node, layout: layout, apply: apply, offsetDirection: insertionItemIndexAndDirection.1, animated: animated && animatedInsertIndices.contains(index), operations: &updatedOperations, itemCount: self.items.count)
self.fillMissingNodes(synchronous: synchronous, synchronousLoads: synchronousLoads, animated: animated, inputAnimatedInsertIndices: animatedInsertIndices, insertDirectionHints: insertDirectionHints, inputState: updatedState, inputPreviousNodes: previousNodes, inputOperations: updatedOperations, inputCompletion: completion)
self.fillMissingNodes(synchronous: synchronous, synchronousLoads: synchronousLoads, animated: animated, customAnimationTransition: customAnimationTransition, inputAnimatedInsertIndices: animatedInsertIndices, insertDirectionHints: insertDirectionHints, inputState: updatedState, inputPreviousNodes: previousNodes, inputOperations: updatedOperations, inputCompletion: completion)
}
})
if !tailRecurse {
@ -2335,7 +2359,7 @@ open class ListView: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDel
}
}
private func updateNodes(synchronous: Bool, synchronousLoads: Bool, crossfade: Bool, animated: Bool, updateIndicesAndItems: [ListViewUpdateItem], inputState: ListViewState, previousNodes: [Int: QueueLocalObject<ListViewItemNode>], inputOperations: [ListViewStateOperation], completion: @escaping (ListViewState, [ListViewStateOperation]) -> Void) {
private func updateNodes(synchronous: Bool, synchronousLoads: Bool, crossfade: Bool, customAnimationTransition: ControlledTransition?, animated: Bool, updateIndicesAndItems: [ListViewUpdateItem], inputState: ListViewState, previousNodes: [Int: QueueLocalObject<ListViewItemNode>], inputOperations: [ListViewStateOperation], completion: @escaping (ListViewState, [ListViewStateOperation]) -> Void) {
var state = inputState
var operations = inputOperations
var updateIndicesAndItems = updateIndicesAndItems
@ -2347,11 +2371,11 @@ open class ListView: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDel
} else {
let updateItem = updateIndicesAndItems[0]
if let previousNode = previousNodes[updateItem.index] {
self.nodeForItem(synchronous: synchronous, synchronousLoads: synchronousLoads, item: updateItem.item, previousNode: previousNode, index: updateItem.index, previousItem: updateItem.index == 0 ? nil : self.items[updateItem.index - 1], nextItem: updateItem.index == (self.items.count - 1) ? nil : self.items[updateItem.index + 1], params: ListViewItemLayoutParams(width: state.visibleSize.width, leftInset: state.insets.left, rightInset: state.insets.right, availableHeight: state.visibleSize.height - state.insets.top - state.insets.bottom), updateAnimationIsAnimated: animated, updateAnimationIsCrossfade: crossfade, completion: { _, layout, apply in
self.nodeForItem(synchronous: synchronous, synchronousLoads: synchronousLoads, item: updateItem.item, previousNode: previousNode, index: updateItem.index, previousItem: updateItem.index == 0 ? nil : self.items[updateItem.index - 1], nextItem: updateItem.index == (self.items.count - 1) ? nil : self.items[updateItem.index + 1], params: ListViewItemLayoutParams(width: state.visibleSize.width, leftInset: state.insets.left, rightInset: state.insets.right, availableHeight: state.visibleSize.height - state.insets.top - state.insets.bottom), updateAnimationIsAnimated: animated, updateAnimationIsCrossfade: crossfade, customAnimationTransition: customAnimationTransition, completion: { _, layout, apply in
state.updateNodeAtItemIndex(updateItem.index, layout: layout, direction: updateItem.directionHint, isAnimated: animated, apply: apply, operations: &operations)
updateIndicesAndItems.remove(at: 0)
self.updateNodes(synchronous: synchronous, synchronousLoads: synchronousLoads, crossfade: crossfade, animated: animated, updateIndicesAndItems: updateIndicesAndItems, inputState: state, previousNodes: previousNodes, inputOperations: operations, completion: completion)
self.updateNodes(synchronous: synchronous, synchronousLoads: synchronousLoads, crossfade: crossfade, customAnimationTransition: customAnimationTransition, animated: animated, updateIndicesAndItems: updateIndicesAndItems, inputState: state, previousNodes: previousNodes, inputOperations: operations, completion: completion)
})
break
} else {
@ -2621,7 +2645,7 @@ open class ListView: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDel
}
}
private func replayOperations(animated: Bool, animateAlpha: Bool, animateCrossfade: Bool, animateFullTransition: Bool, synchronous: Bool, synchronousLoads: Bool, animateTopItemVerticalOrigin: Bool, operations: [ListViewStateOperation], requestItemInsertionAnimationsIndices: Set<Int>, scrollToItem originalScrollToItem: ListViewScrollToItem?, additionalScrollDistance: CGFloat, updateSizeAndInsets: ListViewUpdateSizeAndInsets?, stationaryItemIndex: Int?, updateOpaqueState: Any?, forceInvertOffsetDirection: Bool = false, completion: () -> Void) {
private func replayOperations(animated: Bool, animateAlpha: Bool, animateCrossfade: Bool, animateFullTransition: Bool, customAnimationTransition: ControlledTransition?, synchronous: Bool, synchronousLoads: Bool, animateTopItemVerticalOrigin: Bool, operations: [ListViewStateOperation], requestItemInsertionAnimationsIndices: Set<Int>, scrollToItem originalScrollToItem: ListViewScrollToItem?, additionalScrollDistance: CGFloat, updateSizeAndInsets: ListViewUpdateSizeAndInsets?, stationaryItemIndex: Int?, updateOpaqueState: Any?, forceInvertOffsetDirection: Bool = false, completion: () -> Void) {
var scrollToItem: ListViewScrollToItem?
var isExperimentalSnapToScrollToItem = false
if let originalScrollToItem = originalScrollToItem {
@ -2896,7 +2920,24 @@ open class ListView: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDel
var offsetRanges = OffsetRanges()
if animated {
if let customAnimationTransition {
node.apparentHeight = updatedApparentHeight
let apparentHeightDelta = updatedApparentHeight - previousApparentHeight
if apparentHeightDelta != 0.0 {
var apparentFrame = node.apparentFrame
apparentFrame.origin.y += offsetRanges.offsetForIndex(index)
if apparentFrame.maxY < self.insets.top {
offsetRanges.offset(IndexRange(first: 0, last: index), offset: -apparentHeightDelta)
} else {
offsetRanges.offset(IndexRange(first: index + 1, last: Int.max), offset: apparentHeightDelta)
}
}
if previousApparentHeight != updatedApparentHeight {
customAnimationTransition.legacyAnimator.transition.animateOffsetAdditive(node: node, offset: previousApparentHeight - updatedApparentHeight)
}
} else if animated {
if updatedInsets != previousInsets {
node.insets = previousInsets
node.addInsetsAnimationToValue(updatedInsets, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp)
@ -2966,7 +3007,7 @@ open class ListView: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDel
if offset != 0.0 {
var frame = itemNode.frame
frame.origin.y += offset
itemNode.updateFrame(frame, within: self.visibleSize)
itemNode.updateFrame(frame, within: self.visibleSize, transition: customAnimationTransition)
}
index += 1
@ -3129,7 +3170,7 @@ open class ListView: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDel
self.visibleSize = updateSizeAndInsets.size
for itemNode in self.itemNodes {
itemNode.updateFrame(itemNode.frame.offsetBy(dx: 0.0, dy: offsetFix), within: self.visibleSize)
itemNode.updateFrame(itemNode.frame.offsetBy(dx: 0.0, dy: offsetFix), within: self.visibleSize, transition: customAnimationTransition)
}
let (snappedTopInset, snapToBoundsOffset) = self.snapToBounds(snapTopItem: scrollToItem != nil && scrollToItem?.directionHint != .Down, stackFromBottom: self.stackFromBottom, updateSizeAndInsets: updateSizeAndInsets, isExperimentalSnapToScrollToItem: isExperimentalSnapToScrollToItem, insetDeltaOffsetFix: insetDeltaOffsetFix)
@ -3859,22 +3900,25 @@ open class ListView: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDel
case let .animated(duration, curve):
let previousFrame = headerNode.frame
headerNode.updateFrame(headerFrame, within: self.visibleSize)
var offset = headerFrame.minY - previousFrame.minY + transition.2
var offsetY = headerFrame.minY - previousFrame.minY + transition.2
var offsetX: CGFloat = 0.0
if headerNode.isRotated {
offset = -offset
offsetY = -offsetY
offsetX = headerFrame.width - previousFrame.width
}
let offset = CGPoint(x: offsetX, y: offsetY)
switch curve {
case .linear:
headerNode.layer.animateBoundsOriginYAdditive(from: offset, to: 0.0, duration: duration, mediaTimingFunction: CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear))
headerNode.layer.animateBoundsOriginAdditive(from: offset, to: CGPoint(), duration: duration, mediaTimingFunction: CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear))
case .spring, .customSpring:
transition.0.animateOffsetAdditive(node: headerNode, offset: offset)
case let .custom(p1, p2, p3, p4):
headerNode.layer.animateBoundsOriginYAdditive(from: offset, to: 0.0, duration: duration, mediaTimingFunction: CAMediaTimingFunction(controlPoints: p1, p2, p3, p4))
headerNode.layer.animateBoundsOriginAdditive(from: offset, to: CGPoint(), duration: duration, mediaTimingFunction: CAMediaTimingFunction(controlPoints: p1, p2, p3, p4))
case .easeInOut:
if transition.1 {
headerNode.layer.animateBoundsOriginYAdditive(from: offset, to: 0.0, duration: duration, mediaTimingFunction: ContainedViewLayoutTransitionCurve.slide.mediaTimingFunction)
headerNode.layer.animateBoundsOriginAdditive(from: offset, to: CGPoint(), duration: duration, mediaTimingFunction: ContainedViewLayoutTransitionCurve.slide.mediaTimingFunction)
} else {
headerNode.layer.animateBoundsOriginYAdditive(from: offset, to: 0.0, duration: duration, mediaTimingFunction: CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut))
headerNode.layer.animateBoundsOriginAdditive(from: offset, to: CGPoint(), duration: duration, mediaTimingFunction: CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut))
}
}
}
@ -3883,7 +3927,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)
headerNode.updateLayoutInternal(size: headerFrame.size, leftInset: leftInset, rightInset: rightInset, transition: transition.0)
headerNode.updateInternalStickLocationDistanceFactor(stickLocationDistanceFactor, animated: true)
headerNode.internalStickLocationDistance = stickLocationDistance
if !hasValidNodes && !headerNode.alpha.isZero {
@ -3906,7 +3950,7 @@ open class ListView: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDel
}
headerNode.updateFlashingOnScrolling(flashing, animated: false)
headerNode.frame = headerFrame
headerNode.updateLayoutInternal(size: headerFrame.size, leftInset: leftInset, rightInset: rightInset)
headerNode.updateLayoutInternal(size: headerFrame.size, leftInset: leftInset, rightInset: rightInset, transition: transition.0)
headerNode.updateInternalStickLocationDistanceFactor(stickLocationDistanceFactor, animated: false)
self.itemHeaderNodes[id] = headerNode
if let verticalScrollIndicator = self.verticalScrollIndicator {
@ -4368,15 +4412,15 @@ open class ListView: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDel
let state = self.currentState()
let begin: () -> Void = {
self.fillMissingNodes(synchronous: synchronous, synchronousLoads: false, animated: false, inputAnimatedInsertIndices: [], insertDirectionHints: [:], inputState: state, inputPreviousNodes: [:], inputOperations: []) { state, operations in
self.fillMissingNodes(synchronous: synchronous, synchronousLoads: false, animated: false, customAnimationTransition: nil, inputAnimatedInsertIndices: [], insertDirectionHints: [:], inputState: state, inputPreviousNodes: [:], inputOperations: []) { state, operations in
var updatedState = state
var updatedOperations = operations
updatedState.removeInvisibleNodes(&updatedOperations)
if synchronous {
self.replayOperations(animated: false, animateAlpha: false, animateCrossfade: false, animateFullTransition: false, synchronous: false, synchronousLoads: false, animateTopItemVerticalOrigin: false, operations: updatedOperations, requestItemInsertionAnimationsIndices: Set(), scrollToItem: nil, additionalScrollDistance: 0.0, updateSizeAndInsets: nil, stationaryItemIndex: nil, updateOpaqueState: nil, completion: completion)
self.replayOperations(animated: false, animateAlpha: false, animateCrossfade: false, animateFullTransition: false, customAnimationTransition: nil, synchronous: false, synchronousLoads: false, animateTopItemVerticalOrigin: false, operations: updatedOperations, requestItemInsertionAnimationsIndices: Set(), scrollToItem: nil, additionalScrollDistance: 0.0, updateSizeAndInsets: nil, stationaryItemIndex: nil, updateOpaqueState: nil, completion: completion)
} else {
self.dispatchOnVSync {
self.replayOperations(animated: false, animateAlpha: false, animateCrossfade: false, animateFullTransition: false, synchronous: false, synchronousLoads: false, animateTopItemVerticalOrigin: false, operations: updatedOperations, requestItemInsertionAnimationsIndices: Set(), scrollToItem: nil, additionalScrollDistance: 0.0, updateSizeAndInsets: nil, stationaryItemIndex: nil, updateOpaqueState: nil, completion: completion)
self.replayOperations(animated: false, animateAlpha: false, animateCrossfade: false, animateFullTransition: false, customAnimationTransition: nil, synchronous: false, synchronousLoads: false, animateTopItemVerticalOrigin: false, operations: updatedOperations, requestItemInsertionAnimationsIndices: Set(), scrollToItem: nil, additionalScrollDistance: 0.0, updateSizeAndInsets: nil, stationaryItemIndex: nil, updateOpaqueState: nil, completion: completion)
}
}
}

View file

@ -141,8 +141,9 @@ public struct ListViewUpdateSizeAndInsets {
public var duration: Double
public var curve: ListViewAnimationCurve
public var ensureTopInsetForOverlayHighlightedItems: CGFloat?
public var customAnimationTransition: ControlledTransition?
public init(size: CGSize, insets: UIEdgeInsets, headerInsets: UIEdgeInsets? = nil, scrollIndicatorInsets: UIEdgeInsets? = nil, duration: Double, curve: ListViewAnimationCurve, ensureTopInsetForOverlayHighlightedItems: CGFloat? = nil) {
public init(size: CGSize, insets: UIEdgeInsets, headerInsets: UIEdgeInsets? = nil, scrollIndicatorInsets: UIEdgeInsets? = nil, duration: Double, curve: ListViewAnimationCurve, ensureTopInsetForOverlayHighlightedItems: CGFloat? = nil, customAnimationTransition: ControlledTransition? = nil) {
self.size = size
self.insets = insets
self.headerInsets = headerInsets
@ -150,6 +151,7 @@ public struct ListViewUpdateSizeAndInsets {
self.duration = duration
self.curve = curve
self.ensureTopInsetForOverlayHighlightedItems = ensureTopInsetForOverlayHighlightedItems
self.customAnimationTransition = customAnimationTransition
}
}

View file

@ -128,7 +128,7 @@ open class ListViewItemHeaderNode: ASDisplayNode {
private var cachedLayout: (CGSize, CGFloat, CGFloat)?
public func updateLayoutInternal(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) {
public func updateLayoutInternal(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) {
var update = false
if let cachedLayout = self.cachedLayout {
if cachedLayout.0 != size || cachedLayout.1 != leftInset || cachedLayout.2 != rightInset {
@ -139,11 +139,11 @@ open class ListViewItemHeaderNode: ASDisplayNode {
}
if update {
self.cachedLayout = (size, leftInset, rightInset)
self.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset)
self.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, transition: transition)
}
}
open func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) {
open func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) {
}
open func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {

View file

@ -629,9 +629,16 @@ open class ListViewItemNode: ASDisplayNode, AccessibilityFocusableNode {
(self.supernode as? ListView)?.ensureItemNodeVisible(self, animated: false, overflow: 22.0, allowIntersection: true)
}
public func updateFrame(_ frame: CGRect, within containerSize: CGSize, updateFrame: Bool = true) {
public func updateFrame(_ frame: CGRect, within containerSize: CGSize, updateFrame: Bool = true, transition: ControlledTransition? = nil) {
if updateFrame {
self.frame = frame
if let transition {
let previousFrame = self.frame
self.frame = frame
transition.legacyAnimator.transition.animatePositionAdditive(layer: self.layer, offset: CGPoint(x: previousFrame.minX - frame.minX, y: previousFrame.minY - frame.minY))
} else {
self.frame = frame
}
}
if frame.maxY < 0.0 || frame.minY > containerSize.height {
} else {

View file

@ -2201,11 +2201,11 @@ public final class ItemListPeerItemHeaderNode: ListViewItemHeaderNode, ItemListH
self.actionTextNode.attributedText = NSAttributedString(string: actionTitle ?? "", font: titleFont, textColor: action == nil ? theme.list.sectionHeaderTextColor : theme.list.itemAccentColor)
self.actionButton.isUserInteractionEnabled = self.action != nil
if let (size, leftInset, rightInset) = self.validLayout {
self.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset)
self.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, transition: .immediate)
}
}
override public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) {
override public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) {
self.validLayout = (size, leftInset, rightInset)
self.backgroundNode.frame = CGRect(origin: CGPoint(), size: size)
self.snappedBackgroundNode.frame = CGRect(origin: CGPoint(), size: size)

View file

@ -117,7 +117,7 @@ public final class ListMessageDateHeaderNode: ListViewItemHeaderNode {
self.setNeedsLayout()
}
override public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) {
override public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) {
let headerFrame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: size.width, height: size.height + UIScreenPixel))
self.headerNode.frame = headerFrame
self.headerNode.updateLayout(size: headerFrame.size, leftInset: leftInset, rightInset: rightInset)

View file

@ -253,7 +253,7 @@ private final class BubbleSettingsControllerNode: ASDisplayNode, ASScrollViewDel
}
transition.updateFrame(node: dateHeaderNode, frame: CGRect(origin: CGPoint(x: 0.0, y: bottomOffset), size: CGSize(width: layout.size.width, height: headerItem.height)))
dateHeaderNode.updateLayout(size: self.messagesContainerNode.frame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right)
dateHeaderNode.updateLayout(size: self.messagesContainerNode.frame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: .immediate)
}
func updatePresentationThemeSettings(_ presentationThemeSettings: PresentationThemeSettings) {

View file

@ -524,7 +524,7 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, ASScrollView
}
transition.updateFrame(node: dateHeaderNode, frame: CGRect(origin: CGPoint(x: 0.0, y: bottomOffset), size: CGSize(width: layout.size.width, height: headerItem.height)))
dateHeaderNode.updateLayout(size: self.messagesContainerNode.frame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right)
dateHeaderNode.updateLayout(size: self.messagesContainerNode.frame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: .immediate)
}
func updatePresentationThemeSettings(_ presentationThemeSettings: PresentationThemeSettings) {

View file

@ -696,7 +696,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, ASScrollViewDelegate {
}
transition.updateFrame(node: dateHeaderNode, frame: CGRect(origin: CGPoint(x: 0.0, y: bottomOffset), size: CGSize(width: layout.size.width, height: headerItem.height)))
dateHeaderNode.updateLayout(size: self.messagesContainerNode.frame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right)
dateHeaderNode.updateLayout(size: self.messagesContainerNode.frame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: .immediate)
}
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {

View file

@ -228,7 +228,6 @@ private var declaredEncodables: Void = {
declareEncodable(DerivedDataMessageAttribute.self, f: { DerivedDataMessageAttribute(decoder: $0) })
declareEncodable(TelegramApplicationIcons.self, f: { TelegramApplicationIcons(decoder: $0) })
declareEncodable(OutgoingQuickReplyMessageAttribute.self, f: { OutgoingQuickReplyMessageAttribute(decoder: $0) })
declareEncodable(OutgoingSuggestedPostMessageAttribute.self, f: { OutgoingSuggestedPostMessageAttribute(decoder: $0) })
declareEncodable(EffectMessageAttribute.self, f: { EffectMessageAttribute(decoder: $0) })
declareEncodable(FactCheckMessageAttribute.self, f: { FactCheckMessageAttribute(decoder: $0) })
declareEncodable(TelegramMediaPaidContent.self, f: { TelegramMediaPaidContent(decoder: $0) })

View file

@ -236,8 +236,6 @@ private func filterMessageAttributesForOutgoingMessage(_ attributes: [MessageAtt
return true
case _ as OutgoingQuickReplyMessageAttribute:
return true
case _ as OutgoingSuggestedPostMessageAttribute:
return true
case _ as EmbeddedMediaStickersMessageAttribute:
return true
case _ as EmojiSearchQueryMessageAttribute:
@ -277,8 +275,6 @@ private func filterMessageAttributesForForwardedMessage(_ attributes: [MessageAt
return true
case _ as OutgoingQuickReplyMessageAttribute:
return true
case _ as OutgoingSuggestedPostMessageAttribute:
return true
case _ as ForwardOptionsMessageAttribute:
return true
case _ as SendAsMessageAttribute:
@ -737,9 +733,6 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId,
} else if attribute is OutgoingQuickReplyMessageAttribute {
messageNamespace = Namespaces.Message.QuickReplyLocal
effectiveTimestamp = 0
} else if attribute is OutgoingSuggestedPostMessageAttribute {
messageNamespace = Namespaces.Message.SuggestedPostLocal
effectiveTimestamp = 0
} else if let attribute = attribute as? SendAsMessageAttribute {
if let peer = transaction.getPeer(attribute.peerId) {
sendAsPeer = peer
@ -776,9 +769,6 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId,
if messageNamespace != Namespaces.Message.QuickReplyLocal {
attributes.removeAll(where: { $0 is OutgoingQuickReplyMessageAttribute })
}
if messageNamespace != Namespaces.Message.SuggestedPostLocal {
attributes.removeAll(where: { $0 is OutgoingSuggestedPostMessageAttribute })
}
if let peer = peer as? TelegramChannel {
switch peer.info {
@ -1013,9 +1003,6 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId,
} else if attribute is OutgoingQuickReplyMessageAttribute {
messageNamespace = Namespaces.Message.QuickReplyLocal
effectiveTimestamp = 0
} else if attribute is OutgoingSuggestedPostMessageAttribute {
messageNamespace = Namespaces.Message.SuggestedPostLocal
effectiveTimestamp = 0
} else if let attribute = attribute as? ReplyMessageAttribute {
if let threadMessageId = attribute.threadMessageId {
threadId = Int64(threadMessageId.id)
@ -1048,9 +1035,6 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId,
if messageNamespace != Namespaces.Message.QuickReplyLocal {
attributes.removeAll(where: { $0 is OutgoingQuickReplyMessageAttribute })
}
if messageNamespace != Namespaces.Message.SuggestedPostLocal {
attributes.removeAll(where: { $0 is OutgoingSuggestedPostMessageAttribute })
}
let (tags, globalTags) = tagsForStoreMessage(incoming: false, attributes: attributes, media: sourceMessage.media, textEntities: entitiesAttribute?.entities, isPinned: false)

View file

@ -179,9 +179,6 @@ private func requestEditMessageInternal(accountPeerId: PeerId, postbox: Postbox,
if messageId.namespace == Namespaces.Message.QuickReplyCloud {
quickReplyShortcutId = Int32(clamping: message.threadId ?? 0)
flags |= Int32(1 << 17)
} else if messageId.namespace == Namespaces.Message.SuggestedPostLocal {
//TODO:release
preconditionFailure()
}
return network.request(Api.functions.messages.editMessage(flags: flags, peer: inputPeer, id: messageId.id, message: text, media: inputMedia, replyMarkup: nil, entities: apiEntities, scheduleDate: effectiveScheduleTime, quickReplyShortcutId: quickReplyShortcutId))

View file

@ -72,8 +72,6 @@ private func fetchWebpage(account: Account, messageId: MessageId, threadId: Int6
targetMessageNamespace = Namespaces.Message.ScheduledCloud
} else if Namespaces.Message.allQuickReply.contains(messageId.namespace) {
targetMessageNamespace = Namespaces.Message.QuickReplyCloud
} else if Namespaces.Message.allSuggestedPost.contains(messageId.namespace) {
targetMessageNamespace = Namespaces.Message.SuggestedPostCloud
} else {
targetMessageNamespace = Namespaces.Message.Cloud
}
@ -1073,10 +1071,6 @@ public final class AccountViewTracker {
} else {
fetchSignal = .never()
}
} else if let messageId = messageIds.first, messageId.namespace == Namespaces.Message.SuggestedPostCloud {
//TODO:release
assertionFailure()
fetchSignal = .never()
} else if peerIdAndThreadId.peerId.namespace == Namespaces.Peer.CloudUser || peerIdAndThreadId.peerId.namespace == Namespaces.Peer.CloudGroup {
fetchSignal = account.network.request(Api.functions.messages.getMessages(id: messageIds.map { Api.InputMessage.inputMessageID(id: $0.id) }))
} else if peerIdAndThreadId.peerId.namespace == Namespaces.Peer.CloudChannel {
@ -2126,36 +2120,6 @@ public final class AccountViewTracker {
}
return signal
}
public func postSuggestionsViewForLocation(peerId: EnginePeer.Id, additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> {
guard let account = self.account else {
return .never()
}
let chatLocation: ChatLocationInput = .peer(peerId: peerId, threadId: nil)
let signal = account.postbox.aroundMessageHistoryViewForLocation(chatLocation, anchor: .upperBound, ignoreMessagesInTimestampRange: nil, ignoreMessageIds: Set(), count: 200, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tag: nil, appendMessagesFromTheSameGroup: false, namespaces: .just(Namespaces.Message.allSuggestedPost), orderStatistics: [], additionalData: additionalData)
return withState(signal, { [weak self] () -> Int32 in
if let strongSelf = self {
return OSAtomicIncrement32(&strongSelf.nextViewId)
} else {
return -1
}
}, next: { [weak self] next, viewId in
if let strongSelf = self {
strongSelf.queue.async {
let (messageIds, localWebpages) = pendingWebpages(entries: next.0.entries)
strongSelf.updatePendingWebpages(viewId: viewId, threadId: nil, messageIds: messageIds, localWebpages: localWebpages)
strongSelf.historyViewStateValidationContexts.updateView(id: viewId, view: next.0, location: chatLocation)
}
}
}, disposed: { [weak self] viewId in
if let strongSelf = self {
strongSelf.queue.async {
strongSelf.updatePendingWebpages(viewId: viewId, threadId: nil, messageIds: [], localWebpages: [:])
strongSelf.historyViewStateValidationContexts.updateView(id: viewId, view: nil, location: nil)
}
}
})
}
public func aroundMessageOfInterestHistoryViewForLocation(_ chatLocation: ChatLocationInput, ignoreMessagesInTimestampRange: ClosedRange<Int32>? = nil, ignoreMessageIds: Set<MessageId> = Set(), count: Int, tag: HistoryViewInputTag? = nil, appendMessagesFromTheSameGroup: Bool = false, orderStatistics: MessageHistoryViewOrderStatistics = [], additionalData: [AdditionalMessageHistoryViewData] = [], useRootInterfaceStateForThread: Bool = false) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> {
if let account = self.account {

View file

@ -198,14 +198,6 @@ func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, mes
}
}
}
if Namespaces.Message.allSuggestedPost.contains(message.id.namespace) {
for i in 0 ..< updatedAttributes.count {
if updatedAttributes[i] is OutgoingSuggestedPostMessageAttribute {
updatedAttributes.remove(at: i)
break
}
}
}
attributes = updatedAttributes
text = currentMessage.text
@ -228,8 +220,6 @@ func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, mes
}
if Namespaces.Message.allQuickReply.contains(message.id.namespace) {
namespace = Namespaces.Message.QuickReplyCloud
} else if Namespaces.Message.allSuggestedPost.contains(message.id.namespace) {
namespace = Namespaces.Message.SuggestedPostCloud
} else if let updatedTimestamp = updatedTimestamp {
if attributes.contains(where: { $0 is PendingProcessingMessageAttribute }) {
namespace = Namespaces.Message.ScheduledCloud
@ -253,8 +243,6 @@ func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, mes
if let threadId {
_internal_applySentQuickReplyMessage(transaction: transaction, shortcut: attribute.shortcut, quickReplyId: Int32(clamping: threadId))
}
} else if attribute is OutgoingSuggestedPostMessageAttribute {
//TODO:release
}
}
@ -409,8 +397,6 @@ func applyUpdateGroupMessages(postbox: Postbox, stateManager: AccountStateManage
var namespace = Namespaces.Message.Cloud
if Namespaces.Message.allQuickReply.contains(messages[0].id.namespace) {
namespace = Namespaces.Message.QuickReplyCloud
} else if Namespaces.Message.allSuggestedPost.contains(messages[0].id.namespace) {
namespace = Namespaces.Message.SuggestedPostCloud
} else if let message = messages.first, let apiMessage = result.messages.first {
if message.scheduleTime != nil && message.scheduleTime == apiMessage.timestamp {
namespace = Namespaces.Message.ScheduledCloud
@ -488,8 +474,6 @@ func applyUpdateGroupMessages(postbox: Postbox, stateManager: AccountStateManage
if let threadId = updatedMessage.threadId {
_internal_applySentQuickReplyMessage(transaction: transaction, shortcut: attribute.shortcut, quickReplyId: Int32(clamping: threadId))
}
} else if attribute is OutgoingSuggestedPostMessageAttribute {
//TODO:release
}
}
}

View file

@ -35,25 +35,6 @@ func cloudChatAddClearHistoryOperation(transaction: Transaction, peerId: PeerId,
} else if case .forEveryone = type {
transaction.operationLogAddEntry(peerId: peerId, tag: OperationLogTags.CloudChatRemoveMessages, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: CloudChatClearHistoryOperation(peerId: peerId, topMessageId: MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: .max), threadId: threadId, minTimestamp: minTimestamp, maxTimestamp: maxTimestamp, type: type))
}
} else if type == .suggestedPostMessages {
var messageIds: [MessageId] = []
transaction.withAllMessages(peerId: peerId, namespace: Namespaces.Message.SuggestedPostCloud) { message -> Bool in
messageIds.append(message.id)
return true
}
cloudChatAddRemoveMessagesOperation(transaction: transaction, peerId: peerId, threadId: threadId, messageIds: messageIds, type: .forLocalPeer)
let topMessageId: MessageId?
if let explicitTopMessageId = explicitTopMessageId {
topMessageId = explicitTopMessageId
} else {
topMessageId = transaction.getTopPeerMessageId(peerId: peerId, namespace: Namespaces.Message.SuggestedPostCloud)
}
if let topMessageId = topMessageId {
transaction.operationLogAddEntry(peerId: peerId, tag: OperationLogTags.CloudChatRemoveMessages, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: CloudChatClearHistoryOperation(peerId: peerId, topMessageId: topMessageId, threadId: threadId, minTimestamp: minTimestamp, maxTimestamp: maxTimestamp, type: type))
} else if case .forEveryone = type {
transaction.operationLogAddEntry(peerId: peerId, tag: OperationLogTags.CloudChatRemoveMessages, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: CloudChatClearHistoryOperation(peerId: peerId, topMessageId: MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: .max), threadId: threadId, minTimestamp: minTimestamp, maxTimestamp: maxTimestamp, type: type))
}
} else {
let topMessageId: MessageId?
if let explicitTopMessageId = explicitTopMessageId {

View file

@ -128,7 +128,6 @@ func managedCloudChatRemoveMessagesOperations(postbox: Postbox, network: Network
private func removeMessages(postbox: Postbox, network: Network, stateManager: AccountStateManager, peer: Peer, operation: CloudChatRemoveMessagesOperation) -> Signal<Void, NoError> {
var isScheduled = false
var isQuickReply = false
var isSuggestedPost = false
for id in operation.messageIds {
if id.namespace == Namespaces.Message.ScheduledCloud {
isScheduled = true
@ -136,9 +135,6 @@ private func removeMessages(postbox: Postbox, network: Network, stateManager: Ac
} else if id.namespace == Namespaces.Message.QuickReplyCloud {
isQuickReply = true
break
} else if id.namespace == Namespaces.Message.SuggestedPostCloud {
isSuggestedPost = true
break
}
}
@ -194,10 +190,6 @@ private func removeMessages(postbox: Postbox, network: Network, stateManager: Ac
} else {
return .complete()
}
} else if isSuggestedPost {
//TODO:release
assertionFailure()
return .complete()
} else if peer.id.namespace == Namespaces.Peer.CloudChannel {
if let inputChannel = apiInputChannel(peer) {
var signal: Signal<Void, NoError> = .complete()

View file

@ -858,15 +858,11 @@ public final class PendingMessageManager {
var videoTimestamp: Int32?
var sendAsPeerId: PeerId?
var quickReply: OutgoingQuickReplyMessageAttribute?
var suggestedPost: OutgoingSuggestedPostMessageAttribute?
var messageEffect: EffectMessageAttribute?
var allowPaidStars: Int64?
var flags: Int32 = 0
//TODO:release
let _ = suggestedPost
for attribute in messages[0].0.attributes {
if let replyAttribute = attribute as? ReplyMessageAttribute {
replyMessageId = replyAttribute.messageId.id
@ -894,8 +890,6 @@ public final class PendingMessageManager {
sendAsPeerId = attribute.peerId
} else if let attribute = attribute as? OutgoingQuickReplyMessageAttribute {
quickReply = attribute
} else if let attribute = attribute as? OutgoingSuggestedPostMessageAttribute {
suggestedPost = attribute
} else if let attribute = attribute as? EffectMessageAttribute {
messageEffect = attribute
} else if let _ = attribute as? InvertMediaMessageAttribute {
@ -1846,8 +1840,6 @@ public final class PendingMessageManager {
targetNamespace = Namespaces.Message.ScheduledCloud
} else if Namespaces.Message.allQuickReply.contains(message.id.namespace) {
targetNamespace = Namespaces.Message.QuickReplyCloud
} else if Namespaces.Message.allSuggestedPost.contains(message.id.namespace) {
targetNamespace = Namespaces.Message.SuggestedPostCloud
} else {
targetNamespace = Namespaces.Message.Cloud
}
@ -1899,8 +1891,6 @@ public final class PendingMessageManager {
if let message = messages.first {
if message.id.namespace == Namespaces.Message.QuickReplyLocal {
namespace = Namespaces.Message.QuickReplyCloud
} else if Namespaces.Message.allSuggestedPost.contains(message.id.namespace) {
namespace = Namespaces.Message.SuggestedPostCloud
} else if let apiMessage = result.messages.first, message.scheduleTime != nil && message.scheduleTime == apiMessage.timestamp {
namespace = Namespaces.Message.ScheduledCloud
} else if let apiMessage = result.messages.first, case let .message(_, flags2, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = apiMessage, (flags2 & (1 << 4)) != 0 {

View file

@ -1,37 +0,0 @@
import Foundation
import Postbox
import TelegramApi
public final class OutgoingSuggestedPostMessageAttribute: Equatable, MessageAttribute {
public let price: StarsAmount
public let timestamp: Int32?
public init(price: StarsAmount, timestamp: Int32?) {
self.price = price
self.timestamp = timestamp
}
required public init(decoder: PostboxDecoder) {
self.price = decoder.decodeCodable(StarsAmount.self, forKey: "s") ?? StarsAmount(value: 0, nanos: 0)
self.timestamp = decoder.decodeOptionalInt32ForKey("t")
}
public func encode(_ encoder: PostboxEncoder) {
encoder.encodeCodable(self.price, forKey: "s")
if let timestamp = self.timestamp {
encoder.encodeInt32(timestamp, forKey: "t")
} else {
encoder.encodeNil(forKey: "t")
}
}
public static func ==(lhs: OutgoingSuggestedPostMessageAttribute, rhs: OutgoingSuggestedPostMessageAttribute) -> Bool {
if lhs.price != rhs.price {
return false
}
if lhs.timestamp != rhs.timestamp {
return false
}
return true
}
}

View file

@ -97,7 +97,6 @@ public enum CloudChatClearHistoryType: Int32 {
case forEveryone
case scheduledMessages
case quickReplyMessages
case suggestedPostMessages
}
public enum InteractiveHistoryClearingType: Int32 {

View file

@ -10,13 +10,10 @@ public struct Namespaces {
public static let ScheduledLocal: Int32 = 4
public static let QuickReplyCloud: Int32 = 5
public static let QuickReplyLocal: Int32 = 6
public static let SuggestedPostLocal: Int32 = 7
public static let SuggestedPostCloud: Int32 = 8
public static let allScheduled: Set<Int32> = Set([Namespaces.Message.ScheduledCloud, Namespaces.Message.ScheduledLocal])
public static let allQuickReply: Set<Int32> = Set([Namespaces.Message.QuickReplyCloud, Namespaces.Message.QuickReplyLocal])
public static let allSuggestedPost: Set<Int32> = Set([Namespaces.Message.SuggestedPostCloud, Namespaces.Message.SuggestedPostLocal])
public static let allNonRegular: Set<Int32> = Set([Namespaces.Message.ScheduledCloud, Namespaces.Message.ScheduledLocal, Namespaces.Message.QuickReplyCloud, Namespaces.Message.QuickReplyLocal, Namespaces.Message.SuggestedPostCloud, Namespaces.Message.SuggestedPostLocal])
public static let allNonRegular: Set<Int32> = Set([Namespaces.Message.ScheduledCloud, Namespaces.Message.ScheduledLocal, Namespaces.Message.QuickReplyCloud, Namespaces.Message.QuickReplyLocal])
public static let allLocal: [Int32] = [
Namespaces.Message.Local,
Namespaces.Message.SecretIncoming,

View file

@ -701,9 +701,6 @@ func fetchRemoteMessage(accountPeerId: PeerId, postbox: Postbox, source: FetchMe
} else {
signal = .never()
}
} else if id.namespace == Namespaces.Message.SuggestedPostCloud {
//TODO:release
signal = .never()
} else if id.peerId.namespace == Namespaces.Peer.CloudChannel {
if let channel = peer.inputChannel {
signal = source.request(Api.functions.channels.getMessages(channel: channel, id: [Api.InputMessage.inputMessageID(id: id.id)]))

View file

@ -338,6 +338,8 @@ public enum PresentationResourceKey: Int32 {
case expandSmallDownArrowImage
case callListCallIcon
case chatFreeNavigateToThreadButtonIcon
}
public enum ChatExpiredStoryIndicatorType: Hashable {

View file

@ -1105,7 +1105,24 @@ public struct PresentationResourcesChat {
public static func chatFreeNavigateButtonIcon(_ theme: PresentationTheme, wallpaper: TelegramWallpaper) -> UIImage? {
return theme.image(PresentationResourceKey.chatFreeNavigateButtonIcon.rawValue, { _ in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/NavigateToMessageIcon"), color: bubbleVariableColor(variableColor: theme.chat.message.shareButtonForegroundColor, wallpaper: wallpaper))
return generateTintedImage(image: UIImage(bundleImageName: "Settings/TextArrowRight"), color: bubbleVariableColor(variableColor: theme.chat.message.shareButtonForegroundColor, wallpaper: wallpaper))
})
}
public static func chatFreeNavigateToThreadButtonIcon(_ theme: PresentationTheme, wallpaper: TelegramWallpaper) -> UIImage? {
return theme.image(PresentationResourceKey.chatFreeNavigateToThreadButtonIcon.rawValue, { _ in
return generateImage(CGSize(width: 8.0, height: 14.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setStrokeColor(bubbleVariableColor(variableColor: theme.chat.message.shareButtonForegroundColor, wallpaper: wallpaper).cgColor)
context.setLineWidth(1.66)
context.setLineCap(.round)
context.setLineJoin(.round)
context.beginPath()
context.move(to: CGPoint(x: 1.0, y: 1.0))
context.addLine(to: CGPoint(x: size.width - 1.0, y: size.height / 2.0))
context.addLine(to: CGPoint(x: 1.0, y: size.height - 1.0))
context.strokePath()
})
})
}

View file

@ -781,10 +781,6 @@ private final class ChatEmptyNodeCloudChatContent: ASDisplayNode, ChatEmptyNodeC
insets.top = -9.0
imageSpacing = 4.0
titleSpacing = 5.0
case .postSuggestions:
insets.top = 10.0
imageSpacing = 5.0
titleSpacing = 5.0
case .hashTagSearch:
break
}
@ -846,7 +842,7 @@ private final class ChatEmptyNodeCloudChatContent: ASDisplayNode, ChatEmptyNodeC
}
self.businessLink = link
case .hashTagSearch, .postSuggestions:
case .hashTagSearch:
titleString = ""
strings = []
}
@ -1303,10 +1299,7 @@ public final class ChatEmptyNodePremiumRequiredChatContent: ASDisplayNode, ChatE
let starsString = presentationStringsFormattedNumber(Int32(amount), interfaceState.dateTimeFormat.groupingSeparator)
let rawText: String
if case let .customChatContents(customChatContents) = interfaceState.subject, case .postSuggestions = customChatContents.kind {
//TODO:localize
rawText = "\(peerTitle) charges $ \(starsString) per message suggestion."
} else if self.isPremiumDisabled {
if self.isPremiumDisabled {
rawText = interfaceState.strings.Chat_EmptyStatePaidMessagingDisabled_Text(peerTitle, " $ \(starsString)").string
} else {
rawText = interfaceState.strings.Chat_EmptyStatePaidMessaging_Text(peerTitle, " $ \(starsString)").string
@ -1369,14 +1362,7 @@ public final class ChatEmptyNodePremiumRequiredChatContent: ASDisplayNode, ChatE
contentsHeight += iconTextSpacing
let iconComponent: AnyComponent<Empty>
if case let .customChatContents(customChatContents) = interfaceState.subject, case .postSuggestions = customChatContents.kind {
iconComponent = AnyComponent(
BundleIconComponent(
name: "Chat/Empty Chat/PostSuggestions",
tintColor: serviceColor.primaryText
)
)
} else {
do {
iconComponent = AnyComponent(
LottieComponent(
content: LottieComponent.AppBundleContent(name: "PremiumRequired"),
@ -1447,7 +1433,6 @@ private enum ChatEmptyNodeContentType: Equatable {
case topic
case premiumRequired
case starsRequired(Int64)
case postSuggestions(Int64)
}
private final class EmptyAttachedDescriptionNode: HighlightTrackingButtonNode {
@ -1816,12 +1801,8 @@ public final class ChatEmptyNode: ASDisplayNode {
case let .emptyChat(emptyType):
if case .customGreeting = emptyType {
contentType = .greeting
} else if case let .customChatContents(customChatContents) = interfaceState.subject {
if case let .postSuggestions(postSuggestions) = customChatContents.kind {
contentType = .postSuggestions(postSuggestions.value)
} else {
contentType = .cloud
}
} else if case .customChatContents = interfaceState.subject {
contentType = .cloud
} else if case .replyThread = interfaceState.chatLocation {
if case .topic = emptyType {
contentType = .topic
@ -1908,8 +1889,6 @@ public final class ChatEmptyNode: ASDisplayNode {
node = ChatEmptyNodePremiumRequiredChatContent(context: self.context, interaction: self.interaction, stars: nil)
case let .starsRequired(stars):
node = ChatEmptyNodePremiumRequiredChatContent(context: self.context, interaction: self.interaction, stars: stars)
case let .postSuggestions(stars):
node = ChatEmptyNodePremiumRequiredChatContent(context: self.context, interaction: self.interaction, stars: stars)
}
self.content = (contentType, node)
self.addSubnode(node)
@ -1921,7 +1900,7 @@ public final class ChatEmptyNode: ASDisplayNode {
}
}
switch contentType {
case .peerNearby, .greeting, .premiumRequired, .starsRequired, .cloud, .postSuggestions:
case .peerNearby, .greeting, .premiumRequired, .starsRequired, .cloud:
self.isUserInteractionEnabled = true
default:
self.isUserInteractionEnabled = false

View file

@ -89,6 +89,7 @@ swift_library(
"//submodules/TelegramAnimatedStickerNode",
"//submodules/TelegramUI/Components/LottieMetal",
"//submodules/TelegramStringFormatting",
"//submodules/AvatarNode",
],
visibility = [
"//visibility:public",

View file

@ -78,6 +78,7 @@ import ChatMessageTransitionNode
import AnimatedStickerNode
import TelegramAnimatedStickerNode
import LottieMetal
import AvatarNode
private struct BubbleItemAttributes {
var index: Int?
@ -629,10 +630,12 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
private var swipeToReplyNode: ChatMessageSwipeToReplyNode?
private var swipeToReplyFeedback: HapticFeedback?
private var nameAvatarNode: AvatarNode?
private var nameNode: TextNode?
private var nameButtonNode: HighlightTrackingButtonNode?
private var nameHighlightNode: ASImageNode?
private var viaMeasureNode: TextNode?
private var nameNavigateButton: NameNavigateButton?
private var adminBadgeNode: TextNode?
private var credibilityIconView: ComponentHostView<Empty>?
@ -1479,6 +1482,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation, ListViewItemApply, Bool) -> Void) {
let isPreview = item.presentationData.isPreview
let accessibilityData = ChatMessageAccessibilityData(item: item, isSelected: isSelected)
let isSidePanelOpen = item.controllerInteraction.isSidePanelOpen
let fontSize = floor(item.presentationData.fontSize.baseDisplaySize * 14.0 / 17.0)
let nameFont = Font.semibold(fontSize)
@ -1579,6 +1583,18 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
if let forwardInfo = firstMessage.forwardInfo, forwardInfo.psaType != nil {
displayAuthorInfo = false
}
var isMonoForum = false
if let peer = firstMessage.peers[firstMessage.id.peerId] as? TelegramChannel {
if peer.isMonoForum {
isMonoForum = true
}
}
if isMonoForum {
if case .replyThread = item.chatLocation {
displayAuthorInfo = false
}
}
}
if let channel = firstMessage.peers[firstMessage.id.peerId] as? TelegramChannel, case let .broadcast(info) = channel.info {
@ -1605,9 +1621,14 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
if !peerId.isRepliesOrSavedMessages(accountPeerId: item.context.account.peerId) {
if peerId.isGroupOrChannel && effectiveAuthor != nil {
var isBroadcastChannel = false
if let peer = firstMessage.peers[firstMessage.id.peerId] as? TelegramChannel, case .broadcast = peer.info {
isBroadcastChannel = true
allowFullWidth = true
var isMonoForum = false
if let peer = firstMessage.peers[firstMessage.id.peerId] as? TelegramChannel {
if case .broadcast = peer.info {
isBroadcastChannel = true
allowFullWidth = true
} else if peer.isMonoForum {
isMonoForum = true
}
}
if case let .replyThread(replyThreadMessage) = item.chatLocation, replyThreadMessage.isChannelPost, replyThreadMessage.effectiveTopId == firstMessage.id {
@ -1621,6 +1642,12 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
} else if overrideEffectiveAuthor {
hasAvatar = true
}
if isMonoForum {
if case .replyThread = item.chatLocation {
hasAvatar = false
}
}
}
} else if incoming {
hasAvatar = true
@ -1651,10 +1678,14 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
}
avatarInset = hasAvatar ? layoutConstants.avatarDiameter : 0.0
if isSidePanelOpen {
avatarInset = 0.0
}
let isFailed = item.content.firstMessage.effectivelyFailed(timestamp: item.context.account.network.getApproximateRemoteTimestamp())
var needsShareButton = false
if incoming, case let .customChatContents(contents) = item.associatedData.subject, case .hashTagSearch = contents.kind {
needsShareButton = true
} else if case .pinnedMessages = item.associatedData.subject {
@ -1676,6 +1707,10 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
}
} else if item.message.id.peerId.isRepliesOrVerificationCodes {
needsShareButton = false
} else if let channel = item.content.firstMessage.peers[item.content.firstMessage.id.peerId] as? TelegramChannel, channel.isMonoForum, channel.adminRights != nil, case .peer = item.chatLocation {
if incoming {
needsShareButton = true
}
} else if incoming {
if let _ = sourceReference {
needsShareButton = true
@ -1755,14 +1790,14 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
var tmpWidth: CGFloat
if allowFullWidth {
tmpWidth = baseWidth
if needsShareButton || isAd {
if (needsShareButton && !isSidePanelOpen) || isAd {
tmpWidth -= 45.0
} else {
tmpWidth -= 4.0
}
} else {
tmpWidth = layoutConstants.bubble.maximumWidthFill.widthFor(baseWidth)
if (needsShareButton || isAd) && tmpWidth + 32.0 > baseWidth {
if ((needsShareButton && !isSidePanelOpen) || isAd) && tmpWidth + 32.0 > baseWidth {
tmpWidth = baseWidth - 32.0
}
}
@ -1777,7 +1812,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
let (contentNodeMessagesAndClasses, needSeparateContainers, needReactions) = contentNodeMessagesAndClassesForItem(item)
var maximumContentWidth = floor(tmpWidth - layoutConstants.bubble.edgeInset * 3.0 - layoutConstants.bubble.contentInsets.left - layoutConstants.bubble.contentInsets.right - avatarInset)
if needsShareButton {
if (needsShareButton && !isSidePanelOpen) {
maximumContentWidth -= 10.0
}
@ -2387,6 +2422,8 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
var unlockButtonSizeApply: (CGSize, (Bool) -> ChatMessageUnlockMediaNode?) = (CGSize(), { _ in nil })
var mediaInfoSizeApply: (CGSize, (Bool) -> ChatMessageStarsMediaInfoNode?) = (CGSize(), { _ in nil })
var hasTitleAvatar = false
if displayHeader {
let bubbleWidthInsets: CGFloat = mosaicRange == nil ? layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right : 0.0
if authorNameString != nil || inlineBotNameString != nil {
@ -2394,6 +2431,10 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
headerSize.height += 7.0
}
if isSidePanelOpen {
hasTitleAvatar = true
}
let inlineBotNameColor = messageTheme.accentTextColor
let attributedString: NSAttributedString
@ -2493,8 +2534,15 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
}
nameNodeOriginY = headerSize.height
var nameAvatarSpaceWidth: CGFloat = 0.0
if hasTitleAvatar {
headerSize.height += 12.0
nameAvatarSpaceWidth += 26.0 + 5.0 + 4.0 + 26.0
nameNodeOriginY += 5.0
}
headerSize.width = max(headerSize.width, nameNodeSizeApply.0.width + 8.0 + adminBadgeSizeAndApply.0.size.width + credibilityIconWidth + boostBadgeWidth + closeButtonWidth + bubbleWidthInsets)
headerSize.width = max(headerSize.width, nameAvatarSpaceWidth + nameNodeSizeApply.0.width + 8.0 + adminBadgeSizeAndApply.0.size.width + credibilityIconWidth + boostBadgeWidth + closeButtonWidth + bubbleWidthInsets)
headerSize.height += nameNodeSizeApply.0.height
}
@ -3119,13 +3167,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
}
var suggestedPostInfoNodeLayout: (CGSize, () -> ChatMessageSuggestedPostInfoNode)?
for attribute in item.message.attributes {
if let attribute = attribute as? OutgoingSuggestedPostMessageAttribute {
let _ = attribute
let suggestedPostInfoNodeLayoutValue = makeSuggestedPostInfoNodeLayout(item, baseWidth)
suggestedPostInfoNodeLayout = suggestedPostInfoNodeLayoutValue
}
}
suggestedPostInfoNodeLayout = nil
if let suggestedPostInfoNodeLayout {
additionalTopHeight += 4.0 + suggestedPostInfoNodeLayout.0.height + 8.0
@ -3229,6 +3271,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
viaWidth: viaWidth,
contentOrigin: contentOrigin,
nameNodeOriginY: nameNodeOriginY + detachedContentNodesHeight + additionalTopHeight,
hasTitleAvatar: hasTitleAvatar,
authorNameColor: authorNameColor,
layoutConstants: layoutConstants,
currentCredibilityIcon: currentCredibilityIcon,
@ -3259,7 +3302,8 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
hidesHeaders: hidesHeaders,
disablesComments: disablesComments,
suggestedPostInfoNodeLayout: suggestedPostInfoNodeLayout,
alignment: alignment
alignment: alignment,
isSidePanelOpen: isSidePanelOpen
)
})
}
@ -3290,6 +3334,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
viaWidth: CGFloat,
contentOrigin: CGPoint,
nameNodeOriginY: CGFloat,
hasTitleAvatar: Bool,
authorNameColor: UIColor?,
layoutConstants: ChatMessageItemLayoutConstants,
currentCredibilityIcon: (EmojiStatusComponent.Content, UIColor?)?,
@ -3320,7 +3365,8 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
hidesHeaders: Bool,
disablesComments: Bool,
suggestedPostInfoNodeLayout: (CGSize, () -> ChatMessageSuggestedPostInfoNode)?,
alignment: ChatMessageBubbleContentAlignment
alignment: ChatMessageBubbleContentAlignment,
isSidePanelOpen: Bool
) -> Void {
guard let strongSelf = selfReference.value else {
return
@ -3418,9 +3464,10 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
suggestedPostInfoNode.removeFromSupernode()
}
if let avatarOffset = avatarOffset {
if let avatarOffset {
strongSelf.updateAttachedAvatarNodeOffset(offset: avatarOffset, transition: .animated(duration: 0.3, curve: .spring))
}
strongSelf.updateAttachedAvatarNodeIsHidden(isHidden: isSidePanelOpen, transition: animation.transition)
let isFailed = item.content.firstMessage.effectivelyFailed(timestamp: item.context.account.network.getApproximateRemoteTimestamp())
if isFailed {
@ -3458,8 +3505,91 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
strongSelf.nameNode = nameNode
nameNode.displaysAsynchronously = !item.presentationData.isPreview && !item.presentationData.theme.theme.forceSync
//let previousNameNodeFrame = nameNode.frame
let nameNodeFrame = CGRect(origin: CGPoint(x: contentOrigin.x + layoutConstants.text.bubbleInsets.left, y: layoutConstants.bubble.contentInsets.top + nameNodeOriginY), size: nameNodeSizeApply.0)
let previousNameNodeFrame = nameNode.frame
var nameNodeFrame = CGRect(origin: CGPoint(x: contentOrigin.x + layoutConstants.text.bubbleInsets.left, y: layoutConstants.bubble.contentInsets.top + nameNodeOriginY), size: nameNodeSizeApply.0)
var nameNavigateButtonOffset: CGFloat = currentCredibilityIcon == nil ? 4.0 : 28.0
nameNavigateButtonOffset += 34.0
if hasTitleAvatar {
let nameAvatarNode: AvatarNode
var animateNameAvatar = true
if let current = strongSelf.nameAvatarNode {
nameAvatarNode = current
} else {
animateNameAvatar = false
nameAvatarNode = AvatarNode(font: avatarPlaceholderFont(size: 8.0))
strongSelf.nameAvatarNode = nameAvatarNode
strongSelf.clippingNode.addSubnode(nameAvatarNode)
}
let nameNavigateButton: NameNavigateButton
if let current = strongSelf.nameNavigateButton {
nameNavigateButton = current
} else {
nameNavigateButton = NameNavigateButton(frame: CGRect())
strongSelf.nameNavigateButton = nameNavigateButton
strongSelf.clippingNode.view.addSubview(nameNavigateButton)
nameNavigateButton.action = { [weak strongSelf] in
guard let strongSelf, let item = strongSelf.item else {
return
}
item.controllerInteraction.updateChatLocationThread(item.content.firstMessage.threadId)
}
}
let nameAvatarFrame = CGRect(origin: CGPoint(x: nameNodeFrame.minX, y: nameNodeFrame.minY - 4.0), size: CGSize(width: 26.0, height: 26.0))
let nameNavigateFrame = CGRect(origin: CGPoint(x: nameNodeFrame.maxX + 4.0 + nameNavigateButtonOffset, y: nameNodeFrame.minY - 4.0), size: CGSize(width: 26.0, height: 26.0))
if let peer = item.content.firstMessage.author, peer.smallProfileImage != nil {
nameAvatarNode.setPeerV2(context: item.context, theme: item.presentationData.theme.theme, peer: EnginePeer(peer), displayDimensions: nameAvatarFrame.size)
} else {
nameAvatarNode.setPeer(context: item.context, theme: item.presentationData.theme.theme, peer: item.content.firstMessage.author.flatMap(EnginePeer.init), displayDimensions: nameAvatarFrame.size)
}
nameAvatarNode.updateSize(size: nameAvatarFrame.size)
nameNavigateButton.update(size: nameNavigateFrame.size, color: authorNameColor ?? item.presentationData.theme.theme.chat.message.incoming.accentTextColor)
if animateNameAvatar {
animation.animator.updateFrame(layer: nameAvatarNode.layer, frame: nameAvatarFrame, completion: nil)
animation.animator.updateFrame(layer: nameNavigateButton.layer, frame: nameNavigateFrame, completion: nil)
} else {
nameAvatarNode.frame = CGRect(origin: CGPoint(x: previousNameNodeFrame.minX - 26.0 * 0.5, y: previousNameNodeFrame.minY - 4.0), size: CGSize(width: 26.0, height: 26.0))
animation.animator.updateFrame(layer: nameAvatarNode.layer, frame: nameAvatarFrame, completion: nil)
if animation.isAnimated {
animation.transition.animateTransformScale(view: nameAvatarNode.view, from: 0.001)
nameAvatarNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1)
}
nameNavigateButton.frame = CGRect(origin: CGPoint(x: previousNameNodeFrame.maxX + nameNavigateButtonOffset - 26.0 * 0.5, y: previousNameNodeFrame.minY - 4.0), size: CGSize(width: 26.0, height: 26.0))
animation.animator.updateFrame(layer: nameNavigateButton.layer, frame: nameNavigateFrame, completion: nil)
if animation.isAnimated {
animation.transition.animateTransformScale(view: nameNavigateButton, from: 0.001)
nameNavigateButton.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1)
}
}
nameNodeFrame.origin.x += 26.0 + 5.0
} else {
if let nameAvatarNode = strongSelf.nameAvatarNode {
strongSelf.nameAvatarNode = nil
nameAvatarNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, removeOnCompletion: false, completion: { [weak nameAvatarNode] _ in
nameAvatarNode?.removeFromSupernode()
})
animation.animator.updateFrame(layer: nameAvatarNode.layer, frame: CGRect(origin: CGPoint(x: nameNodeFrame.minX - 26.0 * 0.5, y: nameNodeFrame.minY - 4.0), size: CGSize(width: 26.0, height: 26.0)), completion: nil)
animation.transition.updateTransformScale(node: nameAvatarNode, scale: CGPoint(x: 0.001, y: 0.001))
}
if let nameNavigateButton = strongSelf.nameNavigateButton {
strongSelf.nameNavigateButton = nil
nameNavigateButton.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, removeOnCompletion: false, completion: { [weak nameNavigateButton] _ in
nameNavigateButton?.removeFromSuperview()
})
animation.animator.updateFrame(layer: nameNavigateButton.layer, frame: CGRect(origin: CGPoint(x: nameNodeFrame.maxX + nameNavigateButtonOffset - 26.0 * 0.5, y: nameNodeFrame.minY - 4.0), size: CGSize(width: 26.0, height: 26.0)), completion: nil)
animation.transition.updateTransformScale(layer: nameNavigateButton.layer, scale: CGPoint(x: 0.001, y: 0.001))
}
}
if nameNode.supernode == nil {
if !nameNode.isNodeLoaded {
nameNode.isUserInteractionEnabled = false
@ -3516,9 +3646,11 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
if let (currentCredibilityIcon, currentParticleColor) = currentCredibilityIcon {
let credibilityIconView: ComponentHostView<Empty>
var animateCredibilityIconFrame = true
if let current = strongSelf.credibilityIconView {
credibilityIconView = current
} else {
animateCredibilityIconFrame = false
credibilityIconView = ComponentHostView<Empty>()
credibilityIconView.isUserInteractionEnabled = false
strongSelf.credibilityIconView = credibilityIconView
@ -3549,7 +3681,10 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
)
let credibilityIconFrame = CGRect(origin: CGPoint(x: nameNode.frame.maxX + 3.0, y: nameNode.frame.minY + floor((nameNode.bounds.height - credibilityIconSize.height) / 2.0)), size: credibilityIconSize)
credibilityIconView.frame = credibilityIconFrame
if !animateCredibilityIconFrame {
credibilityIconView.frame = CGRect(origin: CGPoint(x: previousNameNodeFrame.maxX + 3.0, y: previousNameNodeFrame.minY + floor((previousNameNodeFrame.height - credibilityIconSize.height) / 2.0)), size: credibilityIconSize)
}
animation.animator.updateFrame(layer: credibilityIconView.layer, frame: credibilityIconFrame, completion: nil)
let credibilityButtonNode: HighlightTrackingButtonNode
let credibilityHighlightNode: ASImageNode
@ -3780,6 +3915,12 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
}
} else {
if animation.isAnimated {
if let nameAvatarNode = strongSelf.nameAvatarNode {
strongSelf.nameAvatarNode = nil
nameAvatarNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, removeOnCompletion: false, completion: { [weak nameAvatarNode] _ in
nameAvatarNode?.removeFromSupernode()
})
}
if let nameNode = strongSelf.nameNode {
strongSelf.nameNode = nil
nameNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, removeOnCompletion: false, completion: { [weak nameNode] _ in
@ -3811,6 +3952,10 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
})
}
} else {
strongSelf.nameAvatarNode?.removeFromSupernode()
strongSelf.nameAvatarNode = nil
strongSelf.nameNavigateButton?.removeFromSuperview()
strongSelf.nameNavigateButton = nil
strongSelf.nameNode?.removeFromSupernode()
strongSelf.nameNode = nil
strongSelf.adminBadgeNode?.removeFromSupernode()
@ -4582,9 +4727,15 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
buttonFrame.origin.y = buttonFrame.origin.y - (buttonSize.height - 30.0)
}
animation.animator.updateFrame(layer: shareButtonNode.layer, frame: buttonFrame, completion: nil)
animation.animator.updateAlpha(layer: shareButtonNode.layer, alpha: isCurrentlyPlayingMedia ? 0.0 : 1.0, completion: nil)
if isSidePanelOpen {
buttonFrame.origin.x -= buttonFrame.width * 0.5
buttonFrame.origin.y += buttonFrame.height * 0.5
}
animation.animator.updatePosition(layer: shareButtonNode.layer, position: buttonFrame.center, completion: nil)
animation.animator.updateBounds(layer: shareButtonNode.layer, bounds: CGRect(origin: CGPoint(), size: buttonFrame.size), completion: nil)
animation.animator.updateAlpha(layer: shareButtonNode.layer, alpha: (isCurrentlyPlayingMedia || isSidePanelOpen) ? 0.0 : 1.0, completion: nil)
animation.animator.updateScale(layer: shareButtonNode.layer, scale: (isCurrentlyPlayingMedia || isSidePanelOpen) ? 0.001 : 1.0, completion: nil)
}
} else {
/*if let _ = strongSelf.backgroundFrameTransition {
@ -4609,8 +4760,16 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
} else if !disablesComments {
buttonFrame.origin.y = buttonFrame.origin.y - (buttonSize.height - 30.0)
}
shareButtonNode.frame = buttonFrame
shareButtonNode.alpha = isCurrentlyPlayingMedia ? 0.0 : 1.0
if isSidePanelOpen {
buttonFrame.origin.x -= buttonFrame.width * 0.5
buttonFrame.origin.y += buttonFrame.height * 0.5
}
animation.animator.updatePosition(layer: shareButtonNode.layer, position: buttonFrame.center, completion: nil)
animation.animator.updateBounds(layer: shareButtonNode.layer, bounds: CGRect(origin: CGPoint(), size: buttonFrame.size), completion: nil)
animation.animator.updateAlpha(layer: shareButtonNode.layer, alpha: (isCurrentlyPlayingMedia || isSidePanelOpen) ? 0.0 : 1.0, completion: nil)
animation.animator.updateScale(layer: shareButtonNode.layer, scale: (isCurrentlyPlayingMedia || isSidePanelOpen) ? 0.001 : 1.0, completion: nil)
}
if case .System = animation, strongSelf.mainContextSourceNode.isExtractedToContextPreview {
@ -5826,6 +5985,8 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
break
}
}
} else if let channel = item.message.peers[item.message.id.peerId], channel.isMonoForum, case .peer = item.chatLocation {
item.controllerInteraction.updateChatLocationThread(item.message.threadId)
} else {
if !self.disablesComments {
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .broadcast = channel.info {
@ -6482,3 +6643,54 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
return nil
}
}
private func generateNameNavigateButtonImage() -> UIImage {
return generateImage(CGSize(width: 26.0, height: 26.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(UIColor(white: 1.0, alpha: 0.1).cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
let arrowRect = CGSize(width: 4.0, height: 8.0).centered(in: CGRect(origin: CGPoint(), size: size)).offsetBy(dx: 1.0, dy: 0.0)
context.setStrokeColor(UIColor.white.cgColor)
context.setLineWidth(1.0)
context.setLineCap(.round)
context.setLineJoin(.round)
context.beginPath()
context.move(to: arrowRect.origin)
context.addLine(to: CGPoint(x: arrowRect.maxX, y: arrowRect.midY))
context.addLine(to: CGPoint(x: arrowRect.minX, y: arrowRect.maxY))
context.strokePath()
})!.withRenderingMode(.alwaysTemplate)
}
public final class NameNavigateButton: HighlightableButton {
private static let sharedImage: UIImage = generateNameNavigateButtonImage()
private let backgroundView: UIImageView
public var action: (() -> Void)?
override public init(frame: CGRect) {
self.backgroundView = UIImageView(image: NameNavigateButton.sharedImage)
super.init(frame: frame)
self.addSubview(self.backgroundView)
self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
}
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc private func pressed() {
self.action?()
}
public func update(size: CGSize, color: UIColor) {
self.backgroundView.frame = CGRect(origin: CGPoint(), size: size)
self.backgroundView.tintColor = color
}
}

View file

@ -49,12 +49,18 @@ public final class ChatMessageSuggestedPostInfoNode: ASDisplayNode {
var amount: Int64 = 0
var timestamp: Int32?
for attribute in item.message.attributes {
if "".isEmpty {
amount = 0
timestamp = nil
}
/*for attribute in item.message.attributes {
if let attribute = attribute as? OutgoingSuggestedPostMessageAttribute {
amount = attribute.price.value
timestamp = attribute.timestamp
}
}
}*/
//TODO:localize
let amountString: String

View file

@ -95,17 +95,6 @@ public func stringForMessageTimestampStatus(accountPeerId: PeerId, message: Mess
dateText = " "
}
for attribute in message.attributes {
if let attribute = attribute as? OutgoingSuggestedPostMessageAttribute {
if let timestamp = attribute.timestamp {
dateText = stringForMessageTimestamp(timestamp: timestamp, dateTimeFormat: dateTimeFormat)
} else {
//TODO:localize
dateText = "Anytime"
}
}
}
if message.id.namespace == Namespaces.Message.ScheduledCloud, let _ = message.pendingProcessingAttribute {
return "appx. \(dateText)"
}

View file

@ -106,6 +106,7 @@ public enum ChatMessageMerge: Int32 {
public protocol ChatMessageAvatarHeaderNode: ListViewItemHeaderNode {
func updateSelectionState(animated: Bool)
func updateAvatarIsHidden(isHidden: Bool, transition: ContainedViewLayoutTransition)
}
public protocol ChatMessageItem: ListViewItem {

View file

@ -274,7 +274,7 @@ public final class ChatMessageDateHeaderNode: ListViewItemHeaderNode {
}
}
override public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) {
override public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) {
let chatDateSize: CGFloat = 20.0
let chatDateInset: CGFloat = 6.0
@ -282,22 +282,26 @@ public final class ChatMessageDateHeaderNode: ListViewItemHeaderNode {
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)
self.stickBackgroundNode.frame = CGRect(origin: CGPoint(), size: backgroundFrame.size)
self.backgroundNode.frame = backgroundFrame
self.backgroundNode.update(size: backgroundFrame.size, cornerRadius: backgroundFrame.size.height / 2.0, transition: .immediate)
self.labelNode.frame = CGRect(origin: CGPoint(x: backgroundFrame.origin.x + chatDateInset, y: backgroundFrame.origin.y + floorToScreenPixels((backgroundSize.height - labelSize.height) / 2.0)), size: labelSize)
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 + chatDateInset, 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
backgroundContent.frame = self.backgroundNode.frame
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: .immediate)
backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: transition)
}
}
}
@ -472,6 +476,8 @@ public final class ChatMessageAvatarHeaderNodeImpl: ListViewItemHeaderNode, Chat
private var backgroundContent: WallpaperBubbleBackgroundNode?
private var isAvatarHidden: Bool = false
private var trackingIsInHierarchy: Bool = false {
didSet {
if self.trackingIsInHierarchy != oldValue {
@ -713,15 +719,20 @@ public final class ChatMessageAvatarHeaderNodeImpl: ListViewItemHeaderNode, Chat
}
}
override public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) {
self.containerNode.frame = CGRect(origin: CGPoint(x: leftInset + 3.0, y: 0.0), size: CGSize(width: 38.0, height: 38.0))
self.avatarNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 38.0, height: 38.0))
override public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) {
transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(x: leftInset + 3.0, y: 0.0), size: CGSize(width: 38.0, height: 38.0)))
let avatarFrame = CGRect(origin: CGPoint(), size: CGSize(width: 38.0, height: 38.0))
self.avatarNode.position = avatarFrame.center
self.avatarNode.bounds = CGRect(origin: CGPoint(), size: avatarFrame.size)
self.avatarNode.updateSize(size: avatarFrame.size)
}
override public func animateRemoved(duration: Double) {
self.alpha = 0.0
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration, removeOnCompletion: false)
self.avatarNode.layer.animateScale(from: 1.0, to: 0.2, duration: duration, removeOnCompletion: false)
if !self.isAvatarHidden {
self.avatarNode.layer.animateScale(from: 1.0, to: 0.2, duration: duration, removeOnCompletion: false)
}
}
override public func animateAdded(duration: Double) {
@ -748,6 +759,18 @@ public final class ChatMessageAvatarHeaderNodeImpl: ListViewItemHeaderNode, Chat
self.layer.animate(from: NSValue(caTransform3D: previousSubnodeTransform), to: NSValue(caTransform3D: self.subnodeTransform), keyPath: "sublayerTransform", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 0.2)
}
}
public func updateAvatarIsHidden(isHidden: Bool, transition: ContainedViewLayoutTransition) {
self.isAvatarHidden = isHidden
var avatarTransform: CATransform3D = CATransform3DIdentity
if isHidden {
let scale: CGFloat = isHidden ? 0.001 : 1.0
avatarTransform = CATransform3DTranslate(avatarTransform, -38.0 * 0.5, 38.0 * 0.5, 0.0)
avatarTransform = CATransform3DScale(avatarTransform, scale, scale, 1.0)
}
transition.updateTransform(node: self.avatarNode, transform: avatarTransform)
transition.updateAlpha(node: self.avatarNode, alpha: isHidden ? 0.0 : 1.0)
}
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if !self.bounds.contains(point) {
@ -786,7 +809,7 @@ public final class ChatMessageAvatarHeaderNodeImpl: ListViewItemHeaderNode, Chat
self.avatarVideoNode?.updateVisibility(isVisible)
if let videoNode = self.avatarVideoNode {
videoNode.updateLayout(size: self.avatarNode.frame.size, cornerRadius: self.avatarNode.frame.size.width / 2.0, transition: .immediate)
videoNode.updateLayout(size: self.avatarNode.bounds.size, cornerRadius: self.avatarNode.bounds.size.width / 2.0, transition: .immediate)
videoNode.frame = self.avatarNode.bounds
}
}

View file

@ -283,7 +283,7 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible
let incoming = content.effectivelyIncoming(self.context.account.peerId)
var effectiveAuthor: Peer?
let displayAuthorInfo: Bool
var displayAuthorInfo: Bool
let messagePeerId: PeerId = chatLocation.peerId ?? content.firstMessage.id.peerId
@ -316,6 +316,12 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible
}
}
displayAuthorInfo = incoming && peerId.isGroupOrChannel && effectiveAuthor != nil
if let channel = content.firstMessage.peers[content.firstMessage.id.peerId] as? TelegramChannel, channel.isMonoForum {
if case .replyThread = chatLocation {
displayAuthorInfo = false
}
}
}
}
@ -367,10 +373,6 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible
}
}
if let subject = associatedData.subject, case let .customChatContents(contents) = subject, case .postSuggestions = contents.kind {
hasAvatar = false
}
if hasAvatar {
if let effectiveAuthor = effectiveAuthor {
var storyStats: PeerStoryStats?

View file

@ -901,6 +901,7 @@ open class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol {
}
private var attachedAvatarNodeOffset: CGFloat = 0.0
private var attachedAvatarNodeIsHidden: Bool = false
override open func attachedHeaderNodesUpdated() {
if !self.attachedAvatarNodeOffset.isZero {
@ -912,6 +913,12 @@ open class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol {
}
}
}
for headerNode in self.attachedHeaderNodes {
if let headerNode = headerNode as? ChatMessageAvatarHeaderNode {
headerNode.updateAvatarIsHidden(isHidden: self.attachedAvatarNodeIsHidden, transition: .immediate)
}
}
}
open func updateAttachedAvatarNodeOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) {
@ -923,6 +930,15 @@ open class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol {
}
}
open func updateAttachedAvatarNodeIsHidden(isHidden: Bool, transition: ContainedViewLayoutTransition) {
self.attachedAvatarNodeIsHidden = isHidden
for headerNode in self.attachedHeaderNodes {
if let headerNode = headerNode as? ChatMessageAvatarHeaderNode {
headerNode.updateAvatarIsHidden(isHidden: self.attachedAvatarNodeIsHidden, transition: transition)
}
}
}
open func unreadMessageRangeUpdated() {
}

View file

@ -105,14 +105,19 @@ public class ChatMessageShareButton: ASDisplayNode {
public func update(presentationData: ChatPresentationData, controllerInteraction: ChatControllerInteraction, chatLocation: ChatLocation, subject: ChatControllerSubject?, message: Message, account: Account, disableComments: Bool = false) -> CGSize {
var isReplies = false
var isNavigate = false
var replyCount = 0
if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = channel.info {
for attribute in message.attributes {
if let attribute = attribute as? ReplyThreadMessageAttribute {
replyCount = Int(attribute.count)
isReplies = true
break
if let channel = message.peers[message.id.peerId] as? TelegramChannel {
if case .broadcast = channel.info {
for attribute in message.attributes {
if let attribute = attribute as? ReplyThreadMessageAttribute {
replyCount = Int(attribute.count)
isReplies = true
break
}
}
} else if channel.isMonoForum, case .peer = chatLocation {
isNavigate = true
}
}
if case let .replyThread(replyThreadMessage) = chatLocation, replyThreadMessage.effectiveTopId == message.id {
@ -147,6 +152,9 @@ public class ChatMessageShareButton: ASDisplayNode {
} else if case let .customChatContents(contents) = subject, case .hashTagSearch = contents.kind {
updatedIconImage = PresentationResourcesChat.chatFreeNavigateButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper)
updatedIconOffset = CGPoint(x: UIScreenPixel, y: 1.0)
} else if isNavigate {
updatedIconImage = PresentationResourcesChat.chatFreeNavigateToThreadButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper)
updatedIconOffset = CGPoint(x: UIScreenPixel, y: -3.0)
} else if case .pinnedMessages = subject {
updatedIconImage = PresentationResourcesChat.chatFreeNavigateButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper)
updatedIconOffset = CGPoint(x: UIScreenPixel, y: 1.0)

View file

@ -646,6 +646,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
}, forceUpdateWarpContents: {
}, playShakeAnimation: {
}, displayQuickShare: { _, _ ,_ in
}, updateChatLocationThread: { _ in
}, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings,
pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: context, backgroundNode: self.backgroundNode))
self.controllerInteraction = controllerInteraction

View file

@ -501,6 +501,7 @@ public final class ChatSendGroupMediaMessageContextPreview: UIView, ChatSendMess
}, forceUpdateWarpContents: {
}, playShakeAnimation: {
}, displayQuickShare: { _, _ ,_ in
}, updateChatLocationThread: { _ in
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: self.context, backgroundNode: self.wallpaperBackgroundNode))

View file

@ -355,7 +355,7 @@ public final class ChatSideTopicsPanel: Component {
let iconSize = self.icon.update(
transition: .immediate,
component: AnyComponent(BundleIconComponent(
name: "Call/PanelIcon",
name: "Chat/Title Panels/SidebarIcon",
tintColor: theme.rootController.navigationBar.accentTextColor,
maxSize: nil,
scaleFactor: 1.0
@ -364,7 +364,7 @@ public final class ChatSideTopicsPanel: Component {
containerSize: CGSize(width: 100.0, height: 100.0)
)
let topInset: CGFloat = 12.0
let topInset: CGFloat = 10.0
let bottomInset: CGFloat = 12.0
let contentSize: CGFloat = topInset + iconSize.height + bottomInset
@ -578,7 +578,6 @@ public final class ChatSideTopicsPanel: Component {
self.scrollView.alwaysBounceHorizontal = false
self.scrollView.alwaysBounceVertical = false
self.scrollView.scrollsToTop = false
//self.scrollView.delegate = self.wrappedScrollViewDelegate
self.addSubview(self.scrollView)
self.scrollView.addSubview(self.selectedLineView)
@ -591,6 +590,12 @@ public final class ChatSideTopicsPanel: Component {
deinit {
self.itemsDisposable?.dispose()
}
public func updateGlobalOffset(globalOffset: CGFloat, transition: ComponentTransition) {
if let tabItemView = self.tabItemView {
transition.setTransform(view: tabItemView, transform: CATransform3DMakeTranslation(-globalOffset, 0.0, 0.0))
}
}
func update(component: ChatSideTopicsPanel, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
self.isUpdating = true
@ -774,6 +779,7 @@ public final class ChatSideTopicsPanel: Component {
}
contentSize.height += itemSize.height
contentSize.height -= 20.0
}
do {

View file

@ -271,7 +271,6 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol
public let sendGift: (EnginePeer.Id) -> Void
public let openUniqueGift: (String) -> Void
public let openMessageFeeException: () -> Void
public let requestMessageUpdate: (MessageId, Bool) -> Void
public let cancelInteractiveKeyboardGestures: () -> Void
public let dismissTextInput: () -> Void
@ -281,6 +280,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol
public let forceUpdateWarpContents: () -> Void
public let playShakeAnimation: () -> Void
public let displayQuickShare: (MessageId, ASDisplayNode, ContextGesture) -> Void
public let updateChatLocationThread: (Int64?) -> Void
public var canPlayMedia: Bool = false
public var hiddenMedia: [MessageId: [Media]] = [:]
@ -328,6 +328,8 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol
}
}
public var isSidePanelOpen: Bool = false
public init(
openMessage: @escaping (Message, OpenMessageParams) -> Bool,
openPeer: @escaping (EnginePeer, ChatControllerInteractionNavigateToPeer, MessageReference?, OpenPeerSource) -> Void,
@ -441,6 +443,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol
forceUpdateWarpContents: @escaping () -> Void,
playShakeAnimation: @escaping () -> Void,
displayQuickShare: @escaping (MessageId, ASDisplayNode, ContextGesture) -> Void,
updateChatLocationThread: @escaping (Int64?) -> Void,
automaticMediaDownloadSettings: MediaAutoDownloadSettings,
pollActionState: ChatInterfacePollActionState,
stickerSettings: ChatInterfaceStickerSettings,
@ -559,6 +562,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol
self.forceUpdateWarpContents = forceUpdateWarpContents
self.playShakeAnimation = playShakeAnimation
self.displayQuickShare = displayQuickShare
self.updateChatLocationThread = updateChatLocationThread
self.automaticMediaDownloadSettings = automaticMediaDownloadSettings

View file

@ -1816,8 +1816,6 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
case .businessLinkSetup:
stickerContent = nil
gifContent = nil
case .postSuggestions:
break
}
}

View file

@ -326,7 +326,7 @@ public final class DrawingMessageRenderer {
}
avatarHeaderNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 3.0), size: CGSize(width: layout.size.width, height: avatarHeaderItem.height))
avatarHeaderNode.updateLayout(size: size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right)
avatarHeaderNode.updateLayout(size: size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: .immediate)
}
var finalWidth: CGFloat = width

View file

@ -3790,6 +3790,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
}, forceUpdateWarpContents: {
}, playShakeAnimation: {
}, displayQuickShare: { _, _ ,_ in
}, updateChatLocationThread: { _ in
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: context, backgroundNode: nil))
self.hiddenMediaDisposable = context.sharedContext.mediaManager.galleryHiddenMediaManager.hiddenIds().startStrict(next: { [weak self] ids in

View file

@ -1,153 +0,0 @@
import Foundation
import UIKit
import SwiftSignalKit
import Postbox
import TelegramCore
import AccountContext
public final class PostSuggestionsChatContents: ChatCustomContentsProtocol {
private final class Impl {
let queue: Queue
let context: AccountContext
private var peerId: EnginePeer.Id
private(set) var mergedHistoryView: MessageHistoryView?
private var sourceHistoryView: MessageHistoryView?
private var historyViewDisposable: Disposable?
private var pendingHistoryViewDisposable: Disposable?
let historyViewStream = ValuePipe<(MessageHistoryView, ViewUpdateType)>()
private var nextUpdateIsHoleFill: Bool = false
init(queue: Queue, context: AccountContext, peerId: EnginePeer.Id) {
self.queue = queue
self.context = context
self.peerId = peerId
self.updateHistoryViewRequest(reload: false)
}
deinit {
self.historyViewDisposable?.dispose()
self.pendingHistoryViewDisposable?.dispose()
}
private func updateHistoryViewRequest(reload: Bool) {
self.pendingHistoryViewDisposable?.dispose()
self.pendingHistoryViewDisposable = nil
if self.historyViewDisposable == nil || reload {
self.historyViewDisposable?.dispose()
self.historyViewDisposable = (self.context.account.viewTracker.postSuggestionsViewForLocation(peerId: self.peerId)
|> deliverOn(self.queue)).start(next: { [weak self] view, update, _ in
guard let self else {
return
}
if update == .FillHole {
self.nextUpdateIsHoleFill = true
self.updateHistoryViewRequest(reload: true)
return
}
let nextUpdateIsHoleFill = self.nextUpdateIsHoleFill
self.nextUpdateIsHoleFill = false
self.sourceHistoryView = view
self.updateHistoryView(updateType: nextUpdateIsHoleFill ? .FillHole : .Generic)
})
}
}
private func updateHistoryView(updateType: ViewUpdateType) {
var entries = self.sourceHistoryView?.entries ?? []
entries.sort(by: { $0.message.index < $1.message.index })
let mergedHistoryView = MessageHistoryView(tag: nil, namespaces: .just(Namespaces.Message.allSuggestedPost), entries: entries, holeEarlier: false, holeLater: false, isLoading: false)
self.mergedHistoryView = mergedHistoryView
self.historyViewStream.putNext((mergedHistoryView, updateType))
}
func enqueueMessages(messages: [EnqueueMessage]) {
let _ = (TelegramCore.enqueueMessages(account: self.context.account, peerId: self.peerId, messages: messages.compactMap { message -> EnqueueMessage? in
if !message.attributes.contains(where: { $0 is OutgoingSuggestedPostMessageAttribute }) {
return nil
}
return message
})
|> deliverOn(self.queue)).startStandalone()
}
func deleteMessages(ids: [EngineMessage.Id]) {
let _ = self.context.engine.messages.deleteMessagesInteractively(messageIds: ids, type: .forEveryone).startStandalone()
}
func editMessage(id: EngineMessage.Id, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, webpagePreviewAttribute: WebpagePreviewMessageAttribute?, disableUrlPreview: Bool) {
}
}
public let peerId: EnginePeer.Id
public var kind: ChatCustomContentsKind
public var historyView: Signal<(MessageHistoryView, ViewUpdateType), NoError> {
return self.impl.signalWith({ impl, subscriber in
if let mergedHistoryView = impl.mergedHistoryView {
subscriber.putNext((mergedHistoryView, .Initial))
}
return impl.historyViewStream.signal().start(next: subscriber.putNext)
})
}
public var messageLimit: Int? {
return 20
}
private let queue: Queue
private let impl: QueueLocalObject<Impl>
public init(context: AccountContext, peerId: EnginePeer.Id) {
self.peerId = peerId
self.kind = .postSuggestions(price: StarsAmount(value: 250, nanos: 0))
let queue = Queue()
self.queue = queue
self.impl = QueueLocalObject(queue: queue, generate: {
return Impl(queue: queue, context: context, peerId: peerId)
})
}
public func enqueueMessages(messages: [EnqueueMessage]) {
self.impl.with { impl in
impl.enqueueMessages(messages: messages)
}
}
public func deleteMessages(ids: [EngineMessage.Id]) {
self.impl.with { impl in
impl.deleteMessages(ids: ids)
}
}
public func editMessage(id: EngineMessage.Id, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, webpagePreviewAttribute: WebpagePreviewMessageAttribute?, disableUrlPreview: Bool) {
self.impl.with { impl in
impl.editMessage(id: id, text: text, media: media, entities: entities, webpagePreviewAttribute: webpagePreviewAttribute, disableUrlPreview: disableUrlPreview)
}
}
public func quickReplyUpdateShortcut(value: String) {
}
public func businessLinkUpdate(message: String, entities: [MessageTextEntity], title: String?) {
}
public func loadMore() {
}
public func hashtagSearchUpdate(query: String) {
}
public var hashtagSearchResultsUpdate: ((SearchMessagesResult, SearchMessagesState)) -> Void = { _ in }
}

View file

@ -211,8 +211,6 @@ final class AutomaticBusinessMessageSetupChatContents: ChatCustomContentsProtoco
initialShortcut = ""
case .hashTagSearch:
initialShortcut = ""
case .postSuggestions:
initialShortcut = ""
}
let queue = Queue()
@ -251,8 +249,6 @@ final class AutomaticBusinessMessageSetupChatContents: ChatCustomContentsProtoco
break
case .hashTagSearch:
break
case .postSuggestions:
break
}
}

View file

@ -337,7 +337,7 @@ final class PeerNameColorChatPreviewItemNode: ListViewItemNode {
header.updateNode(headerNode, previous: nil, next: nil)
headerNode.item = header
}
headerNode.updateLayoutInternal(size: headerFrame.size, leftInset: leftInset, rightInset: rightInset)
headerNode.updateLayoutInternal(size: headerFrame.size, leftInset: leftInset, rightInset: rightInset, transition: .immediate)
headerNode.updateStickDistanceFactor(stickLocationDistanceFactor, transition: .immediate)
} else {
headerNode = header.node(synchronousLoad: true)
@ -346,7 +346,7 @@ final class PeerNameColorChatPreviewItemNode: ListViewItemNode {
headerNode.item = header
}
headerNode.frame = headerFrame
headerNode.updateLayoutInternal(size: headerFrame.size, leftInset: leftInset, rightInset: rightInset)
headerNode.updateLayoutInternal(size: headerFrame.size, leftInset: leftInset, rightInset: rightInset, transition: .immediate)
strongSelf.itemHeaderNodes[id] = headerNode
strongSelf.containerNode.addSubnode(headerNode)

View file

@ -1180,7 +1180,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, ASScrollViewDelegate
}
transition.updateFrame(node: dateHeaderNode, frame: CGRect(origin: CGPoint(x: 0.0, y: bottomOffset), size: CGSize(width: layout.size.width, height: headerItem.height)))
dateHeaderNode.updateLayout(size: self.messagesContainerNode.frame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right)
dateHeaderNode.updateLayout(size: self.messagesContainerNode.frame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: .immediate)
}
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "list_24.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -1656,7 +1656,7 @@ extension ChatControllerImpl {
self.reportIrrelvantGeoNoticePromise.set(.single(nil))
self.titleDisposable.set(nil)
var peerView: Signal<PeerView?, NoError> = .single(nil)
let peerView: Signal<PeerView?, NoError> = .single(nil)
if case let .customChatContents(customChatContents) = self.subject {
switch customChatContents.kind {
@ -1680,13 +1680,6 @@ extension ChatControllerImpl {
}
self.chatTitleView?.titleContent = .custom(link.title ?? self.presentationData.strings.Business_Links_EditLinkTitle, linkUrl, false)
case .postSuggestions:
if let customChatContents = customChatContents as? PostSuggestionsChatContents {
peerView = context.account.viewTracker.peerView(customChatContents.peerId) |> map(Optional.init)
}
//TODO:localize
self.chatTitleView?.titleContent = .custom("Message Suggestions", nil, false)
}
} else {
self.chatTitleView?.titleContent = .custom(" ", nil, false)
@ -3038,46 +3031,6 @@ extension ChatControllerImpl {
}
strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .succeed(text: strongSelf.presentationData.strings.Business_Links_EditLinkToastSaved, timeout: nil, customUndoText: nil), elevatedLayout: false, action: { _ in return false }), in: .current)
case let .postSuggestions(postSuggestions):
if let customChatContents = customChatContents as? PostSuggestionsChatContents {
//TODO:release
strongSelf.chatDisplayNode.dismissInput()
let _ = (ChatSendStarsScreen.initialData(context: strongSelf.context, peerId: customChatContents.peerId, suggestMessageAmount: postSuggestions, completion: { [weak strongSelf] amount, timestamp in
guard let strongSelf else {
return
}
guard case let .customChatContents(customChatContents) = strongSelf.subject else {
return
}
if amount == 0 {
return
}
let messages = messages.map { message in
return message.withUpdatedAttributes { attributes in
var attributes = attributes
attributes.removeAll(where: { $0 is OutgoingSuggestedPostMessageAttribute })
attributes.append(OutgoingSuggestedPostMessageAttribute(
price: StarsAmount(value: amount, nanos: 0),
timestamp: timestamp
))
return attributes
}
}
customChatContents.enqueueMessages(messages: messages)
strongSelf.chatDisplayNode.historyNode.scrollToEndOfHistory()
})
|> deliverOnMainQueue).startStandalone(next: { [weak strongSelf] initialData in
guard let strongSelf, let initialData else {
return
}
let sendStarsScreen = ChatSendStarsScreen(
context: strongSelf.context,
initialData: initialData
)
strongSelf.push(sendStarsScreen)
})
}
}
}
strongSelf.updateChatPresentationInterfaceState(interactive: true, { $0.updatedShowCommands(false) })
@ -5772,22 +5725,6 @@ extension ChatControllerImpl {
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(monoforumPeer), keepStack: .always))
})
})
/*let contents = PostSuggestionsChatContents(
context: self.context,
peerId: peerId
)
let chatController = self.context.sharedContext.makeChatController(
context: self.context,
chatLocation: .customChatContents,
subject: .customChatContents(contents: contents),
botStart: nil,
mode: .standard(.default),
params: nil
)
chatController.navigationPresentation = .modal
self.push(chatController)*/
}, editMessageMedia: { [weak self] messageId, draw in
if let strongSelf = self {
strongSelf.controllerInteraction?.editMessageMedia(messageId, draw)

View file

@ -230,8 +230,6 @@ func updateChatPresentationInterfaceStateImpl(
break
case .businessLinkSetup:
canHaveUrlPreview = false
case .postSuggestions:
break
}
}

View file

@ -196,8 +196,6 @@ final class ChatBusinessLinkTitlePanelNode: ChatTitleAccessoryPanelNode {
self.link = link
case .hashTagSearch:
break
case .postSuggestions:
break
}
default:
break

View file

@ -777,8 +777,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
case .hashTagSearch:
break
case .postSuggestions:
break
}
}
@ -885,8 +883,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return false
}
case .postSuggestions:
break
}
}
@ -4833,6 +4829,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return
}
self.displayQuickShare(id: messageId, node: node, gesture: gesture)
}, updateChatLocationThread: { [weak self] threadId in
guard let self else {
return
}
self.interfaceInteraction?.updateChatLocationThread(threadId)
}, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: self.stickerSettings, presentationContext: ChatPresentationContext(context: context, backgroundNode: self.chatBackgroundNode))
controllerInteraction.enableFullTranslucency = context.sharedContext.energyUsageSettings.fullTranslucency
@ -5227,14 +5228,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
chatInfoButtonItem = UIBarButtonItem(customDisplayNode: avatarNode)!
self.avatarNode = avatarNode
case .customChatContents:
if case let .customChatContents(customChatContents) = self.subject, case .postSuggestions = customChatContents.kind {
let avatarNode = ChatAvatarNavigationNode()
chatInfoButtonItem = UIBarButtonItem(customDisplayNode: avatarNode)!
chatInfoButtonItem.isEnabled = false
self.avatarNode = avatarNode
} else {
chatInfoButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
}
chatInfoButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
}
chatInfoButtonItem.target = self
chatInfoButtonItem.action = #selector(self.rightNavigationButtonAction)

View file

@ -209,6 +209,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
private(set) var feePanelNode: ChatFeePanelNode?
private let titleAccessoryPanelContainer: ChatControllerTitlePanelNodeContainer
private var titleTopicsAccessoryPanelNode: ChatTopicListTitleAccessoryPanelNode?
private var titleAccessoryPanelNode: ChatTitleAccessoryPanelNode?
private var chatTranslationPanel: ChatTranslationPanelNode?
@ -1363,13 +1364,39 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
previousInputPanelOrigin.y -= secondaryInputPanelNode.bounds.size.height
}
self.containerLayoutAndNavigationBarHeight = (layout, navigationBarHeight)
var extraTransition = transition
var dismissedTitleTopicsAccessoryPanelNode: ChatTopicListTitleAccessoryPanelNode?
var immediatelyLayoutTitleTopicsAccessoryPanelNodeAndAnimateAppearance = false
var titleTopicsAccessoryPanelHeight: CGFloat?
var titleTopicsAccessoryPanelBackgroundHeight: CGFloat?
var titleTopicsAccessoryPanelHitTestSlop: CGFloat?
if let titleTopicsAccessoryPanelNode = titleTopicsPanelForChatPresentationInterfaceState(self.chatPresentationInterfaceState, context: self.context, currentPanel: self.titleTopicsAccessoryPanelNode, controllerInteraction: self.controllerInteraction, interfaceInteraction: self.interfaceInteraction, force: false) {
if self.titleTopicsAccessoryPanelNode != titleTopicsAccessoryPanelNode {
dismissedTitleTopicsAccessoryPanelNode = self.titleTopicsAccessoryPanelNode
self.titleTopicsAccessoryPanelNode = titleTopicsAccessoryPanelNode
immediatelyLayoutTitleTopicsAccessoryPanelNodeAndAnimateAppearance = true
self.titleAccessoryPanelContainer.addSubnode(titleTopicsAccessoryPanelNode)
titleTopicsAccessoryPanelNode.clipsToBounds = true
}
let layoutResult = titleTopicsAccessoryPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: immediatelyLayoutTitleTopicsAccessoryPanelNodeAndAnimateAppearance ? .immediate : transition, interfaceState: self.chatPresentationInterfaceState)
titleTopicsAccessoryPanelHeight = layoutResult.insetHeight
titleTopicsAccessoryPanelBackgroundHeight = layoutResult.backgroundHeight
titleTopicsAccessoryPanelHitTestSlop = layoutResult.hitTestSlop
} else if let titleTopicsAccessoryPanelNode = self.titleTopicsAccessoryPanelNode {
dismissedTitleTopicsAccessoryPanelNode = titleTopicsAccessoryPanelNode
self.titleTopicsAccessoryPanelNode = nil
}
var dismissedTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode?
var immediatelyLayoutTitleAccessoryPanelNodeAndAnimateAppearance = false
var titleAccessoryPanelHeight: CGFloat?
var titleAccessoryPanelBackgroundHeight: CGFloat?
var titleAccessoryPanelHitTestSlop: CGFloat?
var extraTransition = transition
if let titleAccessoryPanelNode = titlePanelForChatPresentationInterfaceState(self.chatPresentationInterfaceState, context: self.context, currentPanel: self.titleAccessoryPanelNode, controllerInteraction: self.controllerInteraction, interfaceInteraction: self.interfaceInteraction, force: false) {
if self.titleAccessoryPanelNode != titleAccessoryPanelNode {
dismissedTitleAccessoryPanelNode = self.titleAccessoryPanelNode
@ -1575,6 +1602,8 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
self.leftPanel = nil
}
self.controllerInteraction.isSidePanelOpen = self.leftPanel != nil
var inputPanelNodeBaseHeight: CGFloat = 0.0
if let inputPanelNode = self.inputPanelNode {
inputPanelNodeBaseHeight += inputPanelNode.minimalHeight(interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics)
@ -1859,12 +1888,27 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
var extraNavigationBarHeight: CGFloat = 0.0
var extraNavigationBarHitTestSlop: CGFloat = 0.0
var titlePanelsContentOffset: CGFloat = 0.0
let sidePanelTopInset: CGFloat = insets.top
var titleTopicsAccessoryPanelFrame: CGRect?
if let _ = self.titleTopicsAccessoryPanelNode, let panelHeight = titleTopicsAccessoryPanelHeight {
titleTopicsAccessoryPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: titlePanelsContentOffset), size: CGSize(width: layout.size.width, height: panelHeight))
insets.top += panelHeight
extraNavigationBarHeight += titleTopicsAccessoryPanelBackgroundHeight ?? 0.0
extraNavigationBarHitTestSlop = titleTopicsAccessoryPanelHitTestSlop ?? 0.0
titlePanelsContentOffset += panelHeight
}
var titleAccessoryPanelFrame: CGRect?
if let _ = self.titleAccessoryPanelNode, let panelHeight = titleAccessoryPanelHeight {
titleAccessoryPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: panelHeight))
titleAccessoryPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: titlePanelsContentOffset), size: CGSize(width: layout.size.width, height: panelHeight))
insets.top += panelHeight
extraNavigationBarHeight += titleAccessoryPanelBackgroundHeight ?? 0.0
extraNavigationBarHitTestSlop = titleAccessoryPanelHitTestSlop ?? 0.0
titlePanelsContentOffset += panelHeight
}
var translationPanelFrame: CGRect?
@ -2226,18 +2270,19 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
self.controller?.presentationContext.containerLayoutUpdated(childrenLayout, transition: transition)
self.controller?.galleryPresentationContext.containerLayoutUpdated(layout, transition: transition)
listViewTransaction(ListViewUpdateSizeAndInsets(size: contentBounds.size, insets: listInsets, scrollIndicatorInsets: listScrollIndicatorInsets, duration: duration, curve: curve, ensureTopInsetForOverlayHighlightedItems: ensureTopInsetForOverlayHighlightedItems), additionalScrollDistance, scrollToTop, { [weak self] in
var customListAnimationTransition: ControlledTransition?
if case let .animated(duration, curve) = transition {
if immediatelyLayoutLeftPanelNodeAndAnimateAppearance || dismissedLeftPanel != nil {
customListAnimationTransition = ControlledTransition(duration: duration, curve: curve, interactive: false)
}
}
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)
}
})
if immediatelyLayoutLeftPanelNodeAndAnimateAppearance {
transition.animatePositionAdditive(layer: self.historyNode.layer, offset: CGPoint(x: -defaultLeftPanelWidth, y: 0.0))
} else if dismissedLeftPanel != nil {
transition.animatePositionAdditive(layer: self.historyNode.layer, offset: CGPoint(x: defaultLeftPanelWidth, y: 0.0))
}
if self.isScrollingLockedAtTop {
switch self.historyNode.visibleContentOffset() {
case let .known(value) where value <= CGFloat.ulpOfOne:
@ -2298,7 +2343,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
component: leftPanel.component.component,
environment: {
ChatSidePanelEnvironment(insets: UIEdgeInsets(
top: containerInsets.top,
top: sidePanelTopInset,
left: leftPanelLeftInset,
bottom: containerInsets.bottom + contentBottomInset,
right: 0.0
@ -2313,8 +2358,14 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
}
if immediatelyLayoutLeftPanelNodeAndAnimateAppearance {
leftPanelView.frame = leftPanelFrame.offsetBy(dx: -leftPanelSize.width, dy: 0.0)
if let leftPanelView = leftPanelView as? ChatSideTopicsPanel.View {
leftPanelView.updateGlobalOffset(globalOffset: -leftPanelSize.width, transition: ComponentTransition(transition))
}
}
transition.updateFrame(view: leftPanelView, frame: leftPanelFrame)
if let leftPanelView = leftPanelView as? ChatSideTopicsPanel.View {
leftPanelView.updateGlobalOffset(globalOffset: 0.0, transition: ComponentTransition(transition))
}
}
}
if let dismissedLeftPanel, let dismissedLeftPanelView = dismissedLeftPanel.view.view {
@ -2323,7 +2374,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
component: dismissedLeftPanel.component.component,
environment: {
ChatSidePanelEnvironment(insets: UIEdgeInsets(
top: containerInsets.top,
top: sidePanelTopInset,
left: leftPanelLeftInset,
bottom: containerInsets.bottom + contentBottomInset,
right: 0.0
@ -2334,6 +2385,9 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
transition.updateFrame(view: dismissedLeftPanelView, frame: CGRect(origin: CGPoint(x: -dismissedLeftPanelSize.width, y: 0.0), size: dismissedLeftPanelSize), completion: { [weak dismissedLeftPanelView] _ in
dismissedLeftPanelView?.removeFromSuperview()
})
if let dismissedLeftPanelView = dismissedLeftPanelView as? ChatSideTopicsPanel.View {
dismissedLeftPanelView.updateGlobalOffset(globalOffset: -dismissedLeftPanelSize.width, transition: ComponentTransition(transition))
}
}
if let navigationBarBackgroundContent = self.navigationBarBackgroundContent {
@ -2382,6 +2436,23 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
transition.updateFrame(node: self.inputPanelBackgroundSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: apparentInputBackgroundFrame.origin.y), size: CGSize(width: apparentInputBackgroundFrame.size.width, height: UIScreenPixel)))
transition.updateFrame(node: self.navigateButtons, frame: apparentNavigateButtonsFrame)
self.navigateButtons.update(rect: apparentNavigateButtonsFrame, within: layout.size, transition: transition)
if let titleTopicsAccessoryPanelNode = self.titleTopicsAccessoryPanelNode, let titleTopicsAccessoryPanelFrame, (immediatelyLayoutTitleTopicsAccessoryPanelNodeAndAnimateAppearance || !titleTopicsAccessoryPanelNode.frame.equalTo(titleTopicsAccessoryPanelFrame)) {
if immediatelyLayoutTitleTopicsAccessoryPanelNodeAndAnimateAppearance {
titleTopicsAccessoryPanelNode.frame = titleTopicsAccessoryPanelFrame.offsetBy(dx: 0.0, dy: -titleTopicsAccessoryPanelFrame.height)
titleTopicsAccessoryPanelNode.updateGlobalOffset(globalOffset: -titleTopicsAccessoryPanelFrame.height, transition: .immediate)
transition.updateFrame(node: titleTopicsAccessoryPanelNode, frame: titleTopicsAccessoryPanelFrame)
titleTopicsAccessoryPanelNode.updateGlobalOffset(globalOffset: 0.0, transition: ComponentTransition(transition))
} else {
let previousFrame = titleTopicsAccessoryPanelNode.frame
titleTopicsAccessoryPanelNode.frame = titleTopicsAccessoryPanelFrame
if transition.isAnimated && previousFrame.width != titleTopicsAccessoryPanelFrame.width {
} else {
transition.animatePositionAdditive(node: titleTopicsAccessoryPanelNode, offset: CGPoint(x: 0.0, y: -titleTopicsAccessoryPanelFrame.height))
}
}
}
if let titleAccessoryPanelNode = self.titleAccessoryPanelNode, let titleAccessoryPanelFrame, !titleAccessoryPanelNode.frame.equalTo(titleAccessoryPanelFrame) {
let previousFrame = titleAccessoryPanelNode.frame
@ -2504,6 +2575,15 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
}
}
}
if let dismissedTitleTopicsAccessoryPanelNode {
var dismissedTopPanelFrame = dismissedTitleTopicsAccessoryPanelNode.frame
dismissedTopPanelFrame.origin.y = -dismissedTopPanelFrame.size.height
transition.updateFrame(node: dismissedTitleTopicsAccessoryPanelNode, frame: dismissedTopPanelFrame, completion: { [weak dismissedTitleTopicsAccessoryPanelNode] _ in
dismissedTitleTopicsAccessoryPanelNode?.removeFromSupernode()
})
dismissedTitleTopicsAccessoryPanelNode.updateGlobalOffset(globalOffset: -dismissedTopPanelFrame.height, transition: ComponentTransition(transition))
}
if let dismissedTitleAccessoryPanelNode {
var dismissedPanelFrame = dismissedTitleAccessoryPanelNode.frame
@ -4375,7 +4455,6 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
}
var postEmptyMessages = false
var isPostSuggestions = false
if case let .customChatContents(customChatContents) = self.chatPresentationInterfaceState.subject {
switch customChatContents.kind {
case .hashTagSearch:
@ -4384,11 +4463,8 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
break
case .businessLinkSetup:
postEmptyMessages = true
case .postSuggestions:
isPostSuggestions = true
}
}
let _ = isPostSuggestions
if !messages.isEmpty, let messageEffect {
messages[0] = messages[0].withUpdatedAttributes { attributes in

View file

@ -985,7 +985,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
self.preloadPages = false
self.beginChatHistoryTransitions(resetScrolling: false)
self.beginChatHistoryTransitions(resetScrolling: false, switchedToAnotherSource: false)
self.beginReadHistoryManagement()
@ -1230,7 +1230,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
}
self.tag = tag
self.beginChatHistoryTransitions(resetScrolling: true)
self.beginChatHistoryTransitions(resetScrolling: true, switchedToAnotherSource: false)
}
public func updateChatLocation(chatLocation: ChatLocation) {
@ -1238,7 +1238,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
return
}
self.chatLocation = chatLocation
self.beginChatHistoryTransitions(resetScrolling: false)
self.beginChatHistoryTransitions(resetScrolling: false, switchedToAnotherSource: true)
}
private func beginAdMessageManagement(adMessages: Signal<(interPostInterval: Int32?, messages: [Message]), NoError>) {
@ -1293,7 +1293,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
private let previousView = Atomic<(ChatHistoryView, Int, Set<MessageId>?, Int)?>(value: nil)
private let previousHistoryAppearsCleared = Atomic<Bool?>(value: nil)
private func beginChatHistoryTransitions(resetScrolling: Bool) {
private func beginChatHistoryTransitions(resetScrolling: Bool, switchedToAnotherSource: Bool) {
self.historyDisposable.set(nil)
self._isReady.set(false)
@ -2085,6 +2085,10 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
var disableAnimations = false
var forceSynchronous = false
if switchedToAnotherSource {
disableAnimations = true
}
if let previousValueAndVersion = previousValueAndVersion, allAdMessages.version != previousValueAndVersion.3 {
reason = ChatHistoryViewTransitionReason.Reload
disableAnimations = true
@ -2243,7 +2247,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
mappedTransition.options.remove(.AnimateTopItemPosition)
mappedTransition.options.remove(.RequestItemInsertionAnimations)
}
if forceSynchronous || resetScrolling {
if forceSynchronous || resetScrolling || switchedToAnotherSource {
mappedTransition.options.insert(.Synchronous)
}
if resetScrolling {

View file

@ -63,8 +63,6 @@ func inputContextQueriesForChatPresentationIntefaceState(_ chatPresentationInter
break
case .businessLinkSetup:
return []
case .postSuggestions:
return []
}
}
@ -243,8 +241,6 @@ func inputTextPanelStateForChatPresentationInterfaceState(_ chatPresentationInte
break
case .businessLinkSetup:
stickersEnabled = false
case .postSuggestions:
break
}
}

View file

@ -2108,39 +2108,6 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
}
case .businessLinkSetup:
actions.removeAll()
case .postSuggestions:
//TODO:release
actions.removeAll()
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_MessageDialogEdit, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.actionSheet.primaryTextColor)
}, action: { c, f in
interfaceInteraction.setupEditMessage(messages[0].id, { transition in
f(.custom(transition))
})
})))
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.ScheduledMessages_EditTime, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Schedule"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in
controllerInteraction.editScheduledMessagesTime(messages.map { $0.id })
f(.dismissWithoutContent)
})))
actions.append(.action(ContextMenuActionItem(text: "Edit Price", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Tag"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in
f(.dismissWithoutContent)
})))
actions.append(.action(ContextMenuActionItem(text: "Delete", textColor: .destructive, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor)
}, action: { controller, f in
interfaceInteraction.deleteMessages(messages, controller, f)
})))
actions.append(.separator)
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let action: ((ContextControllerProtocol?, @escaping (ContextMenuActionResult) -> Void) -> Void)? = nil
actions.append(.action(ContextMenuActionItem(text: "Deleting suggested post will auto-refund your order.", textColor: .primary, textLayout: .multiline, textFont: .custom(font: Font.regular(floor(presentationData.listsFontSize.baseDisplaySize * 0.8)), height: nil, verticalOffset: nil), badge: nil, icon: { theme in
return nil
}, iconSource: nil, action: action)))
}
}

View file

@ -10,13 +10,7 @@ import ChatChannelSubscriberInputPanelNode
import ChatMessageSelectionInputPanelNode
func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, currentPanel: ChatInputPanelNode?, currentSecondaryPanel: ChatInputPanelNode?, textInputPanelNode: ChatTextInputPanelNode?, interfaceInteraction: ChatPanelInterfaceInteraction?) -> (primary: ChatInputPanelNode?, secondary: ChatInputPanelNode?) {
var isPostSuggestions = false
if case let .customChatContents(customChatContents) = chatPresentationInterfaceState.subject, case .postSuggestions = customChatContents.kind {
isPostSuggestions = true
}
if isPostSuggestions {
} else if let renderedPeer = chatPresentationInterfaceState.renderedPeer, renderedPeer.peer?.restrictionText(platform: "ios", contentSettings: context.currentContentSettings.with { $0 }) != nil {
if let renderedPeer = chatPresentationInterfaceState.renderedPeer, renderedPeer.peer?.restrictionText(platform: "ios", contentSettings: context.currentContentSettings.with { $0 }) != nil {
return (nil, nil)
}
if chatPresentationInterfaceState.isNotAccessible {
@ -138,8 +132,7 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
}
}
if isPostSuggestions {
} else if chatPresentationInterfaceState.peerIsBlocked, let peer = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramUser, peer.botInfo == nil {
if chatPresentationInterfaceState.peerIsBlocked, let peer = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramUser, peer.botInfo == nil {
if let currentPanel = (currentPanel as? ChatUnblockInputPanelNode) ?? (currentSecondaryPanel as? ChatUnblockInputPanelNode) {
currentPanel.interfaceInteraction = interfaceInteraction
currentPanel.updateThemeAndStrings(theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings)
@ -154,9 +147,7 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
var displayInputTextPanel = false
if isPostSuggestions {
displayInputTextPanel = true
} else if let peer = chatPresentationInterfaceState.renderedPeer?.peer {
if let peer = chatPresentationInterfaceState.renderedPeer?.peer {
if peer.id.isRepliesOrVerificationCodes {
if let currentPanel = (currentPanel as? ChatChannelSubscriberInputPanelNode) ?? (currentSecondaryPanel as? ChatChannelSubscriberInputPanelNode) {
return (currentPanel, nil)
@ -239,7 +230,20 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
}
if channel.flags.contains(.isMonoforum) {
displayInputTextPanel = true
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)
}
} else {
displayInputTextPanel = true
}
} else if channel.flags.contains(.isForum) && isMember {
var canManage = false
if channel.flags.contains(.isCreator) {
@ -418,8 +422,6 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
displayInputTextPanel = false
case .quickReplyMessageInput, .businessLinkSetup:
displayInputTextPanel = true
case .postSuggestions:
displayInputTextPanel = true
}
if let chatHistoryState = chatPresentationInterfaceState.chatHistoryState, case .loaded(_, true) = chatHistoryState {

View file

@ -59,7 +59,7 @@ func leftNavigationButtonForChatInterfaceState(_ presentationInterfaceState: Cha
switch customChatContents.kind {
case .hashTagSearch:
break
case .quickReplyMessageInput, .businessLinkSetup, .postSuggestions:
case .quickReplyMessageInput, .businessLinkSetup:
if let currentButton = currentButton, currentButton.action == .dismiss {
return currentButton
} else {
@ -149,8 +149,6 @@ func rightNavigationButtonForChatInterfaceState(context: AccountContext, present
buttonItem.accessibilityLabel = strings.Common_Done
return ChatNavigationButton(action: .edit, buttonItem: buttonItem)
}
case .postSuggestions:
return chatInfoNavigationButton
}
}

View file

@ -68,8 +68,6 @@ func titlePanelForChatPresentationInterfaceState(_ chatPresentationInterfaceStat
panel.interfaceInteraction = interfaceInteraction
return panel
}
case .postSuggestions:
break
}
default:
break
@ -167,19 +165,6 @@ func titlePanelForChatPresentationInterfaceState(_ chatPresentationInterfaceStat
}
}
if let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isForumOrMonoForum {
let topicListDisplayMode = chatPresentationInterfaceState.topicListDisplayMode ?? .top
if case .top = topicListDisplayMode, let peerId = chatPresentationInterfaceState.chatLocation.peerId {
if let currentPanel = currentPanel as? ChatTopicListTitleAccessoryPanelNode {
return currentPanel
} else {
let panel = ChatTopicListTitleAccessoryPanelNode(context: context, peerId: peerId)
panel.interfaceInteraction = interfaceInteraction
return panel
}
}
}
if (selectedContext == nil || selectedContext! <= .pinnedMessage) {
if displayActionsPanel {
if let currentPanel = currentPanel as? ChatReportPeerTitlePanelNode {
@ -246,13 +231,30 @@ func titlePanelForChatPresentationInterfaceState(_ chatPresentationInterfaceStat
return nil
}
func titleTopicsPanelForChatPresentationInterfaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, currentPanel: ChatTitleAccessoryPanelNode?, controllerInteraction: ChatControllerInteraction?, interfaceInteraction: ChatPanelInterfaceInteraction?, force: Bool) -> ChatTopicListTitleAccessoryPanelNode? {
if let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isForumOrMonoForum, channel.adminRights != nil {
let topicListDisplayMode = chatPresentationInterfaceState.topicListDisplayMode ?? .top
if case .top = topicListDisplayMode, let peerId = chatPresentationInterfaceState.chatLocation.peerId {
if let currentPanel = currentPanel as? ChatTopicListTitleAccessoryPanelNode {
return currentPanel
} else {
let panel = ChatTopicListTitleAccessoryPanelNode(context: context, peerId: peerId)
panel.interfaceInteraction = interfaceInteraction
return panel
}
}
}
return nil
}
func sidePanelForChatPresentationInterfaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, currentPanel: AnyComponentWithIdentity<ChatSidePanelEnvironment>?, controllerInteraction: ChatControllerInteraction?, interfaceInteraction: ChatPanelInterfaceInteraction?, force: Bool) -> AnyComponentWithIdentity<ChatSidePanelEnvironment>? {
guard let peerId = chatPresentationInterfaceState.chatLocation.peerId else {
return nil
}
if let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isForumOrMonoForum {
if let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isForumOrMonoForum, channel.adminRights != nil {
let topicListDisplayMode = chatPresentationInterfaceState.topicListDisplayMode ?? .top
if case .side = topicListDisplayMode {
return AnyComponentWithIdentity(

View file

@ -89,7 +89,10 @@ final class ChatRestrictedInputPanelNode: ChatInputPanelNode {
if let context = self.context {
accountFreezeConfiguration = AccountFreezeConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
}
if let _ = accountFreezeConfiguration?.freezeUntilDate {
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)
} 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)
isUserInteractionEnabled = true
@ -128,8 +131,6 @@ final class ChatRestrictedInputPanelNode: ChatInputPanelNode {
displayCount = customChatContents.messageLimit ?? 20
case .businessLinkSetup:
displayCount = 0
case .postSuggestions:
displayCount = 0
}
self.textNode.attributedText = NSAttributedString(string: interfaceState.strings.Chat_QuickReplyMessageLimitReachedText(Int32(displayCount)), font: Font.regular(13.0), textColor: interfaceState.theme.chat.inputPanel.secondaryTextColor)
}

View file

@ -290,13 +290,6 @@ final class ChatTextInputActionButtonsNode: ASDisplayNode, ChatSendMessageAction
}
}
starsAmount = amount
} else if case let .customChatContents(customChatContents) = interfaceState.subject {
switch customChatContents.kind {
case let .postSuggestions(postSuggestions):
starsAmount = postSuggestions.value
default:
break
}
}
if let amount = starsAmount {

View file

@ -1567,8 +1567,6 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
break
case .businessLinkSetup:
displayMediaButton = false
case .postSuggestions:
break
}
}
@ -1955,10 +1953,6 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
}
case .businessLinkSetup:
placeholder = interfaceState.strings.Chat_Placeholder_BusinessLinkPreset
case let .postSuggestions(postSuggestions):
//TODO:localize
placeholder = "Suggest for # \(postSuggestions)"
placeholderHasStar = true
}
}
@ -1986,8 +1980,6 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
break
case .businessLinkSetup:
sendButtonHasApplyIcon = true
case .postSuggestions:
break
}
}
}
@ -2515,8 +2507,6 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
showTitle = true
} else if case let .customChatContents(customChatContents) = interfaceState.subject {
switch customChatContents.kind {
case .postSuggestions:
showTitle = true
default:
break
}
@ -3794,8 +3784,6 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
break
case .businessLinkSetup:
keepSendButtonEnabled = true
case .postSuggestions:
break
}
}
}
@ -3916,8 +3904,6 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
break
case .businessLinkSetup:
hideMicButton = true
case .postSuggestions:
break
}
}
}
@ -4023,8 +4009,6 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
break
case .businessLinkSetup:
sendButtonHasApplyIcon = true
case .postSuggestions:
break
}
}

View file

@ -321,7 +321,7 @@ final class ChatTopicListTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, C
let iconSize = self.icon.update(
transition: .immediate,
component: AnyComponent(BundleIconComponent(
name: "Call/PanelIcon",
name: "Chat/Title Panels/SidebarIcon",
tintColor: theme.rootController.navigationBar.secondaryTextColor,
maxSize: nil,
scaleFactor: 1.0
@ -658,7 +658,7 @@ final class ChatTopicListTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, C
let itemSpacing: CGFloat = 24.0
var contentSize = CGSize(width: 0.0, height: panelHeight)
contentSize.width += containerInsets.left
contentSize.width += containerInsets.left + 8.0
var validIds: [Item.Id] = []
var isFirst = true
@ -862,4 +862,10 @@ final class ChatTopicListTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, C
}
}
}
public func updateGlobalOffset(globalOffset: CGFloat, transition: ComponentTransition) {
if let tabItemView = self.tabItemView {
transition.setTransform(view: tabItemView, transform: CATransform3DMakeTranslation(0.0, -globalOffset, 0.0))
}
}
}

View file

@ -193,6 +193,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
}, forceUpdateWarpContents: {
}, playShakeAnimation: {
}, displayQuickShare: { _, _ ,_ in
}, updateChatLocationThread: { _ in
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: context, backgroundNode: nil))
self.dimNode = ASDisplayNode()

View file

@ -2388,6 +2388,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
}, forceUpdateWarpContents: {
}, playShakeAnimation: {
}, displayQuickShare: { _, _ ,_ in
}, updateChatLocationThread: { _ in
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: context, backgroundNode: backgroundNode as? WallpaperBackgroundNode))

View file

@ -296,7 +296,7 @@ final class PeerNameColorChatPreviewItemNode: ListViewItemNode {
header.updateNode(headerNode, previous: nil, next: nil)
headerNode.item = header
}
headerNode.updateLayoutInternal(size: headerFrame.size, leftInset: leftInset, rightInset: rightInset)
headerNode.updateLayoutInternal(size: headerFrame.size, leftInset: leftInset, rightInset: rightInset, transition: .immediate)
headerNode.updateStickDistanceFactor(stickLocationDistanceFactor, transition: .immediate)
} else {
headerNode = header.node(synchronousLoad: true)
@ -305,7 +305,7 @@ final class PeerNameColorChatPreviewItemNode: ListViewItemNode {
headerNode.item = header
}
headerNode.frame = headerFrame
headerNode.updateLayoutInternal(size: headerFrame.size, leftInset: leftInset, rightInset: rightInset)
headerNode.updateLayoutInternal(size: headerFrame.size, leftInset: leftInset, rightInset: rightInset, transition: .immediate)
strongSelf.itemHeaderNodes[id] = headerNode
strongSelf.containerNode.addSubnode(headerNode)