Swiftgram/submodules/InviteLinksUI/Sources/ItemListPermanentInviteLinkItem.swift
2025-04-09 18:21:11 +04:00

785 lines
45 KiB
Swift

import Foundation
import UIKit
import Display
import AsyncDisplayKit
import SwiftSignalKit
import AccountContext
import TelegramPresentationData
import ItemListUI
import SolidRoundedButtonNode
import AnimatedAvatarSetNode
import ShimmerEffect
import TelegramCore
import Markdown
import TextFormat
import ComponentFlow
import MultilineTextComponent
import TextNodeWithEntities
private func actionButtonImage(color: UIColor) -> UIImage? {
return generateImage(CGSize(width: 24.0, height: 24.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(color.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
context.setBlendMode(.clear)
context.fillEllipse(in: CGRect(origin: CGPoint(x: 4.0, y: 10.0), size: CGSize(width: 4.0, height: 4.0)))
context.fillEllipse(in: CGRect(origin: CGPoint(x: 10.0, y: 10.0), size: CGSize(width: 4.0, height: 4.0)))
context.fillEllipse(in: CGRect(origin: CGPoint(x: 16.0, y: 10.0), size: CGSize(width: 4.0, height: 4.0)))
})
}
public class ItemListPermanentInviteLinkItem: ListViewItem, ItemListItem {
let context: AccountContext
let presentationData: ItemListPresentationData
let invite: ExportedInvitation?
let count: Int32
let peers: [EnginePeer]
let displayButton: Bool
let separateButtons: Bool
let displayImporters: Bool
let isCall: Bool
let buttonColor: UIColor?
public let sectionId: ItemListSectionId
let style: ItemListStyle
let copyAction: (() -> Void)?
let shareAction: (() -> Void)?
let contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?
let viewAction: (() -> Void)?
let openCallAction: (() -> Void)?
public let tag: ItemListItemTag?
public init(
context: AccountContext,
presentationData: ItemListPresentationData,
invite: ExportedInvitation?,
count: Int32,
peers: [EnginePeer],
displayButton: Bool,
separateButtons: Bool = false,
displayImporters: Bool,
isCall: Bool = false,
buttonColor: UIColor?,
sectionId: ItemListSectionId,
style: ItemListStyle,
copyAction: (() -> Void)?,
shareAction: (() -> Void)?,
contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?,
viewAction: (() -> Void)?,
openCallAction: (() -> Void)?,
tag: ItemListItemTag? = nil
) {
self.context = context
self.presentationData = presentationData
self.invite = invite
self.count = count
self.peers = peers
self.displayButton = displayButton
self.separateButtons = separateButtons
self.displayImporters = displayImporters
self.isCall = isCall
self.buttonColor = buttonColor
self.sectionId = sectionId
self.style = style
self.copyAction = copyAction
self.shareAction = shareAction
self.contextAction = contextAction
self.viewAction = viewAction
self.openCallAction = openCallAction
self.tag = tag
}
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 = ItemListPermanentInviteLinkItemNode()
let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
node.contentSize = layout.contentSize
node.insets = layout.insets
Queue.mainQueue().async {
completion(node, {
return (nil, { _ in apply() })
})
}
}
}
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 {
if let nodeValue = node() as? ItemListPermanentInviteLinkItemNode {
let makeLayout = nodeValue.asyncLayout()
async {
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
Queue.mainQueue().async {
completion(layout, { _ in
apply()
})
}
}
}
}
}
public var selectable: Bool = false
}
public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItemNode {
private let backgroundNode: ASDisplayNode
private let topStripeNode: ASDisplayNode
private let bottomStripeNode: ASDisplayNode
private let maskNode: ASImageNode
private let fieldNode: ASImageNode
private let addressNode: TextNode
private let fieldButtonNode: HighlightTrackingButtonNode
private let referenceContainerNode: ContextReferenceContentNode
private let containerNode: ContextControllerSourceNode
private let addressButtonNode: HighlightTrackingButtonNode
private let addressButtonIconNode: ASImageNode
private var addressShimmerNode: ShimmerEffectNode?
private var copyButtonNode: SolidRoundedButtonNode?
private var shareButtonNode: SolidRoundedButtonNode?
private let avatarsButtonNode: HighlightTrackingButtonNode
private let avatarsContext: AnimatedAvatarSetContext
private var avatarsContent: AnimatedAvatarSetContext.Content?
private let avatarsNode: AnimatedAvatarSetNode
private let invitedPeersNode: TextNode
private var shimmerNode: ShimmerEffectNode?
private var absoluteLocation: (CGRect, CGSize)?
private var justCreatedCallTextNode: TextNodeWithEntities?
private var justCreatedCallLeftSeparatorLayer: SimpleLayer?
private var justCreatedCallRightSeparatorLayer: SimpleLayer?
private var justCreatedCallSeparatorText: ComponentView<Empty>?
private let activateArea: AccessibilityAreaNode
private var item: ItemListPermanentInviteLinkItem?
override public var canBeSelected: Bool {
return false
}
public var tag: ItemListItemTag? {
return self.item?.tag
}
public init() {
self.backgroundNode = ASDisplayNode()
self.backgroundNode.isLayerBacked = true
self.backgroundNode.backgroundColor = .white
self.maskNode = ASImageNode()
self.topStripeNode = ASDisplayNode()
self.topStripeNode.isLayerBacked = true
self.bottomStripeNode = ASDisplayNode()
self.bottomStripeNode.isLayerBacked = true
self.fieldNode = ASImageNode()
self.fieldNode.displaysAsynchronously = false
self.fieldNode.displayWithoutProcessing = true
self.addressNode = TextNode()
self.addressNode.isUserInteractionEnabled = false
self.fieldButtonNode = HighlightTrackingButtonNode()
self.containerNode = ContextControllerSourceNode()
self.containerNode.animateScale = false
self.referenceContainerNode = ContextReferenceContentNode()
self.addressButtonNode = HighlightTrackingButtonNode()
self.addressButtonIconNode = ASImageNode()
self.addressButtonIconNode.contentMode = .center
self.addressButtonIconNode.displaysAsynchronously = false
self.addressButtonIconNode.displayWithoutProcessing = true
self.avatarsButtonNode = HighlightTrackingButtonNode()
self.avatarsContext = AnimatedAvatarSetContext()
self.avatarsNode = AnimatedAvatarSetNode()
self.invitedPeersNode = TextNode()
self.activateArea = AccessibilityAreaNode()
super.init(layerBacked: false, dynamicBounce: false)
self.addSubnode(self.fieldNode)
self.addSubnode(self.addressNode)
self.addSubnode(self.fieldButtonNode)
self.addSubnode(self.avatarsNode)
self.addSubnode(self.invitedPeersNode)
self.addSubnode(self.avatarsButtonNode)
self.containerNode.addSubnode(self.referenceContainerNode)
self.referenceContainerNode.addSubnode(self.addressButtonIconNode)
self.referenceContainerNode.addSubnode(self.addressButtonNode)
self.addSubnode(self.containerNode)
self.addSubnode(self.activateArea)
self.containerNode.activated = { [weak self] gesture, _ in
if let strongSelf = self, let item = strongSelf.item {
item.contextAction?(strongSelf.referenceContainerNode, gesture)
}
}
self.fieldButtonNode.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self {
if highlighted {
strongSelf.addressNode.layer.removeAnimation(forKey: "opacity")
strongSelf.addressNode.alpha = 0.4
} else {
strongSelf.addressNode.alpha = 1.0
strongSelf.addressNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
}
}
}
self.fieldButtonNode.addTarget(self, action: #selector(self.fieldButtonPressed), forControlEvents: .touchUpInside)
self.addressButtonNode.addTarget(self, action: #selector(self.addressButtonPressed), forControlEvents: .touchUpInside)
self.addressButtonNode.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self {
if highlighted {
strongSelf.addressButtonIconNode.layer.removeAnimation(forKey: "opacity")
strongSelf.addressButtonIconNode.alpha = 0.4
} else {
strongSelf.addressButtonIconNode.alpha = 1.0
strongSelf.addressButtonIconNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
}
}
}
self.copyButtonNode?.pressed = { [weak self] in
if let strongSelf = self, let item = strongSelf.item {
item.copyAction?()
}
}
self.shareButtonNode?.pressed = { [weak self] in
if let strongSelf = self, let item = strongSelf.item {
item.shareAction?()
}
}
self.avatarsButtonNode.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self {
if highlighted {
strongSelf.avatarsNode.layer.removeAnimation(forKey: "opacity")
strongSelf.invitedPeersNode.layer.removeAnimation(forKey: "opacity")
strongSelf.avatarsNode.alpha = 0.4
strongSelf.invitedPeersNode.alpha = 0.4
} else {
strongSelf.avatarsNode.alpha = 1.0
strongSelf.invitedPeersNode.alpha = 1.0
strongSelf.avatarsNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
strongSelf.invitedPeersNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
}
}
}
self.avatarsButtonNode.addTarget(self, action: #selector(self.avatarsButtonPressed), forControlEvents: .touchUpInside)
}
@objc private func fieldButtonPressed() {
if let item = self.item {
item.copyAction?()
}
}
@objc private func addressButtonPressed() {
if let item = self.item {
item.contextAction?(self.referenceContainerNode, nil)
}
}
@objc private func avatarsButtonPressed() {
if let item = self.item {
item.viewAction?()
}
}
@objc private func justCreatedCallTextTap(_ recognizer: UITapGestureRecognizer) {
if case .ended = recognizer.state {
self.item?.openCallAction?()
}
}
public func asyncLayout() -> (_ item: ItemListPermanentInviteLinkItem, _ params: ListViewItemLayoutParams, _ insets: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
let makeAddressLayout = TextNode.asyncLayout(self.addressNode)
let makeInvitedPeersLayout = TextNode.asyncLayout(self.invitedPeersNode)
let makeJustCreatedCallTextNodeLayout = TextNodeWithEntities.asyncLayout(self.justCreatedCallTextNode)
let currentItem = self.item
let avatarsContext = self.avatarsContext
return { item, params, neighbors in
var updatedTheme: PresentationTheme?
if currentItem?.presentationData.theme !== item.presentationData.theme {
updatedTheme = item.presentationData.theme
}
let contentSize: CGSize
let insets: UIEdgeInsets
let separatorHeight = UIScreenPixel
let itemBackgroundColor: UIColor
let itemSeparatorColor: UIColor
let leftInset = 16.0 + params.leftInset
let rightInset = 16.0 + params.rightInset
let titleColor: UIColor
titleColor = item.presentationData.theme.list.itemInputField.primaryColor
let alignCentrally = !(item.invite?.link?.contains("joinchat") ?? true)
let addressFont = Font.regular(!alignCentrally && params.width == 320 ? floor(item.presentationData.fontSize.itemListBaseFontSize * 15.0 / 17.0) : item.presentationData.fontSize.itemListBaseFontSize)
let titleFont = Font.regular(item.presentationData.fontSize.itemListBaseFontSize)
let constrainedWidth = alignCentrally ? params.width - leftInset - rightInset - 90.0 : params.width - leftInset - rightInset - 60.0
let (addressLayout, addressApply) = makeAddressLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.invite.flatMap({ $0.link?.replacingOccurrences(of: "https://", with: "") }) ?? "", font: addressFont, textColor: titleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .middle, constrainedSize: CGSize(width: constrainedWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let subtitle: String
let subtitleColor: UIColor
if item.count > 0 {
subtitle = item.presentationData.strings.InviteLink_PeopleJoined(item.count)
subtitleColor = item.presentationData.theme.list.itemAccentColor
} else {
subtitle = item.presentationData.strings.InviteLink_PeopleJoinedNone
subtitleColor = item.presentationData.theme.list.itemSecondaryTextColor
}
let (invitedPeersLayout, invitedPeersApply) = makeInvitedPeersLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: subtitle, font: titleFont, textColor: subtitleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - 20.0 - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
var justCreatedCallTextNodeLayout: (TextNodeLayout, (TextNodeWithEntities.Arguments?) -> TextNodeWithEntities?)?
if item.isCall {
let chevronImage = generateTintedImage(image: UIImage(bundleImageName: "Contact List/SubtitleArrow"), color: item.presentationData.theme.list.itemAccentColor)
let textFont = Font.regular(15.0)
let boldTextFont = Font.semibold(15.0)
let textColor = item.presentationData.theme.list.itemPrimaryTextColor
let accentColor = item.presentationData.theme.list.itemAccentColor
let markdownAttributes = MarkdownAttributes(
body: MarkdownAttributeSet(font: textFont, textColor: textColor),
bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor),
link: MarkdownAttributeSet(font: textFont, textColor: accentColor),
linkAttribute: { contents in
return (TelegramTextAttributes.URL, contents)
}
)
let justCreatedCallTextAttributedString = parseMarkdownIntoAttributedString(item.presentationData.strings.InviteLink_CreatedGroupCallFooter, attributes: markdownAttributes).mutableCopy() as! NSMutableAttributedString
if let range = justCreatedCallTextAttributedString.string.range(of: ">"), let chevronImage {
justCreatedCallTextAttributedString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: justCreatedCallTextAttributedString.string))
}
justCreatedCallTextNodeLayout = makeJustCreatedCallTextNodeLayout(TextNodeLayoutArguments(
attributedString: justCreatedCallTextAttributedString,
backgroundColor: nil,
maximumNumberOfLines: 0,
truncationType: .end,
constrainedSize: CGSize(width: params.width - params.rightInset - 20.0 - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude),
alignment: .center,
lineSpacing: 0.28,
cutout: nil,
insets: UIEdgeInsets()
))
}
let avatarsContent = avatarsContext.update(peers: item.peers, animated: false)
let verticalInset: CGFloat = 16.0
let fieldHeight: CGFloat = 52.0
let fieldSpacing: CGFloat = 16.0
let buttonHeight: CGFloat = 50.0
let justCreatedCallSeparatorSpacing: CGFloat = 16.0
let justCreatedCallTextSpacing: CGFloat = 45.0
var height = verticalInset * 2.0 + fieldHeight + fieldSpacing + buttonHeight + 54.0
if let justCreatedCallTextNodeLayout {
height += justCreatedCallTextSpacing - 2.0
height += justCreatedCallTextNodeLayout.0.size.height
}
switch item.style {
case .plain:
itemBackgroundColor = item.presentationData.theme.list.plainBackgroundColor
itemSeparatorColor = .clear
insets = UIEdgeInsets()
case .blocks:
itemBackgroundColor = item.presentationData.theme.list.itemBlocksBackgroundColor
itemSeparatorColor = item.presentationData.theme.list.itemBlocksSeparatorColor
insets = itemListNeighborsGroupedInsets(neighbors, params)
}
if !item.displayImporters {
height -= 57.0
}
if !item.displayButton {
height -= 63.0
}
contentSize = CGSize(width: params.width, height: height)
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
return (ListViewItemNodeLayout(contentSize: contentSize, insets: insets), { [weak self] in
if let strongSelf = self {
strongSelf.item = item
strongSelf.avatarsContent = avatarsContent
strongSelf.activateArea.frame = CGRect(origin: CGPoint(x: params.leftInset, y: 0.0), size: CGSize(width: params.width - params.leftInset - params.rightInset, height: layout.contentSize.height))
// strongSelf.activateArea.accessibilityLabel = item.title
// strongSelf.activateArea.accessibilityValue = item.label
strongSelf.activateArea.accessibilityTraits = []
if let _ = updatedTheme {
strongSelf.topStripeNode.backgroundColor = itemSeparatorColor
strongSelf.bottomStripeNode.backgroundColor = itemSeparatorColor
strongSelf.backgroundNode.backgroundColor = itemBackgroundColor
strongSelf.fieldNode.image = generateStretchableFilledCircleImage(diameter: 18.0, color: item.presentationData.theme.list.itemInputField.backgroundColor)
strongSelf.addressButtonIconNode.image = actionButtonImage(color: item.presentationData.theme.list.itemInputField.controlColor)
}
let _ = addressApply()
let _ = invitedPeersApply()
switch item.style {
case .plain:
if strongSelf.backgroundNode.supernode != nil {
strongSelf.backgroundNode.removeFromSupernode()
}
if strongSelf.topStripeNode.supernode != nil {
strongSelf.topStripeNode.removeFromSupernode()
}
if strongSelf.bottomStripeNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 0)
}
if strongSelf.maskNode.supernode != nil {
strongSelf.maskNode.removeFromSupernode()
}
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: leftInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - leftInset, height: separatorHeight))
case .blocks:
if strongSelf.backgroundNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
}
if strongSelf.topStripeNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.topStripeNode, at: 1)
}
if strongSelf.bottomStripeNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2)
}
if strongSelf.maskNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.maskNode, at: 3)
}
let hasCorners = itemListHasRoundedBlockLayout(params)
var hasTopCorners = false
var hasBottomCorners = false
switch neighbors.top {
case .sameSection(false):
strongSelf.topStripeNode.isHidden = true
default:
hasTopCorners = true
strongSelf.topStripeNode.isHidden = hasCorners
}
let bottomStripeInset: CGFloat
switch neighbors.bottom {
case .sameSection(false):
bottomStripeInset = leftInset
strongSelf.bottomStripeNode.isHidden = false
default:
bottomStripeInset = 0.0
hasBottomCorners = true
strongSelf.bottomStripeNode.isHidden = hasCorners
}
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight))
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight))
}
let fieldFrame = CGRect(origin: CGPoint(x: leftInset, y: verticalInset), size: CGSize(width: params.width - leftInset - rightInset, height: fieldHeight))
strongSelf.fieldNode.frame = fieldFrame
strongSelf.fieldButtonNode.frame = fieldFrame
strongSelf.addressNode.frame = CGRect(origin: CGPoint(x: fieldFrame.minX + (alignCentrally ? floorToScreenPixels((fieldFrame.width - addressLayout.size.width) / 2.0) : 14.0), y: fieldFrame.minY + floorToScreenPixels((fieldFrame.height - addressLayout.size.height) / 2.0) + 1.0), size: addressLayout.size)
strongSelf.containerNode.frame = CGRect(origin: CGPoint(x: params.width - rightInset - 38.0 - 14.0, y: verticalInset), size: CGSize(width: 52.0, height: 52.0))
strongSelf.addressButtonNode.frame = strongSelf.containerNode.bounds
strongSelf.referenceContainerNode.frame = strongSelf.containerNode.bounds
strongSelf.addressButtonIconNode.frame = strongSelf.containerNode.bounds
strongSelf.addressButtonNode.isHidden = item.contextAction == nil
strongSelf.addressButtonIconNode.isHidden = item.contextAction == nil
var effectiveSeparateButtons = item.separateButtons
if let invite = item.invite, invitationAvailability(invite).isZero {
effectiveSeparateButtons = false
}
let copyButtonNode: SolidRoundedButtonNode
if let currentCopyButtonNode = strongSelf.copyButtonNode {
copyButtonNode = currentCopyButtonNode
} else {
let buttonTheme: SolidRoundedButtonTheme
if let buttonColor = item.buttonColor {
buttonTheme = SolidRoundedButtonTheme(backgroundColor: buttonColor, foregroundColor: item.presentationData.theme.list.itemCheckColors.foregroundColor)
} else {
buttonTheme = SolidRoundedButtonTheme(theme: item.presentationData.theme)
}
copyButtonNode = SolidRoundedButtonNode(theme: buttonTheme, height: 50.0, cornerRadius: 11.0)
copyButtonNode.title = item.presentationData.strings.InviteLink_CopyShort
copyButtonNode.pressed = { [weak self] in
self?.item?.copyAction?()
}
strongSelf.addSubnode(copyButtonNode)
strongSelf.copyButtonNode = copyButtonNode
}
let shareButtonNode: SolidRoundedButtonNode
if let currentShareButtonNode = strongSelf.shareButtonNode {
shareButtonNode = currentShareButtonNode
} else {
let buttonTheme: SolidRoundedButtonTheme
if let buttonColor = item.buttonColor {
buttonTheme = SolidRoundedButtonTheme(backgroundColor: buttonColor, foregroundColor: item.presentationData.theme.list.itemCheckColors.foregroundColor)
} else {
buttonTheme = SolidRoundedButtonTheme(theme: item.presentationData.theme)
}
shareButtonNode = SolidRoundedButtonNode(theme: buttonTheme, height: 50.0, cornerRadius: 11.0)
if let invite = item.invite, invitationAvailability(invite).isZero {
shareButtonNode.title = item.presentationData.strings.InviteLink_ReactivateLink
} else {
shareButtonNode.title = effectiveSeparateButtons ? item.presentationData.strings.InviteLink_ShareShort : item.presentationData.strings.InviteLink_Share
}
shareButtonNode.pressed = { [weak self] in
self?.item?.shareAction?()
}
strongSelf.addSubnode(shareButtonNode)
strongSelf.shareButtonNode = shareButtonNode
}
let buttonSpacing: CGFloat = 8.0
var buttonWidth = contentSize.width - leftInset - rightInset
var shareButtonOriginX = leftInset
if effectiveSeparateButtons {
buttonWidth = (buttonWidth - buttonSpacing) / 2.0
shareButtonOriginX = leftInset + buttonWidth + buttonSpacing
}
let _ = copyButtonNode.updateLayout(width: buttonWidth, transition: .immediate)
copyButtonNode.frame = CGRect(x: leftInset, y: verticalInset + fieldHeight + fieldSpacing, width: buttonWidth, height: buttonHeight)
let _ = shareButtonNode.updateLayout(width: buttonWidth, transition: .immediate)
shareButtonNode.frame = CGRect(x: shareButtonOriginX, y: verticalInset + fieldHeight + fieldSpacing, width: buttonWidth, height: buttonHeight)
if let justCreatedCallTextNodeLayout {
if let justCreatedCallTextNode = justCreatedCallTextNodeLayout.1(TextNodeWithEntities.Arguments(
context: item.context,
cache: item.context.animationCache,
renderer: item.context.animationRenderer,
placeholderColor: .gray,
attemptSynchronous: true
)) {
if strongSelf.justCreatedCallTextNode !== justCreatedCallTextNode {
strongSelf.justCreatedCallTextNode?.textNode.removeFromSupernode()
strongSelf.justCreatedCallTextNode = justCreatedCallTextNode
strongSelf.addSubnode(justCreatedCallTextNode.textNode)
justCreatedCallTextNode.textNode.view.addGestureRecognizer(UITapGestureRecognizer(target: strongSelf, action: #selector(strongSelf.justCreatedCallTextTap(_:))))
}
let justCreatedCallTextNodeFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((params.width - justCreatedCallTextNodeLayout.0.size.width) / 2.0), y: shareButtonNode.frame.maxY + justCreatedCallTextSpacing), size: CGSize(width: justCreatedCallTextNodeLayout.0.size.width, height: justCreatedCallTextNodeLayout.0.size.height))
justCreatedCallTextNode.textNode.frame = justCreatedCallTextNodeFrame
let justCreatedCallSeparatorText: ComponentView<Empty>
if let current = strongSelf.justCreatedCallSeparatorText {
justCreatedCallSeparatorText = current
} else {
justCreatedCallSeparatorText = ComponentView()
strongSelf.justCreatedCallSeparatorText = justCreatedCallSeparatorText
}
let justCreatedCallLeftSeparatorLayer: SimpleLayer
if let current = strongSelf.justCreatedCallLeftSeparatorLayer {
justCreatedCallLeftSeparatorLayer = current
} else {
justCreatedCallLeftSeparatorLayer = SimpleLayer()
strongSelf.justCreatedCallLeftSeparatorLayer = justCreatedCallLeftSeparatorLayer
strongSelf.layer.addSublayer(justCreatedCallLeftSeparatorLayer)
}
let justCreatedCallRightSeparatorLayer: SimpleLayer
if let current = strongSelf.justCreatedCallRightSeparatorLayer {
justCreatedCallRightSeparatorLayer = current
} else {
justCreatedCallRightSeparatorLayer = SimpleLayer()
strongSelf.justCreatedCallRightSeparatorLayer = justCreatedCallRightSeparatorLayer
strongSelf.layer.addSublayer(justCreatedCallRightSeparatorLayer)
}
justCreatedCallLeftSeparatorLayer.backgroundColor = item.presentationData.theme.list.itemPlainSeparatorColor.cgColor
justCreatedCallRightSeparatorLayer.backgroundColor = item.presentationData.theme.list.itemPlainSeparatorColor.cgColor
let justCreatedCallSeparatorTextSize = justCreatedCallSeparatorText.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: item.presentationData.strings.SendInviteLink_PremiumOrSendSectionSeparator, font: Font.regular(15.0), textColor: item.presentationData.theme.list.itemSecondaryTextColor))
)),
environment: {},
containerSize: CGSize(width: params.width - leftInset - rightInset, height: 100.0)
)
let justCreatedCallSeparatorTextFrame = CGRect(origin: CGPoint(x: floor((params.width - justCreatedCallSeparatorTextSize.width) * 0.5), y: shareButtonNode.frame.maxY + justCreatedCallSeparatorSpacing), size: justCreatedCallSeparatorTextSize)
if let justCreatedCallSeparatorTextView = justCreatedCallSeparatorText.view {
if justCreatedCallSeparatorTextView.superview == nil {
strongSelf.view.addSubview(justCreatedCallSeparatorTextView)
}
justCreatedCallSeparatorTextView.frame = justCreatedCallSeparatorTextFrame
}
let separatorWidth: CGFloat = 72.0
let separatorSpacing: CGFloat = 10.0
justCreatedCallLeftSeparatorLayer.frame = CGRect(origin: CGPoint(x: justCreatedCallSeparatorTextFrame.minX - separatorSpacing - separatorWidth, y: justCreatedCallSeparatorTextFrame.midY + 1.0), size: CGSize(width: separatorWidth, height: UIScreenPixel))
justCreatedCallRightSeparatorLayer.frame = CGRect(origin: CGPoint(x: justCreatedCallSeparatorTextFrame.maxX + separatorSpacing, y: justCreatedCallSeparatorTextFrame.midY + 1.0), size: CGSize(width: separatorWidth, height: UIScreenPixel))
}
} else if let justCreatedCallTextNode = strongSelf.justCreatedCallTextNode {
strongSelf.justCreatedCallTextNode = nil
justCreatedCallTextNode.textNode.removeFromSupernode()
strongSelf.justCreatedCallLeftSeparatorLayer?.removeFromSuperlayer()
strongSelf.justCreatedCallLeftSeparatorLayer = nil
strongSelf.justCreatedCallRightSeparatorLayer?.removeFromSuperlayer()
strongSelf.justCreatedCallRightSeparatorLayer = nil
strongSelf.justCreatedCallSeparatorText?.view?.removeFromSuperview()
strongSelf.justCreatedCallSeparatorText = nil
}
var totalWidth = invitedPeersLayout.size.width
var leftOrigin: CGFloat = floorToScreenPixels((params.width - invitedPeersLayout.size.width) / 2.0)
let avatarSpacing: CGFloat = 21.0
if let avatarsContent = strongSelf.avatarsContent {
let avatarsSize = strongSelf.avatarsNode.update(context: item.context, content: avatarsContent, itemSize: CGSize(width: 32.0, height: 32.0), animated: true, synchronousLoad: true)
if !avatarsSize.width.isZero {
totalWidth += avatarsSize.width + avatarSpacing
}
let avatarsNodeFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((params.width - totalWidth) / 2.0), y: fieldFrame.maxY + 87.0), size: avatarsSize)
strongSelf.avatarsNode.frame = avatarsNodeFrame
if !avatarsSize.width.isZero {
leftOrigin = avatarsNodeFrame.maxX + avatarSpacing
}
}
strongSelf.invitedPeersNode.frame = CGRect(origin: CGPoint(x: leftOrigin, y: fieldFrame.maxY + 92.0), size: invitedPeersLayout.size)
strongSelf.avatarsButtonNode.frame = CGRect(x: floorToScreenPixels((params.width - totalWidth) / 2.0), y: fieldFrame.maxY + 87.0, width: totalWidth, height: 32.0)
strongSelf.avatarsButtonNode.isUserInteractionEnabled = !item.peers.isEmpty && item.invite != nil
strongSelf.addressButtonNode.isUserInteractionEnabled = item.invite != nil
strongSelf.fieldButtonNode.isUserInteractionEnabled = item.invite != nil
strongSelf.addressButtonIconNode.alpha = item.invite != nil ? 1.0 : 0.0
strongSelf.copyButtonNode?.isUserInteractionEnabled = item.invite != nil
strongSelf.copyButtonNode?.alpha = item.invite != nil ? 1.0 : 0.4
strongSelf.copyButtonNode?.isHidden = !item.displayButton || !effectiveSeparateButtons
strongSelf.shareButtonNode?.isUserInteractionEnabled = item.invite != nil
strongSelf.shareButtonNode?.alpha = item.invite != nil ? 1.0 : 0.4
strongSelf.shareButtonNode?.isHidden = !item.displayButton
strongSelf.avatarsButtonNode.isHidden = !item.displayImporters
strongSelf.avatarsNode.isHidden = !item.displayImporters || item.invite == nil
strongSelf.invitedPeersNode.isHidden = !item.displayImporters || item.invite == nil
if item.invite == nil {
let shimmerNode: ShimmerEffectNode
if let current = strongSelf.shimmerNode {
shimmerNode = current
} else {
shimmerNode = ShimmerEffectNode()
strongSelf.shimmerNode = shimmerNode
strongSelf.insertSubnode(shimmerNode, belowSubnode: strongSelf.fieldNode)
}
shimmerNode.frame = CGRect(origin: CGPoint(), size: layout.contentSize)
if let (rect, size) = strongSelf.absoluteLocation {
shimmerNode.updateAbsoluteRect(rect, within: size)
}
let lineWidth: CGFloat = 180.0
let lineDiameter: CGFloat = 12.0
let titleFrame = strongSelf.invitedPeersNode.frame
var shapes: [ShimmerEffectNode.Shape] = []
shapes.append(.roundedRectLine(startPoint: CGPoint(x: floor(titleFrame.center.x - lineWidth / 2.0), y: titleFrame.minY + floor((titleFrame.height - lineDiameter) / 2.0)), width: lineWidth, diameter: lineDiameter))
shimmerNode.update(backgroundColor: item.presentationData.theme.list.itemBlocksBackgroundColor, foregroundColor: item.presentationData.theme.list.mediaPlaceholderColor, shimmeringColor: item.presentationData.theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4), shapes: shapes, size: layout.contentSize)
let addressShimmerNode: ShimmerEffectNode
if let current = strongSelf.addressShimmerNode {
addressShimmerNode = current
} else {
addressShimmerNode = ShimmerEffectNode()
strongSelf.addressShimmerNode = addressShimmerNode
strongSelf.insertSubnode(addressShimmerNode, aboveSubnode: strongSelf.fieldNode)
}
addressShimmerNode.frame = strongSelf.fieldNode.frame.insetBy(dx: 18.0, dy: 0.0)
if let (rect, size) = strongSelf.absoluteLocation {
addressShimmerNode.updateAbsoluteRect(CGRect(x: rect.minX + strongSelf.fieldNode.frame.minX + 18.0, y: rect.minY + strongSelf.fieldNode.frame.minY, width: strongSelf.fieldNode.frame.width - 18.0 * 2.0, height: strongSelf.fieldNode.frame.height), within: size)
}
let addressLineWidth: CGFloat = strongSelf.fieldNode.frame.width - 100.0
var addressShapes: [ShimmerEffectNode.Shape] = []
addressShapes.append(.roundedRectLine(startPoint: CGPoint(x: floor(addressShimmerNode.frame.width / 2.0 - addressLineWidth / 2.0), y: 16.0 + floor((22.0 - lineDiameter) / 2.0)), width: addressLineWidth, diameter: lineDiameter))
addressShimmerNode.update(backgroundColor: item.presentationData.theme.list.itemInputField.backgroundColor, foregroundColor: item.presentationData.theme.list.itemInputField.controlColor.mixedWith(item.presentationData.theme.list.itemInputField.backgroundColor, alpha: 0.7), shimmeringColor: item.presentationData.theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4), shapes: addressShapes, size: addressShimmerNode.frame.size)
} else {
if let shimmerNode = strongSelf.shimmerNode {
strongSelf.shimmerNode = nil
shimmerNode.removeFromSupernode()
}
if let shimmerNode = strongSelf.addressShimmerNode {
strongSelf.shimmerNode = nil
shimmerNode.removeFromSupernode()
}
}
}
})
}
}
override public func animateInsertion(_ currentTimestamp: Double, duration: Double, options: ListViewItemAnimationOptions) {
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
}
override public func animateAdded(_ currentTimestamp: Double, duration: Double) {
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
override public func animateRemoved(_ currentTimestamp: Double, duration: Double) {
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
}
override public func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
var rect = rect
rect.origin.y += self.insets.top
self.absoluteLocation = (rect, containerSize)
if let shimmerNode = self.addressShimmerNode {
shimmerNode.updateAbsoluteRect(CGRect(x: rect.minX + self.fieldNode.frame.minX + 18.0, y: rect.minY + self.fieldNode.frame.minY, width: self.fieldNode.frame.width - 18.0 * 2.0, height: self.fieldNode.frame.height), within: containerSize)
}
if let shimmerNode = self.shimmerNode {
shimmerNode.updateAbsoluteRect(rect, within: containerSize)
}
}
}