diff --git a/submodules/AccountContext/Sources/ChatController.swift b/submodules/AccountContext/Sources/ChatController.swift index 5931ae17b1..4cab8932e6 100644 --- a/submodules/AccountContext/Sources/ChatController.swift +++ b/submodules/AccountContext/Sources/ChatController.swift @@ -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 { diff --git a/submodules/BrowserUI/Sources/BrowserBookmarksScreen.swift b/submodules/BrowserUI/Sources/BrowserBookmarksScreen.swift index d694b93de4..7bb140e7a8 100644 --- a/submodules/BrowserUI/Sources/BrowserBookmarksScreen.swift +++ b/submodules/BrowserUI/Sources/BrowserBookmarksScreen.swift @@ -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)) diff --git a/submodules/ChatListSearchItemHeader/Sources/ChatListSearchItemHeader.swift b/submodules/ChatListSearchItemHeader/Sources/ChatListSearchItemHeader.swift index 463f70fe57..acd26de1ae 100644 --- a/submodules/ChatListSearchItemHeader/Sources/ChatListSearchItemHeader.swift +++ b/submodules/ChatListSearchItemHeader/Sources/ChatListSearchItemHeader.swift @@ -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) diff --git a/submodules/ContactListUI/Sources/ContactListNameIndexHeader.swift b/submodules/ContactListUI/Sources/ContactListNameIndexHeader.swift index 2d263ec274..fcfe07cd01 100644 --- a/submodules/ContactListUI/Sources/ContactListNameIndexHeader.swift +++ b/submodules/ContactListUI/Sources/ContactListNameIndexHeader.swift @@ -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) } diff --git a/submodules/Display/Source/CAAnimationUtils.swift b/submodules/Display/Source/CAAnimationUtils.swift index 952098bdee..73cc4f6e26 100644 --- a/submodules/Display/Source/CAAnimationUtils.swift +++ b/submodules/Display/Source/CAAnimationUtils.swift @@ -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) } diff --git a/submodules/Display/Source/ContainedViewLayoutTransition.swift b/submodules/Display/Source/ContainedViewLayoutTransition.swift index 6b49b01437..e72348ad07 100644 --- a/submodules/Display/Source/ContainedViewLayoutTransition.swift +++ b/submodules/Display/Source/ContainedViewLayoutTransition.swift @@ -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: diff --git a/submodules/Display/Source/ListView.swift b/submodules/Display/Source/ListView.swift index 6484cab49b..872fd3622a 100644 --- a/submodules/Display/Source/ListView.swift +++ b/submodules/Display/Source/ListView.swift @@ -1740,18 +1740,30 @@ open class ListView: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDel } } - private func nodeForItem(synchronous: Bool, synchronousLoads: Bool, item: ListViewItem, previousNode: QueueLocalObject?, index: Int, previousItem: ListViewItem?, nextItem: ListViewItem?, params: ListViewItemLayoutParams, updateAnimationIsAnimated: Bool, updateAnimationIsCrossfade: Bool, completion: @escaping (QueueLocalObject, ListViewItemNodeLayout, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { + private func nodeForItem(synchronous: Bool, synchronousLoads: Bool, item: ListViewItem, previousNode: QueueLocalObject?, index: Int, previousItem: ListViewItem?, nextItem: ListViewItem?, params: ListViewItemLayoutParams, updateAnimationIsAnimated: Bool, updateAnimationIsCrossfade: Bool, customAnimationTransition: ControlledTransition?, completion: @escaping (QueueLocalObject, ListViewItemNodeLayout, @escaping () -> (Signal?, (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(), 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(), 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, operations: [ListViewStateOperation], completion: @escaping (ListViewState, [ListViewStateOperation]) -> Void) { + private func updateAdjacent(synchronous: Bool, animated: Bool, customAnimationTransition: ControlledTransition?, state: ListViewState, updateAdjacentItemsIndices: Set, 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, insertDirectionHints: [Int: ListViewItemOperationDirectionHint], inputState: ListViewState, inputPreviousNodes: [Int: QueueLocalObject], inputOperations: [ListViewStateOperation], inputCompletion: @escaping (ListViewState, [ListViewStateOperation]) -> Void) { + private func fillMissingNodes(synchronous: Bool, synchronousLoads: Bool, animated: Bool, customAnimationTransition: ControlledTransition?, inputAnimatedInsertIndices: Set, insertDirectionHints: [Int: ListViewItemOperationDirectionHint], inputState: ListViewState, inputPreviousNodes: [Int: QueueLocalObject], 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], 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], 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, 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, 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) } } } diff --git a/submodules/Display/Source/ListViewIntermediateState.swift b/submodules/Display/Source/ListViewIntermediateState.swift index 0d5c6d9ba0..e80b3c1185 100644 --- a/submodules/Display/Source/ListViewIntermediateState.swift +++ b/submodules/Display/Source/ListViewIntermediateState.swift @@ -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 } } diff --git a/submodules/Display/Source/ListViewItemHeader.swift b/submodules/Display/Source/ListViewItemHeader.swift index 8842efc8a2..286dade8cf 100644 --- a/submodules/Display/Source/ListViewItemHeader.swift +++ b/submodules/Display/Source/ListViewItemHeader.swift @@ -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) { diff --git a/submodules/Display/Source/ListViewItemNode.swift b/submodules/Display/Source/ListViewItemNode.swift index 2d44033cb2..7c3ff8eee7 100644 --- a/submodules/Display/Source/ListViewItemNode.swift +++ b/submodules/Display/Source/ListViewItemNode.swift @@ -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 { diff --git a/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift b/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift index 0723c7d3a2..44cd222b2d 100644 --- a/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift +++ b/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift @@ -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) diff --git a/submodules/ListMessageItem/Sources/ListMessageDateHeader.swift b/submodules/ListMessageItem/Sources/ListMessageDateHeader.swift index a6763f51d6..6c27a3fae4 100644 --- a/submodules/ListMessageItem/Sources/ListMessageDateHeader.swift +++ b/submodules/ListMessageItem/Sources/ListMessageDateHeader.swift @@ -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) diff --git a/submodules/SettingsUI/Sources/BubbleSettings/BubbleSettingsController.swift b/submodules/SettingsUI/Sources/BubbleSettings/BubbleSettingsController.swift index b3a847c5dd..ff0d42bcf8 100644 --- a/submodules/SettingsUI/Sources/BubbleSettings/BubbleSettingsController.swift +++ b/submodules/SettingsUI/Sources/BubbleSettings/BubbleSettingsController.swift @@ -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) { diff --git a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift index da00b80f4f..27b71eaca3 100644 --- a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift +++ b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift @@ -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) { diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift index 94b8743d41..dce8992d3f 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift @@ -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) { diff --git a/submodules/TelegramCore/Sources/Account/AccountManager.swift b/submodules/TelegramCore/Sources/Account/AccountManager.swift index 180790f157..ef367cdca9 100644 --- a/submodules/TelegramCore/Sources/Account/AccountManager.swift +++ b/submodules/TelegramCore/Sources/Account/AccountManager.swift @@ -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) }) diff --git a/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift b/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift index 8c1cf3caaf..8758b43e1c 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift @@ -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) diff --git a/submodules/TelegramCore/Sources/PendingMessages/RequestEditMessage.swift b/submodules/TelegramCore/Sources/PendingMessages/RequestEditMessage.swift index eaf279d18e..398ddb9e9c 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/RequestEditMessage.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/RequestEditMessage.swift @@ -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)) diff --git a/submodules/TelegramCore/Sources/State/AccountViewTracker.swift b/submodules/TelegramCore/Sources/State/AccountViewTracker.swift index 0e11587c6e..e32b516153 100644 --- a/submodules/TelegramCore/Sources/State/AccountViewTracker.swift +++ b/submodules/TelegramCore/Sources/State/AccountViewTracker.swift @@ -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? = nil, ignoreMessageIds: Set = 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 { diff --git a/submodules/TelegramCore/Sources/State/ApplyUpdateMessage.swift b/submodules/TelegramCore/Sources/State/ApplyUpdateMessage.swift index d0b2dc5fa0..3507b5eda5 100644 --- a/submodules/TelegramCore/Sources/State/ApplyUpdateMessage.swift +++ b/submodules/TelegramCore/Sources/State/ApplyUpdateMessage.swift @@ -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 } } } diff --git a/submodules/TelegramCore/Sources/State/CloudChatRemoveMessagesOperation.swift b/submodules/TelegramCore/Sources/State/CloudChatRemoveMessagesOperation.swift index 1ace711069..acf9a30261 100644 --- a/submodules/TelegramCore/Sources/State/CloudChatRemoveMessagesOperation.swift +++ b/submodules/TelegramCore/Sources/State/CloudChatRemoveMessagesOperation.swift @@ -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 { diff --git a/submodules/TelegramCore/Sources/State/ManagedCloudChatRemoveMessagesOperations.swift b/submodules/TelegramCore/Sources/State/ManagedCloudChatRemoveMessagesOperations.swift index b520f4ba7f..18743c8450 100644 --- a/submodules/TelegramCore/Sources/State/ManagedCloudChatRemoveMessagesOperations.swift +++ b/submodules/TelegramCore/Sources/State/ManagedCloudChatRemoveMessagesOperations.swift @@ -128,7 +128,6 @@ func managedCloudChatRemoveMessagesOperations(postbox: Postbox, network: Network private func removeMessages(postbox: Postbox, network: Network, stateManager: AccountStateManager, peer: Peer, operation: CloudChatRemoveMessagesOperation) -> Signal { 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 = .complete() diff --git a/submodules/TelegramCore/Sources/State/PendingMessageManager.swift b/submodules/TelegramCore/Sources/State/PendingMessageManager.swift index 20f4471981..50036977a0 100644 --- a/submodules/TelegramCore/Sources/State/PendingMessageManager.swift +++ b/submodules/TelegramCore/Sources/State/PendingMessageManager.swift @@ -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 { diff --git a/submodules/TelegramCore/Sources/SyncCore/SuggestedPostMessageAttribute.swift b/submodules/TelegramCore/Sources/SyncCore/SuggestedPostMessageAttribute.swift deleted file mode 100644 index b442c2d157..0000000000 --- a/submodules/TelegramCore/Sources/SyncCore/SuggestedPostMessageAttribute.swift +++ /dev/null @@ -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 - } -} diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CloudChatRemoveMessagesOperation.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CloudChatRemoveMessagesOperation.swift index f3a5be61bf..41e23ccdcf 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CloudChatRemoveMessagesOperation.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CloudChatRemoveMessagesOperation.swift @@ -97,7 +97,6 @@ public enum CloudChatClearHistoryType: Int32 { case forEveryone case scheduledMessages case quickReplyMessages - case suggestedPostMessages } public enum InteractiveHistoryClearingType: Int32 { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift index ad26500ad9..f4e68f4760 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift @@ -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 = Set([Namespaces.Message.ScheduledCloud, Namespaces.Message.ScheduledLocal]) public static let allQuickReply: Set = Set([Namespaces.Message.QuickReplyCloud, Namespaces.Message.QuickReplyLocal]) - public static let allSuggestedPost: Set = Set([Namespaces.Message.SuggestedPostCloud, Namespaces.Message.SuggestedPostLocal]) - public static let allNonRegular: Set = Set([Namespaces.Message.ScheduledCloud, Namespaces.Message.ScheduledLocal, Namespaces.Message.QuickReplyCloud, Namespaces.Message.QuickReplyLocal, Namespaces.Message.SuggestedPostCloud, Namespaces.Message.SuggestedPostLocal]) + public static let allNonRegular: Set = Set([Namespaces.Message.ScheduledCloud, Namespaces.Message.ScheduledLocal, Namespaces.Message.QuickReplyCloud, Namespaces.Message.QuickReplyLocal]) public static let allLocal: [Int32] = [ Namespaces.Message.Local, Namespaces.Message.SecretIncoming, diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/SearchMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/SearchMessages.swift index 0f662c36e9..1b86cc4619 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/SearchMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/SearchMessages.swift @@ -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)])) diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift index 2dea72f9a3..b82e8717a7 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift @@ -338,6 +338,8 @@ public enum PresentationResourceKey: Int32 { case expandSmallDownArrowImage case callListCallIcon + + case chatFreeNavigateToThreadButtonIcon } public enum ChatExpiredStoryIndicatorType: Hashable { diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift index 2f97140cb7..bb868a76cc 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift @@ -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() + }) }) } diff --git a/submodules/TelegramUI/Components/Chat/ChatEmptyNode/Sources/ChatEmptyNode.swift b/submodules/TelegramUI/Components/Chat/ChatEmptyNode/Sources/ChatEmptyNode.swift index f577d0ffc1..c26c8e5ce1 100644 --- a/submodules/TelegramUI/Components/Chat/ChatEmptyNode/Sources/ChatEmptyNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatEmptyNode/Sources/ChatEmptyNode.swift @@ -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 - 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 diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/BUILD index 4f948fc37e..26f2d17330 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/BUILD @@ -89,6 +89,7 @@ swift_library( "//submodules/TelegramAnimatedStickerNode", "//submodules/TelegramUI/Components/LottieMetal", "//submodules/TelegramStringFormatting", + "//submodules/AvatarNode", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index 9154d474d5..14b8433d01 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -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? @@ -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 + var animateCredibilityIconFrame = true if let current = strongSelf.credibilityIconView { credibilityIconView = current } else { + animateCredibilityIconFrame = false credibilityIconView = ComponentHostView() 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 + } +} diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageSuggestedPostInfoNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageSuggestedPostInfoNode.swift index 59430f93c2..210e0770b6 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageSuggestedPostInfoNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageSuggestedPostInfoNode.swift @@ -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 diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/StringForMessageTimestampStatus.swift b/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/StringForMessageTimestampStatus.swift index a9f0a5a929..a751806322 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/StringForMessageTimestampStatus.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/StringForMessageTimestampStatus.swift @@ -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)" } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItem/Sources/ChatMessageItem.swift b/submodules/TelegramUI/Components/Chat/ChatMessageItem/Sources/ChatMessageItem.swift index 9cf52317b8..e31b95a366 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItem/Sources/ChatMessageItem.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItem/Sources/ChatMessageItem.swift @@ -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 { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageDateHeader.swift b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageDateHeader.swift index ec987e7b89..17f2c596b1 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageDateHeader.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageDateHeader.swift @@ -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 } } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageItemImpl.swift b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageItemImpl.swift index 83bb0ae9c4..9f553a7c55 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageItemImpl.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageItemImpl.swift @@ -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? diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemView/Sources/ChatMessageItemView.swift b/submodules/TelegramUI/Components/Chat/ChatMessageItemView/Sources/ChatMessageItemView.swift index 346ac78203..3875398d0a 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItemView/Sources/ChatMessageItemView.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemView/Sources/ChatMessageItemView.swift @@ -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() { } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageShareButton/Sources/ChatMessageShareButton.swift b/submodules/TelegramUI/Components/Chat/ChatMessageShareButton/Sources/ChatMessageShareButton.swift index 16d3d6d133..502bd18b5b 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageShareButton/Sources/ChatMessageShareButton.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageShareButton/Sources/ChatMessageShareButton.swift @@ -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) diff --git a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift index 2937054d2a..f6a07af078 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift @@ -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 diff --git a/submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview/Sources/ChatSendAudioMessageContextPreview.swift b/submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview/Sources/ChatSendAudioMessageContextPreview.swift index 9e7d3345f7..fb77a9c14a 100644 --- a/submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview/Sources/ChatSendAudioMessageContextPreview.swift +++ b/submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview/Sources/ChatSendAudioMessageContextPreview.swift @@ -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)) diff --git a/submodules/TelegramUI/Components/Chat/ChatSideTopicsPanel/Sources/ChatSideTopicsPanel.swift b/submodules/TelegramUI/Components/Chat/ChatSideTopicsPanel/Sources/ChatSideTopicsPanel.swift index 88cc604f04..8f8aa84c8b 100644 --- a/submodules/TelegramUI/Components/Chat/ChatSideTopicsPanel/Sources/ChatSideTopicsPanel.swift +++ b/submodules/TelegramUI/Components/Chat/ChatSideTopicsPanel/Sources/ChatSideTopicsPanel.swift @@ -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, transition: ComponentTransition) -> CGSize { self.isUpdating = true @@ -774,6 +779,7 @@ public final class ChatSideTopicsPanel: Component { } contentSize.height += itemSize.height + contentSize.height -= 20.0 } do { diff --git a/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift b/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift index b42b850c86..ec77cd376e 100644 --- a/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift +++ b/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift @@ -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 diff --git a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift index cea4348d69..2413db2468 100644 --- a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift +++ b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift @@ -1816,8 +1816,6 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { case .businessLinkSetup: stickerContent = nil gifContent = nil - case .postSuggestions: - break } } diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/DrawingMessageRenderer.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/DrawingMessageRenderer.swift index acf33642b9..4a57e7cefd 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/DrawingMessageRenderer.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/DrawingMessageRenderer.swift @@ -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 diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index b6638b9372..2db2475d9d 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -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 diff --git a/submodules/TelegramUI/Components/PeerInfo/PostSuggestionsSettingsScreen/Sources/PostSuggestionsChatContents.swift b/submodules/TelegramUI/Components/PeerInfo/PostSuggestionsSettingsScreen/Sources/PostSuggestionsChatContents.swift deleted file mode 100644 index 462c656fd4..0000000000 --- a/submodules/TelegramUI/Components/PeerInfo/PostSuggestionsSettingsScreen/Sources/PostSuggestionsChatContents.swift +++ /dev/null @@ -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 - - 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 } -} diff --git a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageSetupChatContents.swift b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageSetupChatContents.swift index 54794848fd..c8c10bdc99 100644 --- a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageSetupChatContents.swift +++ b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageSetupChatContents.swift @@ -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 } } diff --git a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorChatPreviewItem.swift b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorChatPreviewItem.swift index 221bd2b1a3..8bbc388aaa 100644 --- a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorChatPreviewItem.swift +++ b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorChatPreviewItem.swift @@ -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) diff --git a/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorControllerNode.swift b/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorControllerNode.swift index 673e91f7e2..4168ac0381 100644 --- a/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorControllerNode.swift +++ b/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorControllerNode.swift @@ -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) { diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Title Panels/SidebarIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Title Panels/SidebarIcon.imageset/Contents.json new file mode 100644 index 0000000000..de77409537 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Title Panels/SidebarIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "list_24.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Title Panels/SidebarIcon.imageset/list_24.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Title Panels/SidebarIcon.imageset/list_24.pdf new file mode 100644 index 0000000000..5b9942caa1 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Title Panels/SidebarIcon.imageset/list_24.pdf differ diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift index d1e97ab6bb..117648521c 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift @@ -1656,7 +1656,7 @@ extension ChatControllerImpl { self.reportIrrelvantGeoNoticePromise.set(.single(nil)) self.titleDisposable.set(nil) - var peerView: Signal = .single(nil) + let peerView: Signal = .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) diff --git a/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift b/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift index 14ae0ce885..7e02a681e8 100644 --- a/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift +++ b/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift @@ -230,8 +230,6 @@ func updateChatPresentationInterfaceStateImpl( break case .businessLinkSetup: canHaveUrlPreview = false - case .postSuggestions: - break } } diff --git a/submodules/TelegramUI/Sources/ChatBusinessLinkTitlePanelNode.swift b/submodules/TelegramUI/Sources/ChatBusinessLinkTitlePanelNode.swift index 993979ecc4..4fe8585ec1 100644 --- a/submodules/TelegramUI/Sources/ChatBusinessLinkTitlePanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatBusinessLinkTitlePanelNode.swift @@ -196,8 +196,6 @@ final class ChatBusinessLinkTitlePanelNode: ChatTitleAccessoryPanelNode { self.link = link case .hashTagSearch: break - case .postSuggestions: - break } default: break diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 6770d41ca7..20c5e037f0 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -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) diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index 1e8ec3690c..4b9b6368f3 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -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 diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index 8c1a5f8e09..3c2d528aff 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -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?, Int)?>(value: nil) private let previousHistoryAppearsCleared = Atomic(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 { diff --git a/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift b/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift index 0488f21816..43afa00b7a 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift @@ -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 } } diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index 6d82545c2b..f71aca16fa 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -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))) } } diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift index 27fe1a43f4..f6556dd621 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift @@ -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 { diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift index 5c40500cb1..57b6cd888b 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift @@ -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 } } diff --git a/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift b/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift index 8664b715c8..518b8a1994 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift @@ -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?, controllerInteraction: ChatControllerInteraction?, interfaceInteraction: ChatPanelInterfaceInteraction?, force: Bool) -> AnyComponentWithIdentity? { 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( diff --git a/submodules/TelegramUI/Sources/ChatRestrictedInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatRestrictedInputPanelNode.swift index aca5e3f05b..81470ba529 100644 --- a/submodules/TelegramUI/Sources/ChatRestrictedInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatRestrictedInputPanelNode.swift @@ -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) } diff --git a/submodules/TelegramUI/Sources/ChatTextInputActionButtonsNode.swift b/submodules/TelegramUI/Sources/ChatTextInputActionButtonsNode.swift index 816584b0c4..79d7e1d41b 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputActionButtonsNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputActionButtonsNode.swift @@ -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 { diff --git a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift index e31573f32c..67d5485e5e 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift @@ -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 } } diff --git a/submodules/TelegramUI/Sources/ChatTopicListTitleAccessoryPanelNode.swift b/submodules/TelegramUI/Sources/ChatTopicListTitleAccessoryPanelNode.swift index 393f250524..cfb4adc56d 100644 --- a/submodules/TelegramUI/Sources/ChatTopicListTitleAccessoryPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTopicListTitleAccessoryPanelNode.swift @@ -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)) + } + } } diff --git a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift index 7b485f6ed6..292d8ae989 100644 --- a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift +++ b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift @@ -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() diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index ba1599d62f..c54bed8408 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -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)) diff --git a/submodules/WebUI/Sources/WebAppMessageChatPreviewItem.swift b/submodules/WebUI/Sources/WebAppMessageChatPreviewItem.swift index ae66455633..641b903f99 100644 --- a/submodules/WebUI/Sources/WebAppMessageChatPreviewItem.swift +++ b/submodules/WebUI/Sources/WebAppMessageChatPreviewItem.swift @@ -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)