mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-24 07:05:35 +00:00
Various improvements
This commit is contained in:
@@ -25,6 +25,7 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode",
|
||||
"//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon",
|
||||
"//submodules/TelegramUI/Components/Chat/PollBubbleTimerNode",
|
||||
"//submodules/TelegramUI/Components/Chat/MergedAvatarsNode",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
||||
@@ -15,6 +15,7 @@ import ChatMessageDateAndStatusNode
|
||||
import ChatMessageBubbleContentNode
|
||||
import ChatMessageItemCommon
|
||||
import PollBubbleTimerNode
|
||||
import MergedAvatarsNode
|
||||
|
||||
private final class ChatMessagePollOptionRadioNodeParameters: NSObject {
|
||||
let timestamp: Double
|
||||
@@ -1440,10 +1441,10 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
timerTransition.updateAlpha(node: strongSelf.solutionButtonNode, alpha: 0.0)
|
||||
}
|
||||
|
||||
let avatarsFrame = CGRect(origin: CGPoint(x: typeFrame.maxX + 6.0, y: typeFrame.minY + floor((typeFrame.height - defaultMergedImageSize) / 2.0)), size: CGSize(width: defaultMergedImageSize + defaultMergedImageSpacing * 2.0, height: defaultMergedImageSize))
|
||||
let avatarsFrame = CGRect(origin: CGPoint(x: typeFrame.maxX + 6.0, y: typeFrame.minY + floor((typeFrame.height - MergedAvatarsNode.defaultMergedImageSize) / 2.0)), size: CGSize(width: MergedAvatarsNode.defaultMergedImageSize + MergedAvatarsNode.defaultMergedImageSpacing * 2.0, height: MergedAvatarsNode.defaultMergedImageSize))
|
||||
strongSelf.avatarsNode.frame = avatarsFrame
|
||||
strongSelf.avatarsNode.updateLayout(size: avatarsFrame.size)
|
||||
strongSelf.avatarsNode.update(context: item.context, peers: avatarPeers, synchronousLoad: synchronousLoad, imageSize: defaultMergedImageSize, imageSpacing: defaultMergedImageSpacing, borderWidth: defaultBorderWidth)
|
||||
strongSelf.avatarsNode.update(context: item.context, peers: avatarPeers, synchronousLoad: synchronousLoad, imageSize: MergedAvatarsNode.defaultMergedImageSize, imageSpacing: MergedAvatarsNode.defaultMergedImageSpacing, borderWidth: MergedAvatarsNode.defaultBorderWidth)
|
||||
strongSelf.avatarsNode.isHidden = isBotChat
|
||||
let alphaTransition: ContainedViewLayoutTransition
|
||||
if animation.isAnimated {
|
||||
@@ -1706,202 +1707,3 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
private enum PeerAvatarReference: Equatable {
|
||||
case letters(PeerId, PeerNameColor?, [String])
|
||||
case image(PeerReference, TelegramMediaImageRepresentation)
|
||||
|
||||
var peerId: PeerId {
|
||||
switch self {
|
||||
case let .letters(value, _, _):
|
||||
return value
|
||||
case let .image(value, _):
|
||||
return value.id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension PeerAvatarReference {
|
||||
init(peer: Peer) {
|
||||
if let photo = peer.smallProfileImage, let peerReference = PeerReference(peer) {
|
||||
self = .image(peerReference, photo)
|
||||
} else {
|
||||
self = .letters(peer.id, peer.nameColor, peer.displayLetters)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class MergedAvatarsNodeArguments: NSObject {
|
||||
let peers: [PeerAvatarReference]
|
||||
let images: [PeerId: UIImage]
|
||||
let imageSize: CGFloat
|
||||
let imageSpacing: CGFloat
|
||||
let borderWidth: CGFloat
|
||||
|
||||
init(peers: [PeerAvatarReference], images: [PeerId: UIImage], imageSize: CGFloat, imageSpacing: CGFloat, borderWidth: CGFloat) {
|
||||
self.peers = peers
|
||||
self.images = images
|
||||
self.imageSize = imageSize
|
||||
self.imageSpacing = imageSpacing
|
||||
self.borderWidth = borderWidth
|
||||
}
|
||||
}
|
||||
|
||||
private let defaultMergedImageSize: CGFloat = 16.0
|
||||
private let defaultMergedImageSpacing: CGFloat = 15.0
|
||||
private let defaultBorderWidth: CGFloat = 1.0
|
||||
|
||||
private let avatarFont = avatarPlaceholderFont(size: 8.0)
|
||||
|
||||
public final class MergedAvatarsNode: ASDisplayNode {
|
||||
private var peers: [PeerAvatarReference] = []
|
||||
private var images: [PeerId: UIImage] = [:]
|
||||
private var disposables: [PeerId: Disposable] = [:]
|
||||
private let buttonNode: HighlightTrackingButtonNode
|
||||
private var imageSize: CGFloat = defaultMergedImageSize
|
||||
private var imageSpacing: CGFloat = defaultMergedImageSpacing
|
||||
private var borderWidthValue: CGFloat = defaultBorderWidth
|
||||
|
||||
public var pressed: (() -> Void)?
|
||||
|
||||
override public init() {
|
||||
self.buttonNode = HighlightTrackingButtonNode()
|
||||
|
||||
super.init()
|
||||
|
||||
self.isOpaque = false
|
||||
self.displaysAsynchronously = true
|
||||
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
|
||||
self.addSubnode(self.buttonNode)
|
||||
}
|
||||
|
||||
deinit {
|
||||
for (_, disposable) in self.disposables {
|
||||
disposable.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func buttonPressed() {
|
||||
self.pressed?()
|
||||
}
|
||||
|
||||
public func updateLayout(size: CGSize) {
|
||||
self.buttonNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||
}
|
||||
|
||||
public func update(context: AccountContext, peers: [Peer], synchronousLoad: Bool, imageSize: CGFloat, imageSpacing: CGFloat, borderWidth: CGFloat) {
|
||||
self.imageSize = imageSize
|
||||
self.imageSpacing = imageSpacing
|
||||
self.borderWidthValue = borderWidth
|
||||
var filteredPeers = peers.map(PeerAvatarReference.init)
|
||||
if filteredPeers.count > 3 {
|
||||
filteredPeers = filteredPeers.dropLast(filteredPeers.count - 3)
|
||||
}
|
||||
if filteredPeers != self.peers {
|
||||
self.peers = filteredPeers
|
||||
|
||||
var validImageIds: [PeerId] = []
|
||||
for peer in filteredPeers {
|
||||
if case .image = peer {
|
||||
validImageIds.append(peer.peerId)
|
||||
}
|
||||
}
|
||||
|
||||
var removedImageIds: [PeerId] = []
|
||||
for (id, _) in self.images {
|
||||
if !validImageIds.contains(id) {
|
||||
removedImageIds.append(id)
|
||||
}
|
||||
}
|
||||
var removedDisposableIds: [PeerId] = []
|
||||
for (id, disposable) in self.disposables {
|
||||
if !validImageIds.contains(id) {
|
||||
disposable.dispose()
|
||||
removedDisposableIds.append(id)
|
||||
}
|
||||
}
|
||||
for id in removedImageIds {
|
||||
self.images.removeValue(forKey: id)
|
||||
}
|
||||
for id in removedDisposableIds {
|
||||
self.disposables.removeValue(forKey: id)
|
||||
}
|
||||
for peer in filteredPeers {
|
||||
switch peer {
|
||||
case let .image(peerReference, representation):
|
||||
if self.disposables[peer.peerId] == nil {
|
||||
if let signal = peerAvatarImage(account: context.account, peerReference: peerReference, authorOfMessage: nil, representation: representation, displayDimensions: CGSize(width: imageSize, height: imageSize), synchronousLoad: synchronousLoad) {
|
||||
let disposable = (signal
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] imageVersions in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let image = imageVersions?.0
|
||||
if let image = image {
|
||||
strongSelf.images[peer.peerId] = image
|
||||
strongSelf.setNeedsDisplay()
|
||||
}
|
||||
})
|
||||
self.disposables[peer.peerId] = disposable
|
||||
}
|
||||
}
|
||||
case .letters:
|
||||
break
|
||||
}
|
||||
}
|
||||
self.setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
override public func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol {
|
||||
return MergedAvatarsNodeArguments(peers: self.peers, images: self.images, imageSize: self.imageSize, imageSpacing: self.imageSpacing, borderWidth: self.borderWidthValue)
|
||||
}
|
||||
|
||||
@objc override public class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) {
|
||||
assertNotOnMainThread()
|
||||
|
||||
let context = UIGraphicsGetCurrentContext()!
|
||||
|
||||
if !isRasterizing {
|
||||
context.setBlendMode(.copy)
|
||||
context.setFillColor(UIColor.clear.cgColor)
|
||||
context.fill(bounds)
|
||||
}
|
||||
|
||||
guard let parameters = parameters as? MergedAvatarsNodeArguments else {
|
||||
return
|
||||
}
|
||||
|
||||
let mergedImageSize = parameters.imageSize
|
||||
let mergedImageSpacing = parameters.imageSpacing
|
||||
|
||||
var currentX = mergedImageSize + mergedImageSpacing * CGFloat(parameters.peers.count - 1) - mergedImageSize
|
||||
for i in (0 ..< parameters.peers.count).reversed() {
|
||||
let imageRect = CGRect(origin: CGPoint(x: currentX, y: 0.0), size: CGSize(width: mergedImageSize, height: mergedImageSize))
|
||||
context.setBlendMode(.copy)
|
||||
context.setFillColor(UIColor.clear.cgColor)
|
||||
context.fillEllipse(in: imageRect.insetBy(dx: -parameters.borderWidth, dy: -parameters.borderWidth))
|
||||
context.setBlendMode(.normal)
|
||||
|
||||
context.saveGState()
|
||||
switch parameters.peers[i] {
|
||||
case let .letters(peerId, nameColor, letters):
|
||||
context.translateBy(x: currentX, y: 0.0)
|
||||
drawPeerAvatarLetters(context: context, size: CGSize(width: mergedImageSize, height: mergedImageSize), font: avatarFont, letters: letters, peerId: peerId, nameColor: nameColor)
|
||||
context.translateBy(x: -currentX, y: 0.0)
|
||||
case .image:
|
||||
if let image = parameters.images[parameters.peers[i].peerId] {
|
||||
context.translateBy(x: imageRect.midX, y: imageRect.midY)
|
||||
context.scaleBy(x: 1.0, y: -1.0)
|
||||
context.translateBy(x: -imageRect.midX, y: -imageRect.midY)
|
||||
context.draw(image.cgImage!, in: imageRect)
|
||||
} else {
|
||||
context.setFillColor(UIColor.gray.cgColor)
|
||||
context.fillEllipse(in: imageRect)
|
||||
}
|
||||
}
|
||||
context.restoreGState()
|
||||
currentX -= mergedImageSpacing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user