mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
[WIP] Pinned message improvements
This commit is contained in:
parent
1620754988
commit
e37edd6319
@ -5874,7 +5874,7 @@ Any member of this group will be able to see messages in the channel.";
|
||||
|
||||
"Chat.PanelHidePinnedMessages" = "Don't Show Pinned Messages";
|
||||
"Chat.PanelUnpinAllMessages_1" = "Unpin Message";
|
||||
"Chat.PanelUnpinAllMessages_any" = "Unpin All %@ Messages";
|
||||
"Chat.PanelUnpinAllMessages_any" = "Unpin All Messages";
|
||||
"Chat.UnpinAllMessagesConfirmation_1" = "Do you want to unpin 1 message in this chat?";
|
||||
"Chat.UnpinAllMessagesConfirmation_any" = "Do you want to unpin all %@ messages in this chat?";
|
||||
|
||||
@ -5883,3 +5883,5 @@ Any member of this group will be able to see messages in the channel.";
|
||||
|
||||
"Chat.PinnedMessagesHiddenTitle" = "Pinned Messages Hidden";
|
||||
"Chat.PinnedMessagesHiddenText" = "You will see the bar with pinned messages only if a new message is pinned.";
|
||||
|
||||
"OpenFile.PotentiallyDangerousContentAlert" = "Previewing this file can potentially expose your IP address to its sender. Continue?";
|
||||
|
16
submodules/AnimatedNavigationStripeNode/BUCK
Normal file
16
submodules/AnimatedNavigationStripeNode/BUCK
Normal file
@ -0,0 +1,16 @@
|
||||
load("//Config:buck_rule_macros.bzl", "static_library")
|
||||
|
||||
static_library(
|
||||
name = "AnimatedNavigationStripeNode",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
deps = [
|
||||
"//submodules/Display:Display#shared",
|
||||
"//submodules/AsyncDisplayKit:AsyncDisplayKit#shared",
|
||||
],
|
||||
frameworks = [
|
||||
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
|
||||
"$SDKROOT/System/Library/Frameworks/UIKit.framework",
|
||||
],
|
||||
)
|
16
submodules/AnimatedNavigationStripeNode/BUILD
Normal file
16
submodules/AnimatedNavigationStripeNode/BUILD
Normal file
@ -0,0 +1,16 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "AnimatedNavigationStripeNode",
|
||||
module_name = "AnimatedNavigationStripeNode",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
deps = [
|
||||
"//submodules/Display:Display",
|
||||
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
@ -0,0 +1,281 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
|
||||
public final class AnimatedNavigationStripeNode: ASDisplayNode {
|
||||
public struct Colors: Equatable {
|
||||
public var foreground: UIColor
|
||||
public var background: UIColor
|
||||
public var clearBackground: UIColor
|
||||
|
||||
public init(
|
||||
foreground: UIColor,
|
||||
background: UIColor,
|
||||
clearBackground: UIColor
|
||||
) {
|
||||
self.foreground = foreground
|
||||
self.background = background
|
||||
self.clearBackground = clearBackground
|
||||
}
|
||||
|
||||
public static func ==(lhs: Colors, rhs: Colors) -> Bool {
|
||||
if !lhs.foreground.isEqual(rhs.foreground) {
|
||||
return false
|
||||
}
|
||||
if !lhs.background.isEqual(rhs.background) {
|
||||
return false
|
||||
}
|
||||
if !lhs.clearBackground.isEqual(rhs.clearBackground) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
public struct Configuration: Equatable {
|
||||
public var height: CGFloat
|
||||
public var index: Int
|
||||
public var count: Int
|
||||
|
||||
public init(height: CGFloat, index: Int, count: Int) {
|
||||
self.height = height
|
||||
self.index = index
|
||||
self.count = count
|
||||
}
|
||||
}
|
||||
|
||||
private final class BackgroundLineNode {
|
||||
let lineNode: ASImageNode
|
||||
let overlayNode: ASImageNode
|
||||
|
||||
init() {
|
||||
self.lineNode = ASImageNode()
|
||||
self.overlayNode = ASImageNode()
|
||||
}
|
||||
}
|
||||
|
||||
private var currentColors: Colors?
|
||||
private var currentConfiguration: Configuration?
|
||||
|
||||
private let foregroundLineNode: ASImageNode
|
||||
private var backgroundLineNodes: [Int: BackgroundLineNode] = [:]
|
||||
private var removingBackgroundLineNodes: [BackgroundLineNode] = []
|
||||
|
||||
private let topShadowNode: ASImageNode
|
||||
private let bottomShadowNode: ASImageNode
|
||||
|
||||
private var currentForegroundImage: UIImage?
|
||||
private var currentBackgroundImage: UIImage?
|
||||
private var currentClearBackgroundImage: UIImage?
|
||||
|
||||
override public init() {
|
||||
self.foregroundLineNode = ASImageNode()
|
||||
self.topShadowNode = ASImageNode()
|
||||
self.bottomShadowNode = ASImageNode()
|
||||
|
||||
super.init()
|
||||
|
||||
self.clipsToBounds = true
|
||||
|
||||
self.addSubnode(self.foregroundLineNode)
|
||||
self.addSubnode(self.topShadowNode)
|
||||
self.addSubnode(self.bottomShadowNode)
|
||||
}
|
||||
|
||||
public func update(colors: Colors, configuration: Configuration, transition: ContainedViewLayoutTransition) {
|
||||
var transition = transition
|
||||
|
||||
let segmentSpacing: CGFloat = 2.0
|
||||
|
||||
if self.currentColors != colors {
|
||||
self.currentColors = colors
|
||||
self.currentForegroundImage = generateFilledCircleImage(diameter: 2.0, color: colors.foreground)?.resizableImage(withCapInsets: UIEdgeInsets(top: 1.0, left: 0.0, bottom: 1.0, right: 0.0), resizingMode: .stretch)
|
||||
self.currentBackgroundImage = generateFilledCircleImage(diameter: 2.0, color: colors.background)?.resizableImage(withCapInsets: UIEdgeInsets(top: 1.0, left: 0.0, bottom: 1.0, right: 0.0), resizingMode: .stretch)
|
||||
self.currentClearBackgroundImage = generateImage(CGSize(width: 2.0, height: 4.0 + segmentSpacing * 2.0 + 1.0 * 2.0), contextGenerator: { size, context in
|
||||
context.setFillColor(colors.clearBackground.cgColor)
|
||||
context.fill(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
context.setFillColor(UIColor.clear.cgColor)
|
||||
context.setBlendMode(.copy)
|
||||
|
||||
let ellipseFudge: CGFloat = 0.02
|
||||
|
||||
let topEllipse = CGRect(origin: CGPoint(x: -ellipseFudge, y: 1.0 + segmentSpacing), size: CGSize(width: 2.0 + ellipseFudge * 2.0, height: 2.0))
|
||||
let bottomEllipse = CGRect(origin: CGPoint(x: -ellipseFudge, y: size.height - (1.0 + segmentSpacing) - 2.0), size: CGSize(width: 2.0 + ellipseFudge * 2.0, height: 2.0))
|
||||
|
||||
context.fillEllipse(in: topEllipse)
|
||||
context.fillEllipse(in: bottomEllipse)
|
||||
|
||||
context.fill(CGRect(origin: CGPoint(x: 0.0, y: topEllipse.midY), size: CGSize(width: 2.0, height: bottomEllipse.midY - topEllipse.midY)))
|
||||
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: 0.0, y: -1.0), size: CGSize(width: 2.0, height: 2.0)))
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: 0.0, y: size.height - 1.0), size: CGSize(width: 2.0, height: 2.0)))
|
||||
})?.resizableImage(withCapInsets: UIEdgeInsets(top: 1.0 + segmentSpacing + 2.0, left: 1.0, bottom: 1.0 + segmentSpacing + 2.0, right: 1.0), resizingMode: .stretch)
|
||||
|
||||
self.foregroundLineNode.image = self.currentForegroundImage
|
||||
for (_, itemNode) in self.backgroundLineNodes {
|
||||
itemNode.lineNode.image = self.currentBackgroundImage
|
||||
itemNode.overlayNode.image = self.currentClearBackgroundImage
|
||||
}
|
||||
|
||||
self.topShadowNode.image = generateImage(CGSize(width: 2.0, height: 7.0), contextGenerator: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
var locations: [CGFloat] = [1.0, 0.0]
|
||||
let colors: [CGColor] = [colors.clearBackground.cgColor, colors.clearBackground.withAlphaComponent(0.0).cgColor]
|
||||
|
||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
||||
|
||||
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions())
|
||||
})
|
||||
|
||||
self.bottomShadowNode.image = generateImage(CGSize(width: 2.0, height: 7.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
var locations: [CGFloat] = [1.0, 0.0]
|
||||
let colors: [CGColor] = [colors.clearBackground.cgColor, colors.clearBackground.withAlphaComponent(0.0).cgColor]
|
||||
|
||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
||||
|
||||
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions())
|
||||
})
|
||||
}
|
||||
|
||||
if self.currentConfiguration == nil {
|
||||
transition = .immediate
|
||||
}
|
||||
|
||||
if self.currentConfiguration != configuration {
|
||||
self.currentConfiguration = configuration
|
||||
|
||||
let defaultVerticalInset: CGFloat = 7.0
|
||||
let minSegmentHeight: CGFloat = 8.0
|
||||
|
||||
transition.updateFrame(node: self.topShadowNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: 2.0, height: defaultVerticalInset)))
|
||||
transition.updateFrame(node: self.bottomShadowNode, frame: CGRect(origin: CGPoint(x: 0.0, y: configuration.height - defaultVerticalInset), size: CGSize(width: 2.0, height: defaultVerticalInset)))
|
||||
|
||||
let availableVerticalHeight: CGFloat = configuration.height - defaultVerticalInset * 2.0
|
||||
|
||||
let proposedSegmentHeight: CGFloat = (availableVerticalHeight - segmentSpacing * CGFloat(configuration.count) + segmentSpacing) / CGFloat(configuration.count)
|
||||
let segmentHeight = max(proposedSegmentHeight, minSegmentHeight)
|
||||
|
||||
let allItemsHeight = CGFloat(configuration.count) * segmentHeight + max(0.0, CGFloat(configuration.count - 1)) * segmentSpacing
|
||||
|
||||
var verticalInset = defaultVerticalInset
|
||||
if allItemsHeight > availableVerticalHeight && allItemsHeight - 2.0 <= availableVerticalHeight {
|
||||
verticalInset -= 2.0
|
||||
}
|
||||
|
||||
let topItemsHeight = CGFloat(configuration.index) * (segmentHeight + segmentSpacing)
|
||||
let bottomItemsHeight = allItemsHeight - topItemsHeight - segmentHeight
|
||||
|
||||
var itemScreenOffset = floorToScreenPixels((configuration.height - segmentHeight) / 2.0)
|
||||
|
||||
if itemScreenOffset - topItemsHeight > verticalInset {
|
||||
itemScreenOffset = topItemsHeight + verticalInset
|
||||
}
|
||||
if itemScreenOffset + segmentHeight + bottomItemsHeight < configuration.height - verticalInset {
|
||||
itemScreenOffset = configuration.height - verticalInset - (segmentHeight + bottomItemsHeight)
|
||||
}
|
||||
|
||||
var backgroundItemNodesToOffset: [BackgroundLineNode] = []
|
||||
var resolvedOffset: CGFloat = 0.0
|
||||
|
||||
func updateBackgroundLine(index: Int) -> Bool {
|
||||
let indexDifference = index - configuration.index
|
||||
let offsetDistance = CGFloat(indexDifference) * (segmentHeight + segmentSpacing)
|
||||
|
||||
let itemFrame = CGRect(origin: CGPoint(x: 0.0, y: itemScreenOffset + offsetDistance), size: CGSize(width: 2.0, height: segmentHeight))
|
||||
|
||||
if itemFrame.maxY <= 0.0 || itemFrame.minY > configuration.height {
|
||||
return false
|
||||
}
|
||||
|
||||
var itemNodeTransition = transition
|
||||
let itemNode: BackgroundLineNode
|
||||
if let current = self.backgroundLineNodes[index] {
|
||||
itemNode = current
|
||||
let offset = itemFrame.minY - itemNode.lineNode.frame.minY
|
||||
if abs(offset) > abs(resolvedOffset) {
|
||||
resolvedOffset = offset
|
||||
}
|
||||
} else {
|
||||
itemNodeTransition = .immediate
|
||||
itemNode = BackgroundLineNode()
|
||||
itemNode.lineNode.image = self.currentBackgroundImage
|
||||
itemNode.overlayNode.image = self.currentClearBackgroundImage
|
||||
self.backgroundLineNodes[index] = itemNode
|
||||
self.insertSubnode(itemNode.lineNode, belowSubnode: self.foregroundLineNode)
|
||||
self.insertSubnode(itemNode.overlayNode, belowSubnode: self.topShadowNode)
|
||||
backgroundItemNodesToOffset.append(itemNode)
|
||||
}
|
||||
itemNodeTransition.updateFrame(node: itemNode.lineNode, frame: itemFrame, beginWithCurrentState: true)
|
||||
itemNodeTransition.updateFrame(node: itemNode.overlayNode, frame: itemFrame.insetBy(dx: 0.0, dy: -(1.0 + segmentSpacing)), beginWithCurrentState: true)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
var validIndices = Set<Int>()
|
||||
if configuration.index >= 0 {
|
||||
for i in (0 ... configuration.index).reversed() {
|
||||
if updateBackgroundLine(index: i) {
|
||||
validIndices.insert(i)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if configuration.index < configuration.count {
|
||||
for i in configuration.index + 1 ..< configuration.count {
|
||||
if updateBackgroundLine(index: i) {
|
||||
validIndices.insert(i)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !resolvedOffset.isZero {
|
||||
for itemNode in backgroundItemNodesToOffset {
|
||||
transition.animatePositionAdditive(node: itemNode.lineNode, offset: CGPoint(x: 0.0, y: -resolvedOffset))
|
||||
transition.animatePositionAdditive(node: itemNode.overlayNode, offset: CGPoint(x: 0.0, y: -resolvedOffset))
|
||||
}
|
||||
for itemNode in self.removingBackgroundLineNodes {
|
||||
transition.animatePosition(node: itemNode.lineNode, to: CGPoint(x: 0.0, y: resolvedOffset), removeOnCompletion: false, additive: true)
|
||||
transition.animatePosition(node: itemNode.overlayNode, to: CGPoint(x: 0.0, y: resolvedOffset), removeOnCompletion: false, additive: true)
|
||||
}
|
||||
}
|
||||
|
||||
var removeIndices: [Int] = []
|
||||
for (index, itemNode) in self.backgroundLineNodes {
|
||||
if !validIndices.contains(index) {
|
||||
removeIndices.append(index)
|
||||
|
||||
if transition.isAnimated {
|
||||
removingBackgroundLineNodes.append(itemNode)
|
||||
transition.animatePosition(node: itemNode.overlayNode, to: CGPoint(x: 0.0, y: resolvedOffset), removeOnCompletion: false, additive: true)
|
||||
transition.animatePosition(node: itemNode.lineNode, to: CGPoint(x: 0.0, y: resolvedOffset), removeOnCompletion: false, additive: true, completion: { [weak self, weak itemNode] _ in
|
||||
guard let strongSelf = self, let itemNode = itemNode else {
|
||||
return
|
||||
}
|
||||
strongSelf.removingBackgroundLineNodes.removeAll(where: { $0 === itemNode })
|
||||
itemNode.lineNode.removeFromSupernode()
|
||||
itemNode.overlayNode.removeFromSupernode()
|
||||
})
|
||||
} else {
|
||||
itemNode.lineNode.removeFromSupernode()
|
||||
itemNode.overlayNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
}
|
||||
for index in removeIndices {
|
||||
self.backgroundLineNodes.removeValue(forKey: index)
|
||||
}
|
||||
|
||||
transition.updateFrame(node: self.foregroundLineNode, frame: CGRect(origin: CGPoint(x: 0.0, y: itemScreenOffset), size: CGSize(width: 2.0, height: segmentHeight)), beginWithCurrentState: true)
|
||||
}
|
||||
}
|
||||
}
|
@ -628,9 +628,11 @@ final class ChatListFilterTabContainerNode: ASDisplayNode {
|
||||
self.selectedLineNode.image = generateImage(CGSize(width: 5.0, height: 3.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(presentationData.theme.list.itemAccentColor.cgColor)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height + 1.0)))
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: 3.0, height: 3.0)))
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: size.width - 3.0, y: 0.0), size: CGSize(width: 3.0, height: 3.0)))
|
||||
context.fill(CGRect(x: 1.5, y: 0.0, width: size.width - 3.0, height: 3.0))
|
||||
context.fill(CGRect(x: 0.0, y: 2.0, width: size.width, height: 2.0))
|
||||
})?.stretchableImage(withLeftCapWidth: 2, topCapHeight: 2)
|
||||
})?.resizableImage(withCapInsets: UIEdgeInsets(top: 3.0, left: 2.5, bottom: 0.0, right: 2.5), resizingMode: .stretch)
|
||||
}
|
||||
|
||||
if isReordering {
|
||||
|
@ -276,8 +276,8 @@ public extension ContainedViewLayoutTransition {
|
||||
}
|
||||
}
|
||||
|
||||
func animatePosition(node: ASDisplayNode, to position: CGPoint, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) {
|
||||
if node.position.equalTo(position) {
|
||||
func animatePosition(node: ASDisplayNode, to position: CGPoint, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
|
||||
if !additive && node.position.equalTo(position) {
|
||||
completion?(true)
|
||||
} else {
|
||||
switch self {
|
||||
@ -286,7 +286,7 @@ public extension ContainedViewLayoutTransition {
|
||||
completion(true)
|
||||
}
|
||||
case let .animated(duration, curve):
|
||||
node.layer.animatePosition(from: node.position, to: position, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: removeOnCompletion, completion: { result in
|
||||
node.layer.animatePosition(from: additive ? CGPoint() : node.position, to: position, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: additive, completion: { result in
|
||||
if let completion = completion {
|
||||
completion(result)
|
||||
}
|
||||
|
@ -5,19 +5,21 @@ import AsyncDisplayKit
|
||||
open class ASImageNode: ASDisplayNode {
|
||||
public var image: UIImage? {
|
||||
didSet {
|
||||
if let image = self.image {
|
||||
let capInsets = image.capInsets
|
||||
if capInsets.left.isZero && capInsets.top.isZero {
|
||||
self.contentsScale = image.scale
|
||||
self.contents = image.cgImage
|
||||
if self.isNodeLoaded {
|
||||
if let image = self.image {
|
||||
let capInsets = image.capInsets
|
||||
if capInsets.left.isZero && capInsets.top.isZero {
|
||||
self.contentsScale = image.scale
|
||||
self.contents = image.cgImage
|
||||
} else {
|
||||
ASDisplayNodeSetResizableContents(self.layer, image)
|
||||
}
|
||||
} else {
|
||||
ASDisplayNodeSetResizableContents(self, image)
|
||||
self.contents = nil
|
||||
}
|
||||
if self.image?.size != oldValue?.size {
|
||||
self.invalidateCalculatedLayout()
|
||||
}
|
||||
} else {
|
||||
self.contents = nil
|
||||
}
|
||||
if self.image?.size != oldValue?.size {
|
||||
self.invalidateCalculatedLayout()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -28,6 +30,20 @@ open class ASImageNode: ASDisplayNode {
|
||||
super.init()
|
||||
}
|
||||
|
||||
override open func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
if let image = self.image {
|
||||
let capInsets = image.capInsets
|
||||
if capInsets.left.isZero && capInsets.top.isZero {
|
||||
self.contentsScale = image.scale
|
||||
self.contents = image.cgImage
|
||||
} else {
|
||||
ASDisplayNodeSetResizableContents(self.layer, image)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override public func calculateSizeThatFits(_ contrainedSize: CGSize) -> CGSize {
|
||||
return self.image?.size ?? CGSize()
|
||||
}
|
||||
|
@ -212,6 +212,7 @@ framework(
|
||||
"//submodules/AnimatedCountLabelNode:AnimatedCountLabelNode",
|
||||
"//submodules/AnimatedAvatarSetNode:AnimatedAvatarSetNode",
|
||||
"//submodules/SlotMachineAnimationNode:SlotMachineAnimationNode",
|
||||
"//submodules/AnimatedNavigationStripeNode:AnimatedNavigationStripeNode",
|
||||
],
|
||||
frameworks = [
|
||||
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
|
||||
|
@ -209,6 +209,7 @@ swift_library(
|
||||
"//submodules/AnimatedCountLabelNode:AnimatedCountLabelNode",
|
||||
"//submodules/AnimatedAvatarSetNode:AnimatedAvatarSetNode",
|
||||
"//submodules/SlotMachineAnimationNode:SlotMachineAnimationNode",
|
||||
"//submodules/AnimatedNavigationStripeNode:AnimatedNavigationStripeNode",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -4206,8 +4206,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
} else {
|
||||
if case .known = strongSelf.chatDisplayNode.historyNode.visibleContentOffset() {
|
||||
strongSelf.chatDisplayNode.historyNode.scrollToEndOfHistory()
|
||||
} else if case let .peer(peerId) = strongSelf.chatLocation {
|
||||
strongSelf.navigateToMessage(messageLocation: .upperBound(peerId), animated: true)
|
||||
} else if case .peer = strongSelf.chatLocation {
|
||||
strongSelf.scrollToEndOfHistory()
|
||||
} else if case .replyThread = strongSelf.chatLocation {
|
||||
strongSelf.scrollToEndOfHistory()
|
||||
} else {
|
||||
@ -4751,8 +4751,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
})
|
||||
strongSelf.updateItemNodesSearchTextHighlightStates()
|
||||
}
|
||||
}, navigateToMessage: { [weak self] messageId, dropStack in
|
||||
self?.navigateToMessage(from: nil, to: .id(messageId), dropStack: dropStack)
|
||||
}, navigateToMessage: { [weak self] messageId, dropStack, forceInCurrentChat in
|
||||
self?.navigateToMessage(from: nil, to: .id(messageId), forceInCurrentChat: forceInCurrentChat, dropStack: dropStack)
|
||||
}, navigateToChat: { [weak self] peerId in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
@ -5294,16 +5294,25 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
if canManagePin {
|
||||
let count = strongSelf.presentationInterfaceState.pinnedMessage?.totalCount ?? 1
|
||||
|
||||
let _ = (requestUnpinAllMessages(account: strongSelf.context.account, peerId: strongSelf.chatLocation.peerId)
|
||||
|> deliverOnMainQueue).start(error: { _ in
|
||||
|
||||
}, completed: {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.dismiss()
|
||||
strongSelf.updatedUnpinnedAllMessages?(count)
|
||||
})
|
||||
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: strongSelf.presentationData.strings.Chat_UnpinAllMessagesConfirmation(Int32(count)), actions: [
|
||||
TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {
|
||||
}),
|
||||
TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Conversation_Unpin, action: {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let _ = (requestUnpinAllMessages(account: strongSelf.context.account, peerId: strongSelf.chatLocation.peerId)
|
||||
|> deliverOnMainQueue).start(error: { _ in
|
||||
|
||||
}, completed: {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.dismiss()
|
||||
strongSelf.updatedUnpinnedAllMessages?(count)
|
||||
})
|
||||
})
|
||||
], parseMarkdown: true), in: .window(.root))
|
||||
} else {
|
||||
let topPinnedMessage: Signal<ChatPinnedMessage?, NoError> = strongSelf.topPinnedMessageSignal(latest: true)
|
||||
|> take(1)
|
||||
@ -9337,7 +9346,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: false, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedForwardMessageIds(nil) }) })
|
||||
}
|
||||
|
||||
if case .peer(peerId) = strongSelf.chatLocation, strongSelf.parentController == nil {
|
||||
var isPinnedMessages = false
|
||||
if case .pinnedMessages = strongSelf.presentationInterfaceState.subject {
|
||||
isPinnedMessages = true
|
||||
}
|
||||
|
||||
if case .peer(peerId) = strongSelf.chatLocation, strongSelf.parentController == nil, !isPinnedMessages {
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: false, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedForwardMessageIds(messages.map { $0.id }).withoutSelectionState() }) })
|
||||
strongController.dismiss()
|
||||
} else if peerId == strongSelf.context.account.peerId {
|
||||
@ -9375,6 +9389,25 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: false, interactive: true, { $0.updatedInterfaceState({ $0.withoutSelectionState() }) })
|
||||
strongController.dismiss()
|
||||
} else {
|
||||
if let navigationController = strongSelf.navigationController as? NavigationController {
|
||||
for controller in navigationController.viewControllers {
|
||||
if let maybeChat = controller as? ChatControllerImpl {
|
||||
if case .peer(peerId) = maybeChat.chatLocation {
|
||||
var isChatPinnedMessages = false
|
||||
if case .pinnedMessages = maybeChat.presentationInterfaceState.subject {
|
||||
isChatPinnedMessages = true
|
||||
}
|
||||
if !isChatPinnedMessages {
|
||||
maybeChat.updateChatPresentationInterfaceState(animated: false, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedForwardMessageIds(messages.map { $0.id }).withoutSelectionState() }) })
|
||||
strongSelf.dismiss()
|
||||
strongController.dismiss()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let _ = (strongSelf.context.account.postbox.transaction({ transaction -> Void in
|
||||
transaction.updatePeerChatInterfaceState(peerId, update: { currentState in
|
||||
if let currentState = currentState as? ChatInterfaceState {
|
||||
|
@ -424,7 +424,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
||||
refineContentImageLayout = refineLayout
|
||||
} else if file.isInstantVideo {
|
||||
let automaticDownload = shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, authorPeerId: message.author?.id, contactsPeerIds: associatedData.contactsPeerIds, media: file)
|
||||
let (videoLayout, apply) = contentInstantVideoLayout(ChatMessageBubbleContentItem(context: context, controllerInteraction: controllerInteraction, message: message, read: messageRead, chatLocation: chatLocation, presentationData: presentationData, associatedData: associatedData, attributes: attributes), constrainedSize.width - horizontalInsets.left - horizontalInsets.right, CGSize(width: 212.0, height: 212.0), .bubble, automaticDownload)
|
||||
let (videoLayout, apply) = contentInstantVideoLayout(ChatMessageBubbleContentItem(context: context, controllerInteraction: controllerInteraction, message: message, read: messageRead, chatLocation: chatLocation, presentationData: presentationData, associatedData: associatedData, attributes: attributes, isItemPinned: message.tags.contains(.pinned)), constrainedSize.width - horizontalInsets.left - horizontalInsets.right, CGSize(width: 212.0, height: 212.0), .bubble, automaticDownload)
|
||||
initialWidth = videoLayout.contentSize.width + videoLayout.overflowLeft + videoLayout.overflowRight
|
||||
contentInstantVideoSizeAndApply = (videoLayout, apply)
|
||||
} else if file.isVideo {
|
||||
@ -474,7 +474,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
let (_, refineLayout) = contentFileLayout(context, presentationData, message, associatedData, chatLocation, attributes, file, automaticDownload, message.effectivelyIncoming(context.account.peerId), false, associatedData.forcedResourceStatus, statusType, nil, CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height))
|
||||
let (_, refineLayout) = contentFileLayout(context, presentationData, message, associatedData, chatLocation, attributes, message.tags.contains(.pinned) && !associatedData.isInPinnedListMode, file, automaticDownload, message.effectivelyIncoming(context.account.peerId), false, associatedData.forcedResourceStatus, statusType, nil, CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height))
|
||||
refineContentFileLayout = refineLayout
|
||||
}
|
||||
} else if let image = media as? TelegramMediaImage {
|
||||
|
@ -108,8 +108,9 @@ final class ChatMessageBubbleContentItem {
|
||||
let presentationData: ChatPresentationData
|
||||
let associatedData: ChatMessageItemAssociatedData
|
||||
let attributes: ChatMessageEntryAttributes
|
||||
let isItemPinned: Bool
|
||||
|
||||
init(context: AccountContext, controllerInteraction: ChatControllerInteraction, message: Message, read: Bool, chatLocation: ChatLocation, presentationData: ChatPresentationData, associatedData: ChatMessageItemAssociatedData, attributes: ChatMessageEntryAttributes) {
|
||||
init(context: AccountContext, controllerInteraction: ChatControllerInteraction, message: Message, read: Bool, chatLocation: ChatLocation, presentationData: ChatPresentationData, associatedData: ChatMessageItemAssociatedData, attributes: ChatMessageEntryAttributes, isItemPinned: Bool) {
|
||||
self.context = context
|
||||
self.controllerInteraction = controllerInteraction
|
||||
self.message = message
|
||||
@ -118,6 +119,7 @@ final class ChatMessageBubbleContentItem {
|
||||
self.presentationData = presentationData
|
||||
self.associatedData = associatedData
|
||||
self.attributes = attributes
|
||||
self.isItemPinned = isItemPinned
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1208,11 +1208,19 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
let contentNodeCount = contentPropertiesAndPrepareLayouts.count
|
||||
|
||||
let read: Bool
|
||||
var isItemPinned = false
|
||||
|
||||
switch item.content {
|
||||
case let .message(_, value, _, _):
|
||||
case let .message(message, value, _, _):
|
||||
read = value
|
||||
isItemPinned = message.tags.contains(.pinned)
|
||||
case let .group(messages):
|
||||
read = messages[0].1
|
||||
for message in messages {
|
||||
if message.0.tags.contains(.pinned) {
|
||||
isItemPinned = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var mosaicStartIndex: Int?
|
||||
@ -1267,7 +1275,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
prepareContentPosition = .linear(top: topPosition, bottom: refinedBottomPosition)
|
||||
}
|
||||
|
||||
let contentItem = ChatMessageBubbleContentItem(context: item.context, controllerInteraction: item.controllerInteraction, message: message, read: read, chatLocation: item.chatLocation, presentationData: item.presentationData, associatedData: item.associatedData, attributes: attributes)
|
||||
let contentItem = ChatMessageBubbleContentItem(context: item.context, controllerInteraction: item.controllerInteraction, message: message, read: read, chatLocation: item.chatLocation, presentationData: item.presentationData, associatedData: item.associatedData, attributes: attributes, isItemPinned: isItemPinned)
|
||||
|
||||
var itemSelection: Bool?
|
||||
switch content {
|
||||
|
@ -89,7 +89,7 @@ class ChatMessageFileBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
|
||||
let automaticDownload = shouldDownloadMediaAutomatically(settings: item.controllerInteraction.automaticMediaDownloadSettings, peerType: item.associatedData.automaticDownloadPeerType, networkType: item.associatedData.automaticDownloadNetworkType, authorPeerId: item.message.author?.id, contactsPeerIds: item.associatedData.contactsPeerIds, media: selectedFile!)
|
||||
|
||||
let (initialWidth, refineLayout) = interactiveFileLayout(item.context, item.presentationData, item.message, item.associatedData, item.chatLocation, item.attributes, selectedFile!, automaticDownload, item.message.effectivelyIncoming(item.context.account.peerId), item.associatedData.isRecentActions, item.associatedData.forcedResourceStatus, statusType, item.message.groupingKey != nil ? selection : nil, CGSize(width: constrainedSize.width - layoutConstants.file.bubbleInsets.left - layoutConstants.file.bubbleInsets.right, height: constrainedSize.height))
|
||||
let (initialWidth, refineLayout) = interactiveFileLayout(item.context, item.presentationData, item.message, item.associatedData, item.chatLocation, item.attributes, item.isItemPinned, selectedFile!, automaticDownload, item.message.effectivelyIncoming(item.context.account.peerId), item.associatedData.isRecentActions, item.associatedData.forcedResourceStatus, statusType, item.message.groupingKey != nil ? selection : nil, CGSize(width: constrainedSize.width - layoutConstants.file.bubbleInsets.left - layoutConstants.file.bubbleInsets.right, height: constrainedSize.height))
|
||||
|
||||
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 0.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)
|
||||
|
||||
|
@ -276,7 +276,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView {
|
||||
}
|
||||
}
|
||||
|
||||
let (videoLayout, videoApply) = makeVideoLayout(ChatMessageBubbleContentItem(context: item.context, controllerInteraction: item.controllerInteraction, message: item.message, read: item.read, chatLocation: item.chatLocation, presentationData: item.presentationData, associatedData: item.associatedData, attributes: item.content.firstMessageAttributes), params.width - params.leftInset - params.rightInset - avatarInset, displaySize, .free, automaticDownload)
|
||||
let (videoLayout, videoApply) = makeVideoLayout(ChatMessageBubbleContentItem(context: item.context, controllerInteraction: item.controllerInteraction, message: item.message, read: item.read, chatLocation: item.chatLocation, presentationData: item.presentationData, associatedData: item.associatedData, attributes: item.content.firstMessageAttributes, isItemPinned: item.message.tags.contains(.pinned)), params.width - params.leftInset - params.rightInset - avatarInset, displaySize, .free, automaticDownload)
|
||||
|
||||
let videoFrame = CGRect(origin: CGPoint(x: (incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + avatarInset + layoutConstants.bubble.contentInsets.left) : (params.width - params.rightInset - videoLayout.contentSize.width - layoutConstants.bubble.edgeInset - layoutConstants.bubble.contentInsets.left - deliveryFailedInset)), y: 0.0), size: videoLayout.contentSize)
|
||||
|
||||
|
@ -208,7 +208,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
func asyncLayout() -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ message: Message, _ associatedData: ChatMessageItemAssociatedData, _ chatLocation: ChatLocation, _ attributes: ChatMessageEntryAttributes, _ file: TelegramMediaFile, _ automaticDownload: Bool, _ incoming: Bool, _ isRecentActions: Bool, _ forcedResourceStatus: FileMediaResourceStatus?, _ dateAndStatusType: ChatMessageDateAndStatusType?, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (CGFloat, (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, (Bool) -> Void))) {
|
||||
func asyncLayout() -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ message: Message, _ associatedData: ChatMessageItemAssociatedData, _ chatLocation: ChatLocation, _ attributes: ChatMessageEntryAttributes, _ isPinned: Bool, _ file: TelegramMediaFile, _ automaticDownload: Bool, _ incoming: Bool, _ isRecentActions: Bool, _ forcedResourceStatus: FileMediaResourceStatus?, _ dateAndStatusType: ChatMessageDateAndStatusType?, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (CGFloat, (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, (Bool) -> Void))) {
|
||||
let currentFile = self.file
|
||||
|
||||
let titleAsyncLayout = TextNode.asyncLayout(self.titleNode)
|
||||
@ -218,7 +218,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
||||
|
||||
let currentMessage = self.message
|
||||
|
||||
return { context, presentationData, message, associatedData, chatLocation, attributes, file, automaticDownload, incoming, isRecentActions, forcedResourceStatus, dateAndStatusType, messageSelection, constrainedSize in
|
||||
return { context, presentationData, message, associatedData, chatLocation, attributes, isPinned, file, automaticDownload, incoming, isRecentActions, forcedResourceStatus, dateAndStatusType, messageSelection, constrainedSize in
|
||||
return (CGFloat.greatestFiniteMagnitude, { constrainedSize in
|
||||
let titleFont = Font.regular(floor(presentationData.fontSize.baseDisplaySize * 16.0 / 17.0))
|
||||
let descriptionFont = Font.regular(floor(presentationData.fontSize.baseDisplaySize * 13.0 / 17.0))
|
||||
@ -327,7 +327,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
||||
|
||||
let dateText = stringForMessageTimestampStatus(accountPeerId: context.account.peerId, message: message, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, strings: presentationData.strings, reactionCount: dateReactionCount)
|
||||
|
||||
let (size, apply) = statusLayout(context, presentationData, edited, viewCount, dateText, statusType, constrainedSize, dateReactions, dateReplies, message.tags.contains(.pinned) && !associatedData.isInPinnedListMode)
|
||||
let (size, apply) = statusLayout(context, presentationData, edited, viewCount, dateText, statusType, constrainedSize, dateReactions, dateReplies, isPinned && !associatedData.isInPinnedListMode)
|
||||
statusSize = size
|
||||
statusApply = apply
|
||||
}
|
||||
@ -1032,12 +1032,12 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
||||
self.fetchingCompactTextNode.frame = CGRect(origin: self.descriptionNode.frame.origin, size: fetchingCompactSize)
|
||||
}
|
||||
|
||||
static func asyncLayout(_ node: ChatMessageInteractiveFileNode?) -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ message: Message, _ associatedData: ChatMessageItemAssociatedData, _ chatLocation: ChatLocation, _ attributes: ChatMessageEntryAttributes, _ file: TelegramMediaFile, _ automaticDownload: Bool, _ incoming: Bool, _ isRecentActions: Bool, _ forcedResourceStatus: FileMediaResourceStatus?, _ dateAndStatusType: ChatMessageDateAndStatusType?, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (CGFloat, (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, (Bool) -> ChatMessageInteractiveFileNode))) {
|
||||
static func asyncLayout(_ node: ChatMessageInteractiveFileNode?) -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ message: Message, _ associatedData: ChatMessageItemAssociatedData, _ chatLocation: ChatLocation, _ attributes: ChatMessageEntryAttributes, _ isPinned: Bool, _ file: TelegramMediaFile, _ automaticDownload: Bool, _ incoming: Bool, _ isRecentActions: Bool, _ forcedResourceStatus: FileMediaResourceStatus?, _ dateAndStatusType: ChatMessageDateAndStatusType?, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (CGFloat, (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, (Bool) -> ChatMessageInteractiveFileNode))) {
|
||||
let currentAsyncLayout = node?.asyncLayout()
|
||||
|
||||
return { context, presentationData, message, associatedData, chatLocation, attributes, file, automaticDownload, incoming, isRecentActions, forcedResourceStatus, dateAndStatusType, messageSelection, constrainedSize in
|
||||
return { context, presentationData, message, associatedData, chatLocation, attributes, isPinned, file, automaticDownload, incoming, isRecentActions, forcedResourceStatus, dateAndStatusType, messageSelection, constrainedSize in
|
||||
var fileNode: ChatMessageInteractiveFileNode
|
||||
var fileLayout: (_ context: AccountContext, _ presentationData: ChatPresentationData, _ message: Message, _ associatedData: ChatMessageItemAssociatedData, _ chatLocation: ChatLocation, _ attributes: ChatMessageEntryAttributes, _ file: TelegramMediaFile, _ automaticDownload: Bool, _ incoming: Bool, _ isRecentActions: Bool, _ forcedResourceStatus: FileMediaResourceStatus?, _ dateAndStatusType: ChatMessageDateAndStatusType?, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (CGFloat, (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, (Bool) -> Void)))
|
||||
var fileLayout: (_ context: AccountContext, _ presentationData: ChatPresentationData, _ message: Message, _ associatedData: ChatMessageItemAssociatedData, _ chatLocation: ChatLocation, _ attributes: ChatMessageEntryAttributes, _ isPinned: Bool, _ file: TelegramMediaFile, _ automaticDownload: Bool, _ incoming: Bool, _ isRecentActions: Bool, _ forcedResourceStatus: FileMediaResourceStatus?, _ dateAndStatusType: ChatMessageDateAndStatusType?, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (CGFloat, (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, (Bool) -> Void)))
|
||||
|
||||
if let node = node, let currentAsyncLayout = currentAsyncLayout {
|
||||
fileNode = node
|
||||
@ -1047,7 +1047,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
||||
fileLayout = fileNode.asyncLayout()
|
||||
}
|
||||
|
||||
let (initialWidth, continueLayout) = fileLayout(context, presentationData, message, associatedData, chatLocation, attributes, file, automaticDownload, incoming, isRecentActions, forcedResourceStatus, dateAndStatusType, messageSelection, constrainedSize)
|
||||
let (initialWidth, continueLayout) = fileLayout(context, presentationData, message, associatedData, chatLocation, attributes, isPinned, file, automaticDownload, incoming, isRecentActions, forcedResourceStatus, dateAndStatusType, messageSelection, constrainedSize)
|
||||
|
||||
return (initialWidth, { constrainedSize in
|
||||
let (finalWidth, finalLayout) = continueLayout(constrainedSize)
|
||||
|
@ -73,7 +73,7 @@ final class ChatPanelInterfaceInteraction {
|
||||
let openSearchResults: () -> Void
|
||||
let openCalendarSearch: () -> Void
|
||||
let toggleMembersSearch: (Bool) -> Void
|
||||
let navigateToMessage: (MessageId, Bool) -> Void
|
||||
let navigateToMessage: (MessageId, Bool, Bool) -> Void
|
||||
let navigateToChat: (PeerId) -> Void
|
||||
let navigateToProfile: (PeerId) -> Void
|
||||
let openPeerInfo: () -> Void
|
||||
@ -151,7 +151,7 @@ final class ChatPanelInterfaceInteraction {
|
||||
navigateMessageSearch: @escaping (ChatPanelSearchNavigationAction) -> Void,
|
||||
openCalendarSearch: @escaping () -> Void,
|
||||
toggleMembersSearch: @escaping (Bool) -> Void,
|
||||
navigateToMessage: @escaping (MessageId, Bool) -> Void,
|
||||
navigateToMessage: @escaping (MessageId, Bool, Bool) -> Void,
|
||||
navigateToChat: @escaping (PeerId) -> Void,
|
||||
navigateToProfile: @escaping (PeerId) -> Void,
|
||||
openPeerInfo: @escaping () -> Void,
|
||||
|
@ -13,6 +13,7 @@ import StickerResources
|
||||
import PhotoResources
|
||||
import TelegramStringFormatting
|
||||
import AnimatedCountLabelNode
|
||||
import AnimatedNavigationStripeNode
|
||||
|
||||
private enum PinnedMessageAnimation {
|
||||
case slideToTop
|
||||
@ -28,7 +29,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
private let clippingContainer: ASDisplayNode
|
||||
private let contentContainer: ASDisplayNode
|
||||
private let contentTextContainer: ASDisplayNode
|
||||
private let lineNode: ASImageNode
|
||||
private let lineNode: AnimatedNavigationStripeNode
|
||||
private let titleNode: AnimatedCountLabelNode
|
||||
private let textNode: TextNode
|
||||
|
||||
@ -69,9 +70,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
self.contentContainer = ASDisplayNode()
|
||||
self.contentTextContainer = ASDisplayNode()
|
||||
|
||||
self.lineNode = ASImageNode()
|
||||
self.lineNode.displayWithoutProcessing = true
|
||||
self.lineNode.displaysAsynchronously = false
|
||||
self.lineNode = AnimatedNavigationStripeNode()
|
||||
|
||||
self.titleNode = AnimatedCountLabelNode()
|
||||
self.titleNode.isUserInteractionEnabled = false
|
||||
@ -145,7 +144,6 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
self.theme = interfaceState.theme
|
||||
self.closeButton.setImage(PresentationResourcesChat.chatInputPanelCloseIconImage(interfaceState.theme), for: [])
|
||||
self.listButton.setImage(PresentationResourcesChat.chatInputPanelPinnedListIconImage(interfaceState.theme), for: [])
|
||||
self.lineNode.image = PresentationResourcesChat.chatInputPanelVerticalSeparatorLineImage(interfaceState.theme)
|
||||
self.backgroundColor = interfaceState.theme.chat.historyNavigation.fillColor
|
||||
self.separatorNode.backgroundColor = interfaceState.theme.chat.historyNavigation.strokeColor
|
||||
}
|
||||
@ -180,7 +178,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
self.currentMessage = interfaceState.pinnedMessage
|
||||
|
||||
if let currentMessage = self.currentMessage, let currentLayout = self.currentLayout {
|
||||
self.enqueueTransition(width: currentLayout.0, leftInset: currentLayout.1, rightInset: currentLayout.2, transition: .immediate, animation: messageUpdatedAnimation, pinnedMessage: currentMessage, theme: interfaceState.theme, strings: interfaceState.strings, nameDisplayOrder: interfaceState.nameDisplayOrder, accountPeerId: self.context.account.peerId, firstTime: previousMessageWasNil, isReplyThread: isReplyThread)
|
||||
self.enqueueTransition(width: currentLayout.0, panelHeight: panelHeight, leftInset: currentLayout.1, rightInset: currentLayout.2, transition: .immediate, animation: messageUpdatedAnimation, pinnedMessage: currentMessage, theme: interfaceState.theme, strings: interfaceState.strings, nameDisplayOrder: interfaceState.nameDisplayOrder, accountPeerId: self.context.account.peerId, firstTime: previousMessageWasNil, isReplyThread: isReplyThread)
|
||||
}
|
||||
}
|
||||
|
||||
@ -200,11 +198,8 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
self.closeButton.isHidden = true
|
||||
}
|
||||
|
||||
let contentLeftInset: CGFloat = 10.0 + leftInset
|
||||
let rightInset: CGFloat = 18.0 + rightInset
|
||||
|
||||
transition.updateFrame(node: self.lineNode, frame: CGRect(origin: CGPoint(x: contentLeftInset, y: 7.0), size: CGSize(width: 2.0, height: panelHeight - 14.0)))
|
||||
|
||||
let closeButtonSize = self.closeButton.measure(CGSize(width: 100.0, height: 100.0))
|
||||
transition.updateFrame(node: self.closeButton, frame: CGRect(origin: CGPoint(x: width - rightInset - closeButtonSize.width, y: 19.0), size: closeButtonSize))
|
||||
|
||||
@ -221,14 +216,14 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
self.currentLayout = (width, leftInset, rightInset)
|
||||
|
||||
if let currentMessage = self.currentMessage {
|
||||
self.enqueueTransition(width: width, leftInset: leftInset, rightInset: rightInset, transition: .immediate, animation: .none, pinnedMessage: currentMessage, theme: interfaceState.theme, strings: interfaceState.strings, nameDisplayOrder: interfaceState.nameDisplayOrder, accountPeerId: interfaceState.accountPeerId, firstTime: true, isReplyThread: isReplyThread)
|
||||
self.enqueueTransition(width: width, panelHeight: panelHeight, leftInset: leftInset, rightInset: rightInset, transition: .immediate, animation: .none, pinnedMessage: currentMessage, theme: interfaceState.theme, strings: interfaceState.strings, nameDisplayOrder: interfaceState.nameDisplayOrder, accountPeerId: interfaceState.accountPeerId, firstTime: true, isReplyThread: isReplyThread)
|
||||
}
|
||||
}
|
||||
|
||||
return panelHeight
|
||||
}
|
||||
|
||||
private func enqueueTransition(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, animation: PinnedMessageAnimation?, pinnedMessage: ChatPinnedMessage, theme: PresentationTheme, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, accountPeerId: PeerId, firstTime: Bool, isReplyThread: Bool) {
|
||||
private func enqueueTransition(width: CGFloat, panelHeight: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, animation: PinnedMessageAnimation?, pinnedMessage: ChatPinnedMessage, theme: PresentationTheme, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, accountPeerId: PeerId, firstTime: Bool, isReplyThread: Bool) {
|
||||
let message = pinnedMessage.message
|
||||
|
||||
var animationTransition: ContainedViewLayoutTransition = .immediate
|
||||
@ -390,6 +385,22 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 5.0), size: titleLayout.size)
|
||||
strongSelf.textNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 23.0), size: textLayout.size)
|
||||
|
||||
let lineFrame = CGRect(origin: CGPoint(x: contentLeftInset, y: 0.0), size: CGSize(width: 2.0, height: panelHeight))
|
||||
animationTransition.updateFrame(node: strongSelf.lineNode, frame: lineFrame)
|
||||
strongSelf.lineNode.update(
|
||||
colors: AnimatedNavigationStripeNode.Colors(
|
||||
foreground: theme.chat.inputPanel.panelControlAccentColor,
|
||||
background: theme.chat.inputPanel.panelControlAccentColor.withAlphaComponent(0.5),
|
||||
clearBackground: theme.chat.inputPanel.panelBackgroundColor
|
||||
),
|
||||
configuration: AnimatedNavigationStripeNode.Configuration(
|
||||
height: panelHeight,
|
||||
index: pinnedMessage.index,
|
||||
count: pinnedMessage.totalCount
|
||||
),
|
||||
transition: animationTransition
|
||||
)
|
||||
|
||||
strongSelf.imageNodeContainer.frame = CGRect(origin: CGPoint(x: contentLeftInset + 9.0, y: 7.0), size: CGSize(width: 35.0, height: 35.0))
|
||||
strongSelf.imageNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 35.0, height: 35.0))
|
||||
|
||||
@ -419,7 +430,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
if self.isReplyThread {
|
||||
interfaceInteraction.scrollToTop()
|
||||
} else {
|
||||
interfaceInteraction.navigateToMessage(message.message.id, false)
|
||||
interfaceInteraction.navigateToMessage(message.message.id, false, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -76,7 +76,7 @@ final class ChatRecentActionsController: TelegramBaseController {
|
||||
}, navigateMessageSearch: { _ in
|
||||
}, openCalendarSearch: {
|
||||
}, toggleMembersSearch: { _ in
|
||||
}, navigateToMessage: { _, _ in
|
||||
}, navigateToMessage: { _, _, _ in
|
||||
}, navigateToChat: { _ in
|
||||
}, navigateToProfile: { _ in
|
||||
}, openPeerInfo: {
|
||||
|
@ -347,7 +347,7 @@ final class EditAccessoryPanelNode: AccessoryPanelNode {
|
||||
|
||||
@objc func contentTap(_ recognizer: UITapGestureRecognizer) {
|
||||
if case .ended = recognizer.state, let message = self.currentMessage {
|
||||
self.interfaceInteraction?.navigateToMessage(message.id, false)
|
||||
self.interfaceInteraction?.navigateToMessage(message.id, false, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -382,7 +382,7 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode {
|
||||
}, navigateMessageSearch: { _ in
|
||||
}, openCalendarSearch: {
|
||||
}, toggleMembersSearch: { _ in
|
||||
}, navigateToMessage: { _, _ in
|
||||
}, navigateToMessage: { _, _, _ in
|
||||
}, navigateToChat: { _ in
|
||||
}, navigateToProfile: { _ in
|
||||
}, openPeerInfo: {
|
||||
|
@ -256,7 +256,7 @@ final class ReplyAccessoryPanelNode: AccessoryPanelNode {
|
||||
|
||||
@objc func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
if case .ended = recognizer.state {
|
||||
self.interfaceInteraction?.navigateToMessage(self.messageId, false)
|
||||
self.interfaceInteraction?.navigateToMessage(self.messageId, false, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -218,7 +218,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
}
|
||||
|
||||
displayUndo = undo
|
||||
self.originalRemainingSeconds = undo ? 5 : 3
|
||||
self.originalRemainingSeconds = undo ? 5 : 5
|
||||
case let .emoji(path, text):
|
||||
self.iconNode = nil
|
||||
self.iconCheckNode = nil
|
||||
|
Loading…
x
Reference in New Issue
Block a user