Swiftgram/submodules/SelectablePeerNode/Sources/SelectablePeerNode.swift
2025-05-21 18:35:07 +03:00

519 lines
27 KiB
Swift

import Foundation
import UIKit
import AsyncDisplayKit
import Display
import TelegramCore
import Postbox
import SwiftSignalKit
import TelegramPresentationData
import AvatarNode
import PeerOnlineMarkerNode
import LegacyComponents
import ContextUI
import LocalizedPeerData
import AccountContext
import CheckNode
import ComponentFlow
import EmojiStatusComponent
import AnimationCache
import MultiAnimationRenderer
import TelegramUIPreferences
private let avatarFont = avatarPlaceholderFont(size: 24.0)
private let textFont = Font.regular(11.0)
public final class SelectablePeerNodeTheme {
let textColor: UIColor
let secretTextColor: UIColor
let selectedTextColor: UIColor
let checkBackgroundColor: UIColor
let checkFillColor: UIColor
let checkColor: UIColor
let avatarPlaceholderColor: UIColor
public init(textColor: UIColor, secretTextColor: UIColor, selectedTextColor: UIColor, checkBackgroundColor: UIColor, checkFillColor: UIColor, checkColor: UIColor, avatarPlaceholderColor: UIColor) {
self.textColor = textColor
self.secretTextColor = secretTextColor
self.selectedTextColor = selectedTextColor
self.checkBackgroundColor = checkBackgroundColor
self.checkFillColor = checkFillColor
self.checkColor = checkColor
self.avatarPlaceholderColor = avatarPlaceholderColor
}
public func isEqual(to: SelectablePeerNodeTheme) -> Bool {
if self === to {
return true
}
if !self.textColor.isEqual(to.textColor) {
return false
}
if !self.secretTextColor.isEqual(to.secretTextColor) {
return false
}
if !self.selectedTextColor.isEqual(to.selectedTextColor) {
return false
}
if !self.checkBackgroundColor.isEqual(to.checkBackgroundColor) {
return false
}
if !self.checkFillColor.isEqual(to.checkFillColor) {
return false
}
if !self.checkColor.isEqual(to.checkColor) {
return false
}
if !self.avatarPlaceholderColor.isEqual(to.avatarPlaceholderColor) {
return false
}
return true
}
}
public final class SelectablePeerNode: ASDisplayNode {
private let contextContainer: ContextControllerSourceNode
private let avatarSelectionNode: ASImageNode
private let avatarNodeContainer: ASDisplayNode
private let avatarNode: AvatarNode
private var avatarBadgeOutline: UIImageView?
private var avatarBadge: UIImageView?
private var avatarBadgeLabel: ImmediateTextView?
private let onlineNode: PeerOnlineMarkerNode
private var checkNode: CheckNode?
private let textNode: ImmediateTextNode
private let iconView: ComponentView<Empty>
public var toggleSelection: ((Bool) -> Void)?
public var contextAction: ((ASDisplayNode, ContextGesture?, CGPoint?) -> Void)? {
didSet {
self.contextContainer.isGestureEnabled = self.contextAction != nil
}
}
private var currentSelected = false
private var peer: EngineRenderedPeer?
private var requiresPremiumForMessaging: Bool = false
public var compact = false
public var theme: SelectablePeerNodeTheme = SelectablePeerNodeTheme(textColor: .black, secretTextColor: .green, selectedTextColor: .blue, checkBackgroundColor: .white, checkFillColor: .blue, checkColor: .white, avatarPlaceholderColor: .white) {
didSet {
if !self.theme.isEqual(to: oldValue) {
if let peer = self.peer, let mainPeer = peer.chatMainPeer {
self.textNode.attributedText = NSAttributedString(string: mainPeer.debugDisplayTitle, font: textFont, textColor: self.currentSelected ? self.theme.selectedTextColor : (peer.peerId.namespace == Namespaces.Peer.SecretChat ? self.theme.secretTextColor : self.theme.textColor), paragraphAlignment: .center)
}
}
}
}
override public init() {
self.contextContainer = ContextControllerSourceNode()
self.contextContainer.isGestureEnabled = false
self.avatarNodeContainer = ASDisplayNode()
self.avatarSelectionNode = ASImageNode()
self.avatarSelectionNode.isLayerBacked = true
self.avatarSelectionNode.displayWithoutProcessing = true
self.avatarSelectionNode.displaysAsynchronously = false
self.avatarSelectionNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 60.0, height: 60.0))
self.avatarSelectionNode.alpha = 0.0
self.avatarNode = AvatarNode(font: avatarFont)
self.avatarNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 60.0, height: 60.0))
self.textNode = ImmediateTextNode()
self.textNode.isUserInteractionEnabled = false
self.textNode.displaysAsynchronously = false
self.onlineNode = PeerOnlineMarkerNode()
self.iconView = ComponentView<Empty>()
super.init()
self.addSubnode(self.contextContainer)
self.avatarNodeContainer.addSubnode(self.avatarSelectionNode)
self.avatarNodeContainer.addSubnode(self.avatarNode)
self.contextContainer.addSubnode(self.avatarNodeContainer)
self.contextContainer.addSubnode(self.textNode)
self.contextContainer.addSubnode(self.onlineNode)
self.contextContainer.activated = { [weak self] gesture, _ in
guard let strongSelf = self, let contextAction = strongSelf.contextAction else {
gesture.cancel()
return
}
contextAction(strongSelf.contextContainer, gesture, nil)
}
}
public func setup(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, peer: EngineRenderedPeer, requiresPremiumForMessaging: Bool, requiresStars: Int64? = nil, customTitle: String? = nil, iconId: Int64? = nil, iconColor: Int32? = nil, online: Bool = false, numberOfLines: Int = 2, synchronousLoad: Bool) {
self.setup(
accountPeerId: context.account.peerId,
postbox: context.account.postbox,
network: context.account.network,
energyUsageSettings: context.sharedContext.energyUsageSettings,
contentSettings: context.currentContentSettings.with { $0 },
animationCache: context.animationCache,
animationRenderer: context.animationRenderer,
resolveInlineStickers: { fileIds in
return context.engine.stickers.resolveInlineStickers(fileIds: fileIds)
},
theme: theme,
strings: strings,
peer: peer,
requiresPremiumForMessaging: requiresPremiumForMessaging,
requiresStars: requiresStars,
customTitle: customTitle,
iconId: iconId,
iconColor: iconColor,
online: online,
numberOfLines: numberOfLines,
synchronousLoad: synchronousLoad
)
}
public func setupStoryRepost(accountPeerId: EnginePeer.Id, postbox: Postbox, network: Network, theme: PresentationTheme, strings: PresentationStrings, synchronousLoad: Bool, isMessage: Bool) {
self.peer = nil
self.textNode.maximumNumberOfLines = 2
self.textNode.attributedText = NSAttributedString(string: isMessage ? strings.Share_RepostToStory : strings.Share_RepostStory, font: textFont, textColor: self.theme.textColor, paragraphAlignment: .center)
self.avatarNode.setPeer(accountPeerId: accountPeerId, postbox: postbox, network: network, contentSettings: ContentSettings.default, theme: theme, peer: nil, overrideImage: .repostIcon, emptyColor: self.theme.avatarPlaceholderColor, clipStyle: .round, synchronousLoad: synchronousLoad)
self.avatarNode.playRepostAnimation()
}
public func setup(accountPeerId: EnginePeer.Id, postbox: Postbox, network: Network, energyUsageSettings: EnergyUsageSettings, contentSettings: ContentSettings, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, resolveInlineStickers: @escaping ([Int64]) -> Signal<[Int64: TelegramMediaFile], NoError>, theme: PresentationTheme, strings: PresentationStrings, peer: EngineRenderedPeer, requiresPremiumForMessaging: Bool, requiresStars: Int64? = nil, customTitle: String? = nil, iconId: Int64? = nil, iconColor: Int32? = nil, online: Bool = false, numberOfLines: Int = 2, synchronousLoad: Bool) {
let isFirstTime = self.peer == nil
self.peer = peer
guard let mainPeer = peer.chatMainPeer else {
return
}
self.requiresPremiumForMessaging = requiresPremiumForMessaging
let defaultColor: UIColor
if requiresPremiumForMessaging {
defaultColor = self.theme.textColor.withMultipliedAlpha(0.4)
} else {
defaultColor = peer.peerId.namespace == Namespaces.Peer.SecretChat ? self.theme.secretTextColor : self.theme.textColor
}
var isForum = false
if let peer = peer.chatMainPeer, case let .channel(channel) = peer, channel.flags.contains(.isForum) {
isForum = true
}
let text: String
var overrideImage: AvatarNodeImageOverride?
if peer.peerId == accountPeerId {
text = self.compact ? strings.DeleteAccount_SavedMessages : strings.DialogList_SavedMessages
overrideImage = .savedMessagesIcon
} else if peer.peerId.isReplies {
text = strings.DialogList_Replies
overrideImage = .repliesIcon
} else {
text = mainPeer.compactDisplayTitle
if mainPeer.isDeleted {
overrideImage = .deletedIcon
}
}
self.textNode.maximumNumberOfLines = numberOfLines
self.textNode.attributedText = NSAttributedString(string: customTitle ?? text, font: textFont, textColor: self.currentSelected ? self.theme.selectedTextColor : defaultColor, paragraphAlignment: .center)
self.avatarNode.setPeer(accountPeerId: accountPeerId, postbox: postbox, network: network, contentSettings: contentSettings, theme: theme, peer: mainPeer, overrideImage: overrideImage, emptyColor: self.theme.avatarPlaceholderColor, clipStyle: isForum ? .roundedRect : .round, synchronousLoad: synchronousLoad)
if let requiresStars {
let avatarBadgeOutline: UIImageView
if let current = self.avatarBadgeOutline {
avatarBadgeOutline = current
} else {
avatarBadgeOutline = UIImageView()
avatarBadgeOutline.contentMode = .scaleToFill
avatarBadgeOutline.image = PresentationResourcesChatList.shareAvatarStarsLockBadgeBackground(theme)
avatarBadgeOutline.tintColor = theme.actionSheet.opaqueItemBackgroundColor
self.avatarBadgeOutline = avatarBadgeOutline
self.avatarNodeContainer.view.addSubview(avatarBadgeOutline)
}
let avatarBadge: UIImageView
if let current = self.avatarBadge {
avatarBadge = current
} else {
avatarBadge = UIImageView()
avatarBadge.contentMode = .scaleToFill
avatarBadge.image = PresentationResourcesChatList.shareAvatarStarsLockBadgeInnerBackground(theme)
avatarBadge.tintColor = theme.actionSheet.controlAccentColor
self.avatarBadge = avatarBadge
self.avatarNodeContainer.view.addSubview(avatarBadge)
}
let avatarBadgeLabel: ImmediateTextView
if let current = self.avatarBadgeLabel {
avatarBadgeLabel = current
} else {
avatarBadgeLabel = ImmediateTextView()
self.avatarBadgeLabel = avatarBadgeLabel
self.avatarNodeContainer.view.addSubview(avatarBadgeLabel)
}
let badgeString = NSMutableAttributedString(string: "⭐️\(presentationStringsFormattedNumber(Int32(requiresStars), " "))", font: Font.with(size: 9.0, design: .round , weight: .bold), textColor: theme.list.itemCheckColors.foregroundColor)
if let range = badgeString.string.range(of: "⭐️") {
badgeString.addAttribute(.attachment, value: UIImage(bundleImageName: "Premium/SendStarsPeerBadgeStarIcon")!, range: NSRange(range, in: badgeString.string))
badgeString.addAttribute(.baselineOffset, value: 1.5, range: NSRange(range, in: badgeString.string))
badgeString.addAttribute(.kern, value: -0.8, range: NSRange(badgeString.string.startIndex ..< badgeString.string.endIndex, in: badgeString.string))
}
avatarBadgeLabel.attributedText = badgeString
let avatarFrame = self.avatarNode.frame
let badgeSize = avatarBadgeLabel.updateLayout(avatarFrame.size)
var badgeFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((avatarFrame.width - badgeSize.width) / 2.0) - (self.currentSelected ? 15.0 : 0.0), y: avatarFrame.height - 13.0), size: badgeSize)
let badgeBackgroundFrame = CGRect(origin: CGPoint(x: badgeFrame.minX - 2.0, y: badgeFrame.minY - 3.0 - UIScreenPixel), size: CGSize(width: badgeFrame.width + 4.0, height: 16.0))
let badgeOutlineFrame = CGRect(origin: CGPoint(x: badgeBackgroundFrame.minX - 2.0, y: badgeBackgroundFrame.minY - 2.0), size: CGSize(width: badgeBackgroundFrame.width + 4.0, height: 20.0))
badgeFrame = badgeFrame.offsetBy(dx: -2.0, dy: 0.0)
avatarBadge.frame = badgeBackgroundFrame
avatarBadgeOutline.frame = badgeOutlineFrame
avatarBadgeLabel.frame = badgeFrame
} else if requiresPremiumForMessaging {
let avatarBadgeOutline: UIImageView
if let current = self.avatarBadgeOutline {
avatarBadgeOutline = current
} else {
avatarBadgeOutline = UIImageView()
avatarBadgeOutline.image = PresentationResourcesChatList.shareAvatarPremiumLockBadgeBackground(theme)
avatarBadgeOutline.tintColor = theme.chatList.itemBackgroundColor
self.avatarBadgeOutline = avatarBadgeOutline
self.avatarNode.view.addSubview(avatarBadgeOutline)
}
let avatarBadge: UIImageView
if let current = self.avatarBadge {
avatarBadge = current
} else {
avatarBadge = UIImageView()
avatarBadge.image = PresentationResourcesChatList.shareAvatarPremiumLockBadge(theme)
self.avatarBadge = avatarBadge
self.avatarNode.view.addSubview(avatarBadge)
}
let avatarFrame = self.avatarNode.frame
let badgeFrame = CGRect(origin: CGPoint(x: avatarFrame.width - 20.0, y: avatarFrame.height - 20.0), size: CGSize(width: 20.0, height: 20.0))
let badgeBackgroundFrame = badgeFrame.insetBy(dx: -2.0, dy: -2.0)
avatarBadgeOutline.frame = badgeBackgroundFrame
avatarBadge.frame = badgeFrame
} else {
if let avatarBadgeOutline = self.avatarBadgeOutline {
self.avatarBadgeOutline = nil
avatarBadgeOutline.removeFromSuperview()
}
if let avatarBadge = self.avatarBadge {
self.avatarBadge = nil
avatarBadge.removeFromSuperview()
}
if let avatarBadgeLabel = self.avatarBadgeLabel {
self.avatarBadgeLabel = nil
avatarBadgeLabel.removeFromSuperview()
}
}
let onlineLayout = self.onlineNode.asyncLayout()
let (onlineSize, onlineApply) = onlineLayout(online, false)
let _ = onlineApply(!isFirstTime)
self.onlineNode.setImage(PresentationResourcesChatList.recentStatusOnlineIcon(theme, state: .panel), color: nil, transition: .immediate)
self.onlineNode.frame = CGRect(origin: CGPoint(), size: onlineSize)
let iconContent: EmojiStatusComponent.Content?
if let fileId = iconId {
iconContent = .animation(content: .customEmoji(fileId: fileId), size: CGSize(width: 18.0, height: 18.0), placeholderColor: theme.actionSheet.disabledActionTextColor, themeColor: theme.actionSheet.primaryTextColor, loopMode: .count(0))
} else if let customTitle = customTitle {
iconContent = .topic(title: String(customTitle.prefix(1)), color: iconColor ?? 0, size: CGSize(width: 32.0, height: 32.0))
} else {
iconContent = nil
}
if let iconContent = iconContent {
let iconSize = self.iconView.update(
transition: .easeInOut(duration: 0.2),
component: AnyComponent(EmojiStatusComponent(
postbox: postbox,
energyUsageSettings: energyUsageSettings,
resolveInlineStickers: resolveInlineStickers,
animationCache: animationCache,
animationRenderer: animationRenderer,
content: iconContent,
isVisibleForAnimations: true,
action: nil
)),
environment: {},
containerSize: CGSize(width: 18.0, height: 18.0)
)
if let iconComponentView = self.iconView.view {
if iconComponentView.superview == nil {
self.view.addSubview(iconComponentView)
}
iconComponentView.frame = CGRect(origin: .zero, size: iconSize)
}
} else if let iconComponentView = self.iconView.view {
iconComponentView.removeFromSuperview()
}
self.setNeedsLayout()
self.isAccessibilityElement = true
self.accessibilityLabel = customTitle ?? text
self.accessibilityTraits = [.button]
if self.currentSelected {
self.accessibilityTraits.insert(.selected)
}
}
public func updateSelection(selected: Bool, animated: Bool) {
if selected != self.currentSelected {
self.currentSelected = selected
if let attributedText = self.textNode.attributedText {
self.textNode.attributedText = NSAttributedString(string: attributedText.string, font: textFont, textColor: selected ? self.theme.selectedTextColor : (self.peer?.peerId.namespace == Namespaces.Peer.SecretChat ? self.theme.secretTextColor : self.theme.textColor), paragraphAlignment: .center)
}
var isForum = false
if let peer = self.peer?.chatMainPeer, case let .channel(channel) = peer, channel.flags.contains(.isForum) {
isForum = true
}
if selected {
self.avatarNode.transform = CATransform3DMakeScale(0.866666, 0.866666, 1.0)
self.avatarSelectionNode.alpha = 1.0
self.avatarSelectionNode.image = generateImage(CGSize(width: 60.0 + 4.0, height: 60.0 + 4.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
let bounds = CGRect(origin: .zero, size: size)
if isForum {
context.setStrokeColor(self.theme.selectedTextColor.cgColor)
context.setLineWidth(2.0)
context.addPath(UIBezierPath(roundedRect: bounds.insetBy(dx: 1.0, dy: 1.0), cornerRadius: floorToScreenPixels(bounds.size.width * 0.26)).cgPath)
context.strokePath()
} else {
context.setFillColor(self.theme.selectedTextColor.cgColor)
context.fillEllipse(in: bounds)
context.setBlendMode(.copy)
context.setFillColor(UIColor.clear.cgColor)
context.fillEllipse(in: bounds.insetBy(dx: 2.0, dy: 2.0))
}
})
if let avatarBadgeLabel = self.avatarBadgeLabel, let avatarBadge = self.avatarBadge, let avatarBadgeOutline = self.avatarBadgeOutline {
avatarBadgeLabel.center = CGPoint(x: self.avatarNode.bounds.width / 2.0 - 17.0, y: avatarBadgeLabel.center.y)
avatarBadge.center = CGPoint(x: self.avatarNode.bounds.width / 2.0 - 15.0, y: avatarBadge.center.y)
avatarBadgeOutline.center = CGPoint(x: self.avatarNode.bounds.width / 2.0 - 15.0, y: avatarBadgeOutline.center.y)
if animated {
avatarBadgeLabel.layer.animatePosition(from: CGPoint(x: 15.0, y: 0.0), to: .zero, duration: 0.2, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
avatarBadge.layer.animatePosition(from: CGPoint(x: 15.0, y: 0.0), to: .zero, duration: 0.2, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
avatarBadgeOutline.layer.animatePosition(from: CGPoint(x: 15.0, y: 0.0), to: .zero, duration: 0.2, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
}
}
if animated {
self.avatarNode.layer.animateScale(from: 1.0, to: 0.866666, duration: 0.2, timingFunction: kCAMediaTimingFunctionSpring)
self.avatarSelectionNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
}
} else {
self.avatarNode.transform = CATransform3DIdentity
self.avatarSelectionNode.alpha = 0.0
if animated {
self.avatarNode.layer.animateScale(from: 0.866666, to: 1.0, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring)
self.avatarSelectionNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.28, completion: { [weak avatarSelectionNode] _ in
avatarSelectionNode?.image = nil
})
} else {
self.avatarSelectionNode.image = nil
}
if let avatarBadgeLabel = self.avatarBadgeLabel, let avatarBadge = self.avatarBadge, let avatarBadgeOutline = self.avatarBadgeOutline {
avatarBadgeLabel.center = CGPoint(x: self.avatarNode.bounds.width / 2.0 - 2.0, y: avatarBadgeLabel.center.y)
avatarBadge.center = CGPoint(x: self.avatarNode.bounds.width / 2.0, y: avatarBadge.center.y)
avatarBadgeOutline.center = CGPoint(x: self.avatarNode.bounds.width / 2.0, y: avatarBadgeOutline.center.y)
if animated {
avatarBadgeLabel.layer.animatePosition(from: CGPoint(x: -15.0, y: 0.0), to: .zero, duration: 0.2, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
avatarBadge.layer.animatePosition(from: CGPoint(x: -15.0, y: 0.0), to: .zero, duration: 0.2, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
avatarBadgeOutline.layer.animatePosition(from: CGPoint(x: -15.0, y: 0.0), to: .zero, duration: 0.2, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
}
}
}
if selected {
if self.checkNode == nil {
let checkNode = CheckNode(theme: CheckNodeTheme(backgroundColor: self.theme.checkFillColor, strokeColor: self.theme.checkColor, borderColor: self.theme.checkBackgroundColor, overlayBorder: true, hasInset: false, hasShadow: false, filledBorder: true, borderWidth: 2.0))
self.checkNode = checkNode
checkNode.isUserInteractionEnabled = false
self.addSubnode(checkNode)
let avatarFrame = self.avatarNode.frame
let checkSize = CGSize(width: 22.0, height: 22.0)
checkNode.frame = CGRect(origin: CGPoint(x: avatarFrame.maxX - 14.0, y: avatarFrame.maxY - 15.0), size: checkSize)
checkNode.setSelected(true, animated: animated)
}
} else if let checkNode = self.checkNode {
self.checkNode = nil
checkNode.setSelected(false, animated: animated)
}
self.setNeedsLayout()
}
if selected {
self.accessibilityTraits.insert(.selected)
} else {
self.accessibilityTraits.remove(.selected)
}
}
override public func didLoad() {
super.didLoad()
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
}
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
if case .ended = recognizer.state {
self.toggleSelection?(self.requiresPremiumForMessaging)
}
}
override public func layout() {
super.layout()
let bounds = self.bounds
self.contextContainer.frame = bounds
self.avatarNodeContainer.frame = CGRect(origin: CGPoint(x: floor((bounds.size.width - 60.0) / 2.0), y: 4.0), size: CGSize(width: 60.0, height: 60.0))
let iconSize = CGSize(width: 18.0, height: 18.0)
let textSize = self.textNode.updateLayout(bounds.size)
var totalWidth = textSize.width
var leftOrigin = floorToScreenPixels((bounds.width - textSize.width) / 2.0)
if let iconView = self.iconView.view, iconView.superview != nil {
totalWidth += iconView.frame.width + 2.0
leftOrigin = floorToScreenPixels((bounds.width - totalWidth) / 2.0)
iconView.frame = CGRect(origin: CGPoint(x: leftOrigin, y: 4.0 + 60.0 + 4.0 + floorToScreenPixels((textSize.height - iconSize.height) / 2.0)), size: iconSize)
leftOrigin += iconSize.width + 2.0
}
self.textNode.frame = CGRect(origin: CGPoint(x: leftOrigin, y: 4.0 + 60.0 + 4.0), size: textSize)
let avatarFrame = self.avatarNode.frame
let avatarContainerFrame = self.avatarNodeContainer.frame
self.onlineNode.frame = CGRect(origin: CGPoint(x: avatarContainerFrame.maxX - self.onlineNode.frame.width - 2.0, y: avatarContainerFrame.maxY - self.onlineNode.frame.height - 2.0), size: self.onlineNode.frame.size)
if let checkNode = self.checkNode {
let checkSize = CGSize(width: 22.0, height: 22.0)
checkNode.frame = CGRect(origin: CGPoint(x: avatarFrame.maxX - 14.0, y: avatarFrame.maxY - 15.0), size: checkSize)
}
}
}