Swiftgram/submodules/HorizontalPeerItem/Sources/HorizontalPeerItem.swift
2020-09-25 19:11:58 +03:00

247 lines
12 KiB
Swift

import Foundation
import UIKit
import Display
import Postbox
import AsyncDisplayKit
import TelegramCore
import SyncCore
import SwiftSignalKit
import TelegramPresentationData
import TelegramStringFormatting
import PeerOnlineMarkerNode
import SelectablePeerNode
import ContextUI
import AccountContext
public enum HorizontalPeerItemMode {
case list
case actionSheet
}
private let badgeFont = Font.regular(14.0)
public final class HorizontalPeerItem: ListViewItem {
let theme: PresentationTheme
let strings: PresentationStrings
let mode: HorizontalPeerItemMode
let context: AccountContext
public let peer: Peer
let action: (Peer) -> Void
let contextAction: (Peer, ASDisplayNode, ContextGesture?) -> Void
let isPeerSelected: (PeerId) -> Bool
let customWidth: CGFloat?
let presence: PeerPresence?
let unreadBadge: (Int32, Bool)?
public init(theme: PresentationTheme, strings: PresentationStrings, mode: HorizontalPeerItemMode, context: AccountContext, peer: Peer, presence: PeerPresence?, unreadBadge: (Int32, Bool)?, action: @escaping (Peer) -> Void, contextAction: @escaping (Peer, ASDisplayNode, ContextGesture?) -> Void, isPeerSelected: @escaping (PeerId) -> Bool, customWidth: CGFloat?) {
self.theme = theme
self.strings = strings
self.mode = mode
self.context = context
self.peer = peer
self.action = action
self.contextAction = contextAction
self.isPeerSelected = isPeerSelected
self.customWidth = customWidth
self.presence = presence
self.unreadBadge = unreadBadge
}
public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
async {
let node = HorizontalPeerItemNode()
let (nodeLayout, apply) = node.asyncLayout()(self, params)
node.insets = nodeLayout.insets
node.contentSize = nodeLayout.contentSize
Queue.mainQueue().async {
completion(node, {
return (nil, { _ in
apply(false, synchronousLoads)
})
})
}
}
}
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
Queue.mainQueue().async {
assert(node() is HorizontalPeerItemNode)
if let nodeValue = node() as? HorizontalPeerItemNode {
let layout = nodeValue.asyncLayout()
async {
let (nodeLayout, apply) = layout(self, params)
Queue.mainQueue().async {
completion(nodeLayout, { _ in
apply(animation.isAnimated, false)
})
}
}
}
}
}
}
public final class HorizontalPeerItemNode: ListViewItemNode {
private(set) var peerNode: SelectablePeerNode
let badgeBackgroundNode: ASImageNode
let badgeTextNode: TextNode
let onlineNode: PeerOnlineMarkerNode
public private(set) var item: HorizontalPeerItem?
public init() {
self.peerNode = SelectablePeerNode()
self.badgeBackgroundNode = ASImageNode()
self.badgeBackgroundNode.isLayerBacked = true
self.badgeBackgroundNode.displaysAsynchronously = false
self.badgeBackgroundNode.displayWithoutProcessing = true
self.badgeTextNode = TextNode()
self.badgeTextNode.isUserInteractionEnabled = false
self.badgeTextNode.displaysAsynchronously = true
self.onlineNode = PeerOnlineMarkerNode()
super.init(layerBacked: false, dynamicBounce: false)
self.addSubnode(self.peerNode)
self.addSubnode(self.badgeBackgroundNode)
self.addSubnode(self.badgeTextNode)
self.addSubnode(self.onlineNode)
self.peerNode.toggleSelection = { [weak self] in
if let item = self?.item {
item.action(item.peer)
}
}
self.peerNode.contextAction = { [weak self] node, gesture in
if let item = self?.item {
item.contextAction(item.peer, node, gesture)
}
}
}
override public func didLoad() {
super.didLoad()
self.layer.sublayerTransform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
}
public func asyncLayout() -> (HorizontalPeerItem, ListViewItemLayoutParams) -> (ListViewItemNodeLayout, (Bool, Bool) -> Void) {
let badgeTextLayout = TextNode.asyncLayout(self.badgeTextNode)
let onlineLayout = self.onlineNode.asyncLayout()
let currentItem = self.item
return { [weak self] item, params in
let itemLayout = ListViewItemNodeLayout(contentSize: CGSize(width: 92.0, height: item.customWidth ?? 80.0), insets: UIEdgeInsets())
let itemTheme: SelectablePeerNodeTheme
switch item.mode {
case .list:
itemTheme = SelectablePeerNodeTheme(textColor: item.theme.list.itemPrimaryTextColor, secretTextColor: item.theme.chatList.secretTitleColor, selectedTextColor: item.theme.list.itemAccentColor, checkBackgroundColor: item.theme.list.plainBackgroundColor, checkFillColor: item.theme.list.itemAccentColor, checkColor: item.theme.list.plainBackgroundColor, avatarPlaceholderColor: item.theme.list.mediaPlaceholderColor)
case .actionSheet:
itemTheme = SelectablePeerNodeTheme(textColor: item.theme.actionSheet.primaryTextColor, secretTextColor: item.theme.chatList.secretTitleColor, selectedTextColor: item.theme.actionSheet.controlAccentColor, checkBackgroundColor: item.theme.actionSheet.opaqueItemBackgroundColor, checkFillColor: item.theme.actionSheet.controlAccentColor, checkColor: item.theme.actionSheet.opaqueItemBackgroundColor, avatarPlaceholderColor: item.theme.list.mediaPlaceholderColor)
}
let currentBadgeBackgroundImage: UIImage?
let badgeAttributedString: NSAttributedString
if let unreadBadge = item.unreadBadge {
let badgeTextColor: UIColor
let (unreadCount, isMuted) = unreadBadge
if isMuted {
currentBadgeBackgroundImage = PresentationResourcesChatList.badgeBackgroundInactive(item.theme, diameter: 20.0)
badgeTextColor = item.theme.chatList.unreadBadgeInactiveTextColor
} else {
currentBadgeBackgroundImage = PresentationResourcesChatList.badgeBackgroundActive(item.theme, diameter: 20.0)
badgeTextColor = item.theme.chatList.unreadBadgeActiveTextColor
}
badgeAttributedString = NSAttributedString(string: unreadCount > 0 ? "\(unreadCount)" : " ", font: badgeFont, textColor: badgeTextColor)
} else {
currentBadgeBackgroundImage = nil
badgeAttributedString = NSAttributedString()
}
var online = false
let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970
if let peer = item.peer as? TelegramUser, let presence = item.presence as? TelegramUserPresence, !isServicePeer(peer) && !peer.flags.contains(.isSupport) {
let relativeStatus = relativeUserPresenceStatus(presence, relativeTo: Int32(timestamp))
if case .online = relativeStatus {
online = true
}
}
let (badgeLayout, badgeApply) = badgeTextLayout(TextNodeLayoutArguments(attributedString: badgeAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 50.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
var badgeSize: CGFloat = 0.0
if let currentBadgeBackgroundImage = currentBadgeBackgroundImage {
badgeSize += max(currentBadgeBackgroundImage.size.width, badgeLayout.size.width + 10.0) + 5.0
}
let (onlineLayout, onlineApply) = onlineLayout(online)
var animateContent = false
if let currentItem = currentItem, currentItem.peer.id == item.peer.id {
animateContent = true
}
return (itemLayout, { animated, synchronousLoads in
if let strongSelf = self {
strongSelf.item = item
strongSelf.peerNode.theme = itemTheme
strongSelf.peerNode.setup(context: item.context, theme: item.theme, strings: item.strings, peer: RenderedPeer(peer: item.peer), numberOfLines: 1, synchronousLoad: synchronousLoads)
strongSelf.peerNode.frame = CGRect(origin: CGPoint(), size: itemLayout.size)
strongSelf.peerNode.updateSelection(selected: item.isPeerSelected(item.peer.id), animated: false)
let badgeBackgroundWidth: CGFloat
if let currentBadgeBackgroundImage = currentBadgeBackgroundImage {
strongSelf.badgeBackgroundNode.image = currentBadgeBackgroundImage
strongSelf.badgeBackgroundNode.isHidden = false
badgeBackgroundWidth = max(badgeLayout.size.width + 10.0, currentBadgeBackgroundImage.size.width)
let badgeBackgroundFrame = CGRect(x: itemLayout.size.width - floorToScreenPixels(badgeBackgroundWidth * 1.8), y: 2.0, width: badgeBackgroundWidth, height: currentBadgeBackgroundImage.size.height)
let badgeTextFrame = CGRect(origin: CGPoint(x: badgeBackgroundFrame.midX - badgeLayout.size.width / 2.0, y: badgeBackgroundFrame.minY + 2.0), size: badgeLayout.size)
strongSelf.badgeTextNode.frame = badgeTextFrame
strongSelf.badgeBackgroundNode.frame = badgeBackgroundFrame
} else {
badgeBackgroundWidth = 0.0
strongSelf.badgeBackgroundNode.image = nil
strongSelf.badgeBackgroundNode.isHidden = true
}
strongSelf.onlineNode.setImage(PresentationResourcesChatList.recentStatusOnlineIcon(item.theme, state: .regular))
strongSelf.onlineNode.frame = CGRect(x: itemLayout.size.width - onlineLayout.width - 18.0, y: itemLayout.size.height - onlineLayout.height - 18.0, width: onlineLayout.width, height: onlineLayout.height)
let _ = badgeApply()
let _ = onlineApply(animateContent)
}
})
}
}
public func updateSelection(animated: Bool) {
if let item = self.item {
self.peerNode.updateSelection(selected: item.isPeerSelected(item.peer.id), animated: animated)
}
}
override public func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
super.animateInsertion(currentTimestamp, duration: duration, short: short)
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
override public func animateRemoved(_ currentTimestamp: Double, duration: Double) {
super.animateRemoved(currentTimestamp, duration: duration)
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
}
override public func animateAdded(_ currentTimestamp: Double, duration: Double) {
super.animateAdded(currentTimestamp, duration: duration)
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
}