Files
Leeksov 4647310322 GLEGram 12.5 — Initial public release
Based on Swiftgram 12.5 (Telegram iOS 12.5).
All GLEGram features ported and organized in GLEGram/ folder.

Features: Ghost Mode, Saved Deleted Messages, Content Protection Bypass,
Font Replacement, Fake Profile, Chat Export, Plugin System, and more.

See CHANGELOG_12.5.md for full details.
2026-04-06 09:48:12 +03:00

175 lines
8.3 KiB
Swift

import Foundation
import UIKit
import Display
import AsyncDisplayKit
import SwiftSignalKit
import ComponentFlow
import TelegramCore
import TelegramPresentationData
import PresentationDataUtils
import ContextUI
import AvatarNode
import EmojiStatusComponent
import AccountContext
public final class AccountPeerContextItem: ContextMenuCustomItem {
let context: AccountContext
let account: Account
let peer: EnginePeer
let action: (ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void
public init(context: AccountContext, account: Account, peer: EnginePeer, action: @escaping (ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void) {
self.context = context
self.account = account
self.peer = peer
self.action = action
}
public func node(presentationData: PresentationData, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) -> ContextMenuCustomNode {
return AccountPeerContextItemNode(presentationData: presentationData, item: self, getController: getController, actionSelected: actionSelected)
}
}
private final class AccountPeerContextItemNode: ASDisplayNode, ContextMenuCustomNode {
private let item: AccountPeerContextItem
private let presentationData: PresentationData
private let getController: () -> ContextControllerProtocol?
private let actionSelected: (ContextMenuActionResult) -> Void
private let buttonNode: HighlightTrackingButtonNode
private let textNode: ImmediateTextNode
private let avatarNode: AvatarNode
private let emojiStatusView: ComponentView<Empty>
init(presentationData: PresentationData, item: AccountPeerContextItem, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) {
self.item = item
self.presentationData = presentationData
self.getController = getController
self.actionSelected = actionSelected
let textFont = Font.regular(presentationData.listsFontSize.baseDisplaySize * 17.0 / 17.0)
self.textNode = ImmediateTextNode()
self.textNode.isAccessibilityElement = false
self.textNode.isUserInteractionEnabled = false
self.textNode.displaysAsynchronously = false
let peerTitle = item.peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
self.textNode.attributedText = NSAttributedString(string: peerTitle, font: textFont, textColor: presentationData.theme.contextMenu.primaryColor)
self.textNode.maximumNumberOfLines = 1
self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 14.0))
self.emojiStatusView = ComponentView<Empty>()
self.buttonNode = HighlightTrackingButtonNode()
self.buttonNode.isAccessibilityElement = true
self.buttonNode.accessibilityLabel = peerTitle
super.init()
self.addSubnode(self.textNode)
self.addSubnode(self.avatarNode)
self.addSubnode(self.buttonNode)
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
}
func updateLayout(constrainedWidth: CGFloat, constrainedHeight: CGFloat) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void) {
let sideInset: CGFloat = 18.0
let iconSideInset: CGFloat = 20.0
let verticalInset: CGFloat = 11.0
let iconSize = CGSize(width: 28.0, height: 28.0)
let standardIconWidth: CGFloat = 32.0
var rightTextInset: CGFloat = sideInset
if !iconSize.width.isZero {
rightTextInset = max(iconSize.width, standardIconWidth) + iconSideInset + sideInset - 12.0
}
self.avatarNode.setPeer(context: self.item.context, account: self.item.account, theme: self.presentationData.theme, peer: self.item.peer)
if self.item.peer.emojiStatus != nil {
rightTextInset += 32.0
}
let textSize = self.textNode.updateLayout(CGSize(width: constrainedWidth - sideInset - rightTextInset, height: .greatestFiniteMagnitude))
return (CGSize(width: textSize.width + sideInset + rightTextInset, height: verticalInset * 2.0 + textSize.height), { size, transition in
let verticalOrigin = floor((size.height - textSize.height) / 2.0)
let textFrame = CGRect(origin: CGPoint(x: iconSideInset + 40.0, y: verticalOrigin), size: textSize)
transition.updateFrameAdditive(node: self.textNode, frame: textFrame)
var iconContent: EmojiStatusComponent.Content?
if case let .user(user) = self.item.peer {
if let emojiStatus = user.emojiStatus {
iconContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 28.0, height: 28.0), placeholderColor: self.presentationData.theme.list.mediaPlaceholderColor, themeColor: self.presentationData.theme.list.itemAccentColor, loopMode: .forever)
} else if user.isPremium {
iconContent = .premium(color: self.presentationData.theme.list.itemAccentColor)
}
} else if case let .channel(channel) = self.item.peer {
if let emojiStatus = channel.emojiStatus {
iconContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 28.0, height: 28.0), placeholderColor: self.presentationData.theme.list.mediaPlaceholderColor, themeColor: self.presentationData.theme.list.itemAccentColor, loopMode: .forever)
}
}
if let iconContent {
let emojiStatusSize = self.emojiStatusView.update(
transition: .immediate,
component: AnyComponent(EmojiStatusComponent(
context: self.item.context,
animationCache: self.item.context.animationCache,
animationRenderer: self.item.context.animationRenderer,
content: iconContent,
isVisibleForAnimations: true,
action: nil
)),
environment: {},
containerSize: CGSize(width: 24.0, height: 24.0)
)
if let view = self.emojiStatusView.view {
if view.superview == nil {
self.view.addSubview(view)
}
transition.updateFrame(view: view, frame: CGRect(origin: CGPoint(x: textFrame.maxX + 2.0, y: textFrame.minY + floor((textFrame.height - emojiStatusSize.height) / 2.0)), size: emojiStatusSize))
}
}
transition.updateFrame(node: self.avatarNode, frame: CGRect(origin: CGPoint(x: iconSideInset + floor((standardIconWidth - iconSize.width) / 2.0), y: floor((size.height - iconSize.height) / 2.0)), size: iconSize))
transition.updateFrame(node: self.buttonNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)))
})
}
func updateTheme(presentationData: PresentationData) {
if let attributedText = self.textNode.attributedText {
let updatedAttributedText = NSMutableAttributedString(attributedString: attributedText)
updatedAttributedText.addAttribute(.foregroundColor, value: presentationData.theme.contextMenu.primaryColor.cgColor, range: NSRange(location: 0, length: updatedAttributedText.length))
self.textNode.attributedText = updatedAttributedText
}
}
@objc private func buttonPressed() {
self.performAction()
}
func canBeHighlighted() -> Bool {
return true
}
func setIsHighlighted(_ value: Bool) {
}
func updateIsHighlighted(isHighlighted: Bool) {
self.setIsHighlighted(isHighlighted)
}
func performAction() {
guard let controller = self.getController() else {
return
}
self.item.action(controller, { [weak self] result in
self?.actionSelected(result)
})
}
}