feat: новые функции, исправлены критические ошибки сборки и баги интерфейса, больше подписей в файлах

This commit is contained in:
ichmagmaus 812
2026-03-04 22:06:16 +01:00
parent a614259289
commit f033954db2
81 changed files with 1256 additions and 298 deletions
@@ -27,7 +27,6 @@ import MediaEditor
import AvatarBackground
import LottieComponent
import UndoUI
import PremiumAlertController
public struct AvatarKeyboardInputData: Equatable {
var emoji: EmojiPagerContentComponent
@@ -481,6 +481,10 @@ private func mapVisibility(_ visibility: ListViewItemNodeVisibility, boundsSize:
}
}
private func isDeletedBubbleMessage(_ message: Message) -> Bool {
return AntiDeleteManager.shared.isMessageDeleted(peerId: message.id.peerId.toInt64(), messageId: message.id.id) || AntiDeleteManager.shared.isMessageDeleted(text: message.text)
}
public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode {
public class ContentContainer {
public let contentMessageStableId: UInt32
@@ -4368,6 +4372,14 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
}
}
let deletedMessageAlpha = CGFloat(AntiDeleteManager.shared.deletedMessageDisplayAlpha)
var deletedMessageStableIds = Set<UInt32>()
for (message, _) in item.content {
if isDeletedBubbleMessage(message) {
deletedMessageStableIds.insert(message.stableId)
}
}
var incomingOffset: CGFloat = 0.0
switch backgroundType {
case .incoming:
@@ -4512,10 +4524,31 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
}
contentContainer?.update(size: relativeFrame.size, contentOrigin: contentOrigin, selectionInsets: selectionInsets, index: index, presentationData: item.presentationData, graphics: graphics, backgroundType: backgroundType, presentationContext: item.controllerInteraction.presentationContext, mediaBox: item.context.account.postbox.mediaBox, messageSelection: itemSelection)
if let contentContainer = contentContainer {
let containerAlpha: CGFloat = deletedMessageStableIds.contains(stableId) ? deletedMessageAlpha : 1.0
if case .System = animation {
animation.animator.updateAlpha(layer: contentContainer.sourceNode.contentNode.layer, alpha: containerAlpha, completion: nil)
} else {
contentContainer.sourceNode.contentNode.alpha = containerAlpha
}
}
index += 1
}
let mainContainerAlpha: CGFloat
if contentContainerNodeFrames.isEmpty, !deletedMessageStableIds.isEmpty {
mainContainerAlpha = deletedMessageAlpha
} else {
mainContainerAlpha = 1.0
}
if case .System = animation {
animation.animator.updateAlpha(layer: strongSelf.mainContextSourceNode.contentNode.layer, alpha: mainContainerAlpha, completion: nil)
} else {
strongSelf.mainContextSourceNode.contentNode.alpha = mainContainerAlpha
}
if hasSelection {
var currentMaskView: UIImageView?
if let maskView = strongSelf.contentContainersWrapperNode.view.mask as? UIImageView {
@@ -29,6 +29,27 @@ public final class NavigationButtonComponent: Component {
case more
case icon(imageName: String)
case proxy(status: ChatTitleProxyStatus)
/// Liquid glass avatar button for account switching.
/// peerId is used as a diff key; avatarImage is the rendered avatar.
case avatar(peerId: String, avatarImage: UIImage?)
public static func ==(lhs: Content, rhs: Content) -> Bool {
switch (lhs, rhs) {
case let (.text(lt, lb), .text(rt, rb)):
return lt == rt && lb == rb
case (.more, .more):
return true
case let (.icon(l), .icon(r)):
return l == r
case let (.proxy(l), .proxy(r)):
return l == r
case let (.avatar(lId, _), .avatar(rId, _)):
// Re-render when peerId changes; image updates are handled by the view itself
return lId == rId
default:
return false
}
}
}
public let content: Content
@@ -62,6 +83,12 @@ public final class NavigationButtonComponent: Component {
private var moreButton: MoreHeaderButton?
// MARK: - Liquid Glass Avatar
private var avatarContainerView: UIView?
private var avatarBlurView: UIVisualEffectView?
private var avatarImageView: UIImageView?
private var avatarBorderLayer: CAShapeLayer?
private var component: NavigationButtonComponent?
private var theme: PresentationTheme?
@@ -74,19 +101,23 @@ public final class NavigationButtonComponent: Component {
guard let self else {
return
}
if highlighted {
self.textView?.alpha = 0.6
self.proxyNode?.alpha = 0.6
self.iconView?.alpha = 0.6
} else {
self.textView?.alpha = 1.0
self.textView?.layer.animateAlpha(from: 0.6, to: 1.0, duration: 0.2)
self.proxyNode?.alpha = 1.0
self.proxyNode?.layer.animateAlpha(from: 0.6, to: 1.0, duration: 0.2)
self.iconView?.alpha = 1.0
self.iconView?.layer.animateAlpha(from: 0.6, to: 1.0, duration: 0.2)
let alpha: CGFloat = highlighted ? 0.55 : 1.0
self.textView?.alpha = alpha
self.proxyNode?.alpha = alpha
self.iconView?.alpha = alpha
self.avatarContainerView?.alpha = alpha
if !highlighted {
let animateAlpha = { (layer: CALayer?) in
let anim = CABasicAnimation(keyPath: "opacity")
anim.fromValue = 0.55
anim.toValue = 1.0
anim.duration = 0.2
layer?.add(anim, forKey: "opacity")
}
animateAlpha(self.textView?.layer)
animateAlpha(self.proxyNode?.layer)
animateAlpha(self.iconView?.layer)
animateAlpha(self.avatarContainerView?.layer)
}
}
}
@@ -99,6 +130,51 @@ public final class NavigationButtonComponent: Component {
self.component?.pressed(self)
}
// MARK: - Liquid glass avatar setup
private func setupAvatarViewsIfNeeded() {
guard avatarContainerView == nil else { return }
// Container holds blur + image
let container = UIView()
container.isUserInteractionEnabled = false
container.clipsToBounds = true
// Blur background liquid glass effect
let blurEffect = UIBlurEffect(style: .systemUltraThinMaterial)
let blurView = UIVisualEffectView(effect: blurEffect)
blurView.isUserInteractionEnabled = false
container.addSubview(blurView)
// Avatar image on top of blur
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFill
imageView.isUserInteractionEnabled = false
imageView.clipsToBounds = true
container.addSubview(imageView)
self.addSubview(container)
self.avatarContainerView = container
self.avatarBlurView = blurView
self.avatarImageView = imageView
// Subtle glass ring border
let borderLayer = CAShapeLayer()
borderLayer.fillColor = UIColor.clear.cgColor
borderLayer.strokeColor = UIColor.white.withAlphaComponent(0.22).cgColor
borderLayer.lineWidth = 1.5
container.layer.addSublayer(borderLayer)
self.avatarBorderLayer = borderLayer
}
private func removeAvatarViews() {
avatarContainerView?.removeFromSuperview()
avatarContainerView = nil
avatarBlurView = nil
avatarImageView = nil
avatarBorderLayer = nil
}
func update(component: NavigationButtonComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<NavigationButtonComponentEnvironment>, transition: ComponentTransition) -> CGSize {
self.component = component
@@ -113,6 +189,7 @@ public final class NavigationButtonComponent: Component {
var imageName: String?
var proxyStatus: ChatTitleProxyStatus?
var isMore: Bool = false
var avatarContent: (peerId: String, image: UIImage?)? = nil
switch component.content {
case let .text(title, isBold):
@@ -123,10 +200,13 @@ public final class NavigationButtonComponent: Component {
imageName = imageNameValue
case let .proxy(status):
proxyStatus = status
case let .avatar(peerId, image):
avatarContent = (peerId, image)
}
var size = CGSize(width: 0.0, height: availableSize.height)
// MARK: Text
if let textString = textString {
let textView: ImmediateTextView
if let current = self.textView {
@@ -144,11 +224,13 @@ public final class NavigationButtonComponent: Component {
size.width = max(44.0, textSize.width + textInset * 2.0)
textView.frame = CGRect(origin: CGPoint(x: floor((size.width - textSize.width) / 2.0), y: floor((availableSize.height - textSize.height) / 2.0)), size: textSize)
removeAvatarViews()
} else if let textView = self.textView {
self.textView = nil
textView.removeFromSuperview()
}
// MARK: Icon
if let imageName = imageName {
let iconView: UIImageView
if let current = self.iconView {
@@ -166,15 +248,16 @@ public final class NavigationButtonComponent: Component {
if let iconSize = iconView.image?.size {
size.width = 44.0
iconView.frame = CGRect(origin: CGPoint(x: floor((size.width - iconSize.width) / 2.0), y: floor((availableSize.height - iconSize.height) / 2.0)), size: iconSize)
}
removeAvatarViews()
} else if let iconView = self.iconView {
self.iconView = nil
iconView.removeFromSuperview()
self.iconImageName = nil
}
// MARK: Proxy
if let proxyStatus = proxyStatus {
let proxyNode: ChatTitleProxyNode
if let current = self.proxyNode {
@@ -191,13 +274,14 @@ public final class NavigationButtonComponent: Component {
proxyNode.theme = theme
proxyNode.status = proxyStatus
proxyNode.frame = CGRect(origin: CGPoint(x: floor((size.width - proxySize.width) / 2.0), y: floor((availableSize.height - proxySize.height) / 2.0)), size: proxySize)
removeAvatarViews()
} else if let proxyNode = self.proxyNode {
self.proxyNode = nil
proxyNode.removeFromSupernode()
}
// MARK: More
if isMore {
let moreButton: MoreHeaderButton
if let current = self.moreButton, !themeUpdated {
@@ -233,13 +317,52 @@ public final class NavigationButtonComponent: Component {
size.width = 44.0
moreButton.setContent(.more(MoreHeaderButton.optionsCircleImage(color: theme.rootController.navigationBar.buttonColor)))
moreButton.frame = CGRect(origin: CGPoint(x: floor((size.width - buttonSize.width) / 2.0), y: floor((size.height - buttonSize.height) / 2.0)), size: buttonSize)
removeAvatarViews()
} else if let moreButton = self.moreButton {
self.moreButton = nil
moreButton.removeFromSupernode()
}
// MARK: Liquid Glass Avatar
if let (_, image) = avatarContent {
setupAvatarViewsIfNeeded()
let avatarDiameter: CGFloat = 28.0
size.width = 44.0
let containerRect = CGRect(
x: floor((size.width - avatarDiameter) / 2.0),
y: floor((availableSize.height - avatarDiameter) / 2.0),
width: avatarDiameter,
height: avatarDiameter
)
avatarContainerView?.frame = containerRect
avatarContainerView?.layer.cornerRadius = avatarDiameter / 2.0
avatarBlurView?.frame = CGRect(origin: .zero, size: containerRect.size)
if let image = image {
avatarImageView?.image = image
avatarImageView?.frame = CGRect(origin: .zero, size: containerRect.size)
avatarImageView?.backgroundColor = nil
} else {
avatarImageView?.image = nil
// Fallback: solid tinted background when no photo
avatarImageView?.frame = CGRect(origin: .zero, size: containerRect.size)
avatarImageView?.backgroundColor = theme.list.itemAccentColor.withAlphaComponent(0.35)
}
// Update border ring path
let borderPath = UIBezierPath(roundedRect: CGRect(origin: .zero, size: containerRect.size).insetBy(dx: 0.75, dy: 0.75), cornerRadius: avatarDiameter / 2.0)
avatarBorderLayer?.path = borderPath.cgPath
avatarBorderLayer?.frame = CGRect(origin: .zero, size: containerRect.size)
} else if avatarContent == nil && avatarContainerView != nil {
removeAvatarViews()
}
return size
}
}
@@ -5,6 +5,8 @@ swift_library(
module_name = "GiftViewScreen",
srcs = glob([
"Sources/**/*.swift",
], exclude = [
"Sources/TableComponent.swift",
]),
copts = [
"-warnings-as-errors",
@@ -133,7 +133,7 @@ public func giftOfferAlertController(
HStack(items, spacing: 4.0)
)
tableItems.append(.init(
tableItems.append(TableComponent.Item(
id: id,
title: title,
hasBackground: false,
@@ -180,12 +180,12 @@ public func giftOfferAlertController(
AlertTextComponent(content: .plain(text))
)
))
content.append(AnyComponentWithIdentity(
let tableComponent = AnyComponent(AlertTableComponent(items: tableItems))
let tableEntry = AnyComponentWithIdentity<AlertComponentEnvironment>(
id: "table",
component: AnyComponent(
AlertTableComponent(items: tableItems)
)
))
component: tableComponent
)
content.append(tableEntry)
if let valueAmount = gift.valueUsdAmount {
let resaleConfiguration = StarsSubscriptionConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
@@ -118,7 +118,7 @@ public func giftTransferAlertController(
HStack(items, spacing: 4.0)
)
tableItems.append(.init(
tableItems.append(TableComponent.Item(
id: id,
title: title,
hasBackground: false,
@@ -165,12 +165,12 @@ public func giftTransferAlertController(
AlertTextComponent(content: .plain(text))
)
))
content.append(AnyComponentWithIdentity(
let tableComponent = AnyComponent(AlertTableComponent(items: tableItems))
let tableEntry = AnyComponentWithIdentity<AlertComponentEnvironment>(
id: "table",
component: AnyComponent(
AlertTableComponent(items: tableItems)
)
))
component: tableComponent
)
content.append(tableEntry)
let alertController = ChatMessagePaymentAlertController(
context: context,
@@ -8,7 +8,7 @@ import ChatPresentationInterfaceState
import AsyncDisplayKit
import AccountContext
open class ChatTitleAccessoryPanelNode: ASDisplayNode {
open class LegacyChatTitleAccessoryPanelNode: ASDisplayNode {
public typealias LayoutResult = ChatControllerCustomNavigationPanelNodeLayoutResult
open var interfaceInteraction: ChatPanelInterfaceInteraction?
@@ -19,11 +19,11 @@ open class ChatTitleAccessoryPanelNode: ASDisplayNode {
}
public final class LegacyChatHeaderPanelComponent: Component {
public let panelNode: ChatTitleAccessoryPanelNode
public let panelNode: LegacyChatTitleAccessoryPanelNode
public let interfaceState: ChatPresentationInterfaceState
public init(
panelNode: ChatTitleAccessoryPanelNode,
panelNode: LegacyChatTitleAccessoryPanelNode,
interfaceState: ChatPresentationInterfaceState
) {
self.panelNode = panelNode
@@ -232,20 +232,34 @@ final class AffiliateProgramSetupScreenComponent: Component {
)
))
let tableItems: [TableComponent.Item] = [
TableComponent.Item(id: 0, title: environment.strings.AffiliateSetup_AlertApply_SectionCommission, component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: commissionTitle, font: Font.regular(15.0), textColor: environment.theme.actionSheet.primaryTextColor))
))),
TableComponent.Item(id: 1, title: environment.strings.AffiliateSetup_AlertApply_SectionDuration, component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: durationTitle, font: Font.regular(15.0), textColor: environment.theme.actionSheet.primaryTextColor))
)))
]
content.append(AnyComponentWithIdentity(
let textColor = environment.theme.actionSheet.primaryTextColor
let commissionItemText = NSAttributedString(
string: commissionTitle,
font: Font.regular(15.0),
textColor: textColor
)
let durationItemText = NSAttributedString(
string: durationTitle,
font: Font.regular(15.0),
textColor: textColor
)
let commissionItem = TableComponent.Item(
id: 0,
title: environment.strings.AffiliateSetup_AlertApply_SectionCommission,
component: AnyComponent(MultilineTextComponent(text: .plain(commissionItemText)))
)
let durationItem = TableComponent.Item(
id: 1,
title: environment.strings.AffiliateSetup_AlertApply_SectionDuration,
component: AnyComponent(MultilineTextComponent(text: .plain(durationItemText)))
)
let tableItems: [TableComponent.Item] = [commissionItem, durationItem]
let tableComponent = AnyComponent(AlertTableComponent(items: tableItems))
let tableEntry = AnyComponentWithIdentity<AlertComponentEnvironment>(
id: "table",
component: AnyComponent(
AlertTableComponent(items: tableItems)
)
))
component: tableComponent
)
content.append(tableEntry)
let alertController = AlertScreen(
context: component.context,
@@ -6,7 +6,7 @@ import TelegramPresentationData
import MultilineTextComponent
import AlertComponent
final class TableComponent: CombinedComponent {
final class AffiliateTableComponent: CombinedComponent {
class Item: Equatable {
public let id: AnyHashable
public let title: String
@@ -45,7 +45,7 @@ final class TableComponent: CombinedComponent {
self.items = items
}
public static func ==(lhs: TableComponent, rhs: TableComponent) -> Bool {
public static func ==(lhs: AffiliateTableComponent, rhs: AffiliateTableComponent) -> Bool {
if lhs.theme !== rhs.theme {
return false
}
@@ -228,9 +228,9 @@ private final class TableAlertContentComponent: CombinedComponent {
let theme: PresentationTheme
let title: String
let text: String
let table: TableComponent
let table: AffiliateTableComponent
init(theme: PresentationTheme, title: String, text: String, table: TableComponent) {
init(theme: PresentationTheme, title: String, text: String, table: AffiliateTableComponent) {
self.theme = theme
self.title = title
self.text = text
@@ -256,7 +256,7 @@ private final class TableAlertContentComponent: CombinedComponent {
public static var body: Body {
let title = Child(MultilineTextComponent.self)
let text = Child(MultilineTextComponent.self)
let table = Child(TableComponent.self)
let table = Child(AffiliateTableComponent.self)
return { context in
let title = title.update(
@@ -318,17 +318,3 @@ private final class TableAlertContentComponent: CombinedComponent {
}
}
}
func tableAlert(theme: PresentationTheme, title: String, text: String, table: TableComponent, actions: [ComponentAlertAction]) -> ViewController {
return componentAlertController(
theme: AlertControllerTheme(presentationTheme: theme, fontSize: .regular),
content: AnyComponent(TableAlertContentComponent(
theme: theme,
title: title,
text: text,
table: table
)),
actions: actions,
actionLayout: .horizontal
)
}
@@ -8,7 +8,8 @@ import TelegramPresentationData
import ProgressNavigationButtonNode
import AccountContext
import SearchUI
import ChatListUI
import func ChatListUI.chatListFilterItems
import enum ChatListUI.ChatListContainerNodeFilter
import CounterControllerTitleView
import ChatListFilterTabContainerNode
@@ -69,7 +69,7 @@ public final class BirthdayPickerComponent: Component {
private let calendar = Calendar(identifier: .gregorian)
private var value = TelegramBirthday(day: 1, month: 1, year: nil)
private var minYear: Int32 = 1900
private var minYear: Int32 = 1
private let maxYear: Int32
override init(frame: CGRect) {
@@ -1,5 +0,0 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.6581 5.56807C9.26054 4.9489 10.0877 4.59961 10.9516 4.59961H18.4004C20.1677 4.59961 21.6004 6.0323 21.6004 7.79961V16.1996C21.6004 17.9669 20.1677 19.3996 18.4004 19.3996H10.9516C10.0877 19.3996 9.26054 19.0503 8.6581 18.4311L3.486 13.1154C2.88168 12.4943 2.88168 11.5049 3.486 10.8838L8.6581 5.56807Z" stroke="black" stroke-width="1.66"/>
<path d="M11.5996 9.2002L17.1996 14.8002" stroke="black" stroke-width="1.66" stroke-linecap="round"/>
<path d="M11.5996 14.7998L17.1996 9.1998" stroke="black" stroke-width="1.66" stroke-linecap="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 659 B

@@ -1,8 +0,0 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="12" cy="12" r="9.2" stroke="black" stroke-width="1.66"/>
<ellipse cx="12" cy="12" rx="5.2" ry="9.2" stroke="black" stroke-width="1.328"/>
<path d="M12 2.8V21.2" stroke="black" stroke-width="1.328"/>
<path d="M2.8 12L21.2 12" stroke="black" stroke-width="1.328"/>
<path d="M4.4 17.6C4.4 17.6 6.75325 16 12 16C17.2468 16 19.6 17.6 19.6 17.6" stroke="black" stroke-width="1.328"/>
<path d="M19.6 6.4C19.6 6.4 17.2468 8 12 8C6.75325 8 4.4 6.4 4.4 6.4" stroke="black" stroke-width="1.328"/>
</svg>

Before

Width:  |  Height:  |  Size: 601 B

@@ -1,3 +0,0 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.0626 21.9306H12.9374C13.652 21.9306 14.1996 21.494 14.3666 20.8065L14.7657 19.0693L15.0627 18.9672L16.5755 19.8961C17.1787 20.277 17.8748 20.1841 18.3852 19.6732L19.6846 18.3819C20.195 17.871 20.2879 17.165 19.9073 16.5704L18.9607 15.0656L19.0721 14.7868L20.8076 14.3781C21.4851 14.2109 21.9306 13.6535 21.9306 12.9475V11.1082C21.9306 10.4022 21.4944 9.84484 20.8076 9.67761L19.0906 9.2596L18.9699 8.96231L19.9166 7.45739C20.2971 6.86287 20.2043 6.16613 19.6939 5.64595L18.3945 4.34539C17.8934 3.84375 17.1973 3.75086 16.594 4.12244L15.0812 5.05138L14.7657 4.93066L14.3666 3.19348C14.1996 2.50605 13.652 2.06944 12.9374 2.06944H11.0626C10.348 2.06944 9.80046 2.50605 9.63335 3.19348L9.22503 4.93066L8.90949 5.05138L7.40598 4.12244C6.80272 3.75086 6.09735 3.84375 5.5962 4.34539L4.30614 5.64595C3.79569 6.16613 3.6936 6.86287 4.0834 7.45739L5.02077 8.96231L4.90939 9.2596L3.19243 9.67761C2.50565 9.84484 2.06945 10.4022 2.06945 11.1082V12.9475C2.06945 13.6535 2.51493 14.2109 3.19243 14.3781L4.92794 14.7868L5.03005 15.0656L4.09268 16.5704C3.70289 17.165 3.80498 17.871 4.31542 18.3819L5.60548 19.6732C6.1159 20.1841 6.82127 20.277 7.42453 19.8961L8.92804 18.9672L9.22503 19.0693L9.63335 20.8065C9.80046 21.494 10.348 21.9306 11.0626 21.9306ZM11.2111 20.4814C11.0534 20.4814 10.9698 20.4164 10.942 20.2677L10.3851 17.9639C9.81901 17.8246 9.28997 17.6016 8.89093 17.3508L6.86766 18.5956C6.75627 18.6792 6.63567 18.6606 6.52428 18.5492L5.42915 17.453C5.32704 17.3508 5.31776 17.2393 5.39198 17.1092L6.63567 15.1027C6.42217 14.7126 6.1809 14.1831 6.03241 13.6164L3.73073 13.0683C3.58223 13.0404 3.51727 12.9568 3.51727 12.7989V11.2475C3.51727 11.0803 3.57295 11.006 3.73073 10.9781L6.02313 10.4208C6.17163 9.81694 6.45 9.26888 6.61711 8.92515L5.3827 6.91859C5.29921 6.77926 5.30849 6.66775 5.41059 6.55631L6.515 5.47873C6.62639 5.36722 6.72844 5.34867 6.86766 5.43228L8.87232 6.6492C9.27142 6.42625 9.83757 6.19402 10.3944 6.03607L10.942 3.73228C10.9698 3.58365 11.0534 3.51862 11.2111 3.51862H12.7889C12.9466 3.51862 13.0302 3.58365 13.0487 3.73228L13.6149 6.05469C14.1903 6.2033 14.6915 6.43553 15.1091 6.65848L17.1231 5.43228C17.2716 5.34867 17.3643 5.36722 17.485 5.47873L18.5801 6.55631C18.6915 6.66775 18.6915 6.77926 18.608 6.91859L17.3737 8.92515C17.55 9.26888 17.8191 9.81694 17.9676 10.4208L20.2693 10.9781C20.4178 11.006 20.4827 11.0803 20.4827 11.2475V12.7989C20.4827 12.9568 20.4085 13.0404 20.2693 13.0683L17.9583 13.6164C17.8098 14.1831 17.5778 14.7126 17.3551 15.1027L18.5987 17.1092C18.673 17.2393 18.673 17.3508 18.5616 17.453L17.4757 18.5492C17.3551 18.6606 17.2437 18.6792 17.1231 18.5956L15.0998 17.3508C14.7007 17.6016 14.181 17.8246 13.6149 17.9639L13.0487 20.2677C13.0302 20.4164 12.9466 20.4814 12.7889 20.4814H11.2111ZM12 15.5486C13.9397 15.5486 15.536 13.9508 15.536 12C15.536 10.0678 13.9397 8.46997 12 8.46997C10.0603 8.46997 8.45472 10.0678 8.45472 12C8.45472 13.9415 10.051 15.5486 12 15.5486ZM12 14.1087C10.8491 14.1087 9.90251 13.1612 9.90251 12C9.90251 10.8574 10.8491 9.90984 12 9.90984C13.1323 9.90984 14.0789 10.8574 14.0789 12C14.0789 13.1519 13.1323 14.1087 12 14.1087Z" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 3.2 KiB

@@ -1,3 +0,0 @@
<svg width="44" height="44" viewBox="0 0 44 44" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M22 4.515C12.3433 4.515 4.515 12.3433 4.515 22C4.515 31.6567 12.3433 39.485 22 39.485C31.6567 39.485 39.485 31.6567 39.485 22C39.485 12.3433 31.6567 4.515 22 4.515ZM1.485 22C1.485 10.6699 10.6699 1.485 22 1.485C33.3301 1.485 42.515 10.6699 42.515 22C42.515 33.3301 33.3301 42.515 22 42.515C10.6699 42.515 1.485 33.3301 1.485 22ZM21.9986 13.152C22.8353 13.152 23.5136 13.8303 23.5136 14.667V20.4854H29.3319C30.1686 20.4854 30.8469 21.1636 30.8469 22.0004C30.8469 22.8371 30.1686 23.5154 29.3319 23.5154H23.5136V29.3337C23.5136 30.1704 22.8353 30.8487 21.9986 30.8487C21.1619 30.8487 20.4836 30.1704 20.4836 29.3337V23.5154H14.6652C13.8285 23.5154 13.1502 22.8371 13.1502 22.0004C13.1502 21.1636 13.8285 20.4854 14.6652 20.4854H20.4836V14.667C20.4836 13.8303 21.1619 13.152 21.9986 13.152Z" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 956 B

@@ -692,6 +692,44 @@ extension ChatControllerImpl {
self.displayNode = ChatControllerNode(context: self.context, chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder, subject: self.subject, controllerInteraction: self.controllerInteraction!, chatPresentationInterfaceState: self.presentationInterfaceState, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings, navigationBar: self.navigationBar, statusBar: self.statusBar, backgroundNode: self.chatBackgroundNode, controller: self)
// Seed the title pill synchronously from whatever data is available at this point,
// so the header shows the peer name immediately instead of staying blank until
// the first async contentDataUpdated() fires.
let initialTitleContent: ChatTitleContent? = self.contentData?.state.chatTitleContent
?? self.presentationInterfaceState.renderedPeer.flatMap { renderedPeer -> ChatTitleContent? in
guard let peer = renderedPeer.peer else { return nil }
let peerData = ChatTitleContent.PeerData(
peerId: renderedPeer.peerId,
peer: peer,
isContact: false,
isSavedMessages: peer.id == self.context.account.peerId,
notificationSettings: nil,
peerPresences: [:],
cachedData: nil
)
return .peer(
peerView: peerData,
customTitle: nil,
customSubtitle: nil,
onlineMemberCount: (nil, nil),
isScheduledMessages: false,
isMuted: nil,
customMessageCount: nil,
isEnabled: true
)
}
if let initialTitleContent {
self.chatTitleView?.update(
context: self.context,
theme: self.presentationData.theme,
strings: self.presentationData.strings,
dateTimeFormat: self.presentationData.dateTimeFormat,
nameDisplayOrder: self.presentationData.nameDisplayOrder,
content: initialTitleContent,
transition: .immediate
)
}
if let currentItem = self.globalControlPanelsContext?.tempVoicePlaylistCurrentItem {
self.chatDisplayNode.historyNode.voicePlaylistItemChanged(nil, currentItem)
}
@@ -146,7 +146,6 @@ import ChatTextInputPanelNode
import ChatInputAccessoryPanel
import GlobalControlPanelsContext
import ChatSearchNavigationContentNode
import ChatAgeRestrictionAlertController
public final class ChatControllerOverlayPresentationData {
public let expandData: (ASDisplayNode?, () -> Void)
@@ -830,7 +829,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return true
}
let controllerInteraction = ChatControllerInteraction(openMessage: { [weak self] message, params in
let openMessage: (Message, OpenMessageParams) -> Bool = { [weak self] message, params in
guard let self, self.isNodeLoaded, let message = self.chatDisplayNode.historyNode.messageInCurrentHistoryView(message.id) else {
return false
}
@@ -1566,7 +1565,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self.controllerInteraction?.isOpeningMediaSignal = openChatMessageParams.blockInteraction.get()
return context.sharedContext.openChatMessage(openChatMessageParams)
}, openPeer: { [weak self] peer, navigation, fromMessage, source in
}
let openPeer: (EnginePeer, ChatControllerInteractionNavigateToPeer, MessageReference?, ChatControllerInteraction.OpenPeerSource) -> Void = { [weak self] peer, navigation, fromMessage, source in
var expandAvatar = false
if case let .groupParticipant(storyStats, avatarHeaderNode) = source {
if let storyStats, storyStats.totalCount != 0, let avatarHeaderNode = avatarHeaderNode as? ChatMessageAvatarHeaderNodeImpl {
@@ -1581,20 +1582,28 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
fromReactionMessageId = fromMessage?.id
}
self?.openPeer(peer: peer, navigation: navigation, fromMessage: fromMessage, fromReactionMessageId: fromReactionMessageId, expandAvatar: expandAvatar)
}, openPeerMention: { [weak self] name, progress in
}
let openPeerMention: (String, Promise<Bool>?) -> Void = { [weak self] name, progress in
self?.openPeerMention(name, progress: progress)
}, openMessageContextMenu: { [weak self] message, selectAll, node, frame, anyRecognizer, location in
}
let openMessageContextMenu: (Message, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?, CGPoint?) -> Void = { [weak self] message, selectAll, node, frame, anyRecognizer, location in
guard let self, self.isNodeLoaded else {
return
}
self.openMessageContextMenu(message: message, selectAll: selectAll, node: node, frame: frame, anyRecognizer: anyRecognizer, location: location)
}, openMessageReactionContextMenu: { [weak self] message, sourceView, gesture, value in
}
let openMessageReactionContextMenu: (Message, ContextExtractedContentContainingView, ContextGesture?, MessageReaction.Reaction) -> Void = { [weak self] message, sourceView, gesture, value in
guard let self else {
return
}
self.openMessageReactionContextMenu(message: message, sourceView: sourceView, gesture: gesture, value: value)
}, updateMessageReaction: { [weak self] initialMessage, reaction, force, sourceView in
}
let updateMessageReaction: (Message, ChatControllerInteractionReaction, Bool, ContextExtractedContentContainingView?) -> Void = { [weak self] initialMessage, reaction, force, sourceView in
guard let strongSelf = self else {
return
}
@@ -2062,7 +2071,16 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
}
})
}, activateMessagePinch: { [weak self] sourceNode in
}
let controllerInteraction = ChatControllerInteraction(
openMessage: openMessage,
openPeer: openPeer,
openPeerMention: openPeerMention,
openMessageContextMenu: openMessageContextMenu,
openMessageReactionContextMenu: openMessageReactionContextMenu,
updateMessageReaction: updateMessageReaction,
activateMessagePinch: { [weak self] sourceNode in
guard let strongSelf = self else {
return
}
@@ -893,7 +893,8 @@ extension ChatControllerImpl {
peerVerification = cachedChannelData.verification
}
}
copyProtectionEnabled = peer.isCopyProtectionEnabled
// GHOSTGRAM: Bypass copy protection if enabled in Misc settings
copyProtectionEnabled = MiscSettingsManager.shared.shouldBypassCopyProtection ? false : peer.isCopyProtectionEnabled
if let cachedGroupData = peerView.cachedData as? CachedGroupData {
if !cachedGroupData.botInfos.isEmpty {
hasBots = true
@@ -1371,7 +1372,8 @@ extension ChatControllerImpl {
var alwaysShowGiftButton = false
var disallowedGifts: TelegramDisallowedGifts?
if let peer = peerView.peers[peerView.peerId] {
copyProtectionEnabled = peer.isCopyProtectionEnabled
// GHOSTGRAM: Bypass copy protection if enabled in Misc settings
copyProtectionEnabled = MiscSettingsManager.shared.shouldBypassCopyProtection ? false : peer.isCopyProtectionEnabled
if let cachedData = peerView.cachedData as? CachedUserData {
contactStatus = ChatContactStatus(canAddContact: !peerView.peerIsContact, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: nil, managingBot: managingBot)
if case let .known(value) = cachedData.businessIntro {
@@ -1129,7 +1129,9 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
}
}
let isSecret = self.chatPresentationInterfaceState.copyProtectionEnabled || self.chatLocation.peerId?.namespace == Namespaces.Peer.SecretChat || self.chatLocation.peerId?.isVerificationCodes == true
// GHOSTGRAM: Bypass screenshot protection if enabled in Misc settings
let effectiveCopyProtection = MiscSettingsManager.shared.shouldBypassScreenshotProtection ? false : self.chatPresentationInterfaceState.copyProtectionEnabled
let isSecret = effectiveCopyProtection || self.chatLocation.peerId?.namespace == Namespaces.Peer.SecretChat || self.chatLocation.peerId?.isVerificationCodes == true
if self.historyNodeContainer.isSecret != isSecret {
self.historyNodeContainer.isSecret = isSecret
setLayerDisableScreenshots(self.titleAccessoryPanelContainer.layer, isSecret)
@@ -4600,12 +4602,10 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
}
}
self.setupSendActionOnViewUpdate({ [weak self] in
guard let self, let textInputPanelNode = self.inputPanelNode as? ChatTextInputPanelNode else {
return
}
// GHOSTGRAM: When send delay is active, scheduled messages
// don't trigger history view update, so clear input immediately.
if SendDelayManager.shared.isEnabled {
self.collapseInput()
self.ignoreUpdateHeight = true
textInputPanelNode.text = ""
self.requestUpdateChatInterfaceState(.immediate, overrideThreadId == nil, { state in
@@ -4624,7 +4624,33 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
return state
})
self.ignoreUpdateHeight = false
}, usedCorrelationId)
} else {
self.setupSendActionOnViewUpdate({ [weak self] in
guard let self, let textInputPanelNode = self.inputPanelNode as? ChatTextInputPanelNode else {
return
}
self.collapseInput()
self.ignoreUpdateHeight = true
textInputPanelNode.text = ""
self.requestUpdateChatInterfaceState(.immediate, overrideThreadId == nil, { state in
var state = state
state = state.withUpdatedReplyMessageSubject(nil)
state = state.withUpdatedSendMessageEffect(nil)
if state.postSuggestionState != nil {
state = state.withUpdatedPostSuggestionState(nil)
state = state.withUpdatedEditMessage(nil)
}
state = state.withUpdatedForwardMessageIds(nil)
state = state.withUpdatedForwardOptionsState(nil)
state = state.withUpdatedComposeDisableUrlPreviews([])
return state
})
self.ignoreUpdateHeight = false
}, usedCorrelationId)
}
completion()
self.sendMessages(messages, silentPosting, scheduleTime, repeatPeriod, messages.count > 1, postpone)
@@ -2056,6 +2056,10 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
}
}
}
// GHOSTGRAM: Bypass copy protection if enabled in Misc settings
if MiscSettingsManager.shared.shouldBypassCopyProtection {
isCopyProtectionEnabled = false
}
let alwaysDisplayTranscribeButton = ChatMessageItemAssociatedData.DisplayTranscribeButton(
canBeDisplayed: suggestAudioTranscription.0 < 2,
displayForNotConsumed: suggestAudioTranscription.1,
@@ -2102,7 +2106,8 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
selectedMessages: selectedMessages,
presentationData: chatPresentationData,
historyAppearsCleared: historyAppearsCleared,
skipViewOnceMedia: mode != .bubbles,
// GHOSTGRAM: Keep view-once media visible if bypass is enabled
skipViewOnceMedia: MiscSettingsManager.shared.shouldDisableViewOnceAutoDelete ? false : (mode != .bubbles),
pendingUnpinnedAllMessages: pendingUnpinnedAllMessages,
pendingRemovedMessages: pendingRemovedMessages,
associatedData: associatedData,
@@ -2110,8 +2115,9 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
customChannelDiscussionReadState: customChannelDiscussionReadState,
customThreadOutgoingReadState: customThreadOutgoingReadState,
cachedData: data.cachedData,
adMessage: allAdMessages.fixed,
dynamicAdMessages: allAdMessages.opportunistic
// GHOSTGRAM: Block ads if enabled in Misc settings
adMessage: MiscSettingsManager.shared.shouldBlockAds ? nil : allAdMessages.fixed,
dynamicAdMessages: MiscSettingsManager.shared.shouldBlockAds ? [] : allAdMessages.opportunistic
)
let lastHeaderId = filteredEntries.last.flatMap { listMessageDateHeaderId(timestamp: $0.index.timestamp) } ?? 0
let processedView = ChatHistoryView(originalView: view, filteredEntries: filteredEntries, associatedData: associatedData, lastHeaderId: lastHeaderId, id: id, locationInput: update.2, ignoreMessagesInTimestampRange: update.3, ignoreMessageIds: update.4)
@@ -31,7 +31,7 @@ final class ChatSearchNavigationContentNode: NavigationBarContentNode {
self.chatLocation = chatLocation
self.interaction = interaction
self.searchBar = SearchBarNode(theme: SearchBarNodeTheme(theme: theme, hasBackground: false, hasSeparator: false), strings: strings, fieldStyle: .modern)
self.searchBar = SearchBarNode(theme: SearchBarNodeTheme(theme: theme, hasBackground: false, hasSeparator: false), presentationTheme: theme, strings: strings, fieldStyle: .modern)
let placeholderText: String
switch chatLocation {
case .peer, .replyThread, .customChatContents:
@@ -90,10 +90,11 @@ final class ChatSearchNavigationContentNode: NavigationBarContentNode {
return 54.0
}
override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) {
override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize {
let searchBarFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - self.nominalHeight), size: CGSize(width: size.width, height: 54.0))
self.searchBar.frame = searchBarFrame
self.searchBar.updateLayout(boundingSize: searchBarFrame.size, leftInset: leftInset, rightInset: rightInset, transition: transition)
return size
}
func activate() {
@@ -106,7 +107,7 @@ final class ChatSearchNavigationContentNode: NavigationBarContentNode {
func update(presentationInterfaceState: ChatPresentationInterfaceState) {
if let search = presentationInterfaceState.search {
self.searchBar.updateThemeAndStrings(theme: SearchBarNodeTheme(theme: presentationInterfaceState.theme, hasBackground: false, hasSeparator: false), strings: presentationInterfaceState.strings)
self.searchBar.updateThemeAndStrings(theme: SearchBarNodeTheme(theme: presentationInterfaceState.theme, hasBackground: false, hasSeparator: false), presentationTheme: presentationInterfaceState.theme, strings: presentationInterfaceState.strings)
switch search.domain {
case .everything, .tag:
@@ -1,16 +1,4 @@
import Foundation
import UIKit
import Display
import AsyncDisplayKit
import ChatPresentationInterfaceState
import AccountContext
import class LegacyChatHeaderPanelComponent.LegacyChatTitleAccessoryPanelNode
class ChatTitleAccessoryPanelNode: ASDisplayNode {
typealias LayoutResult = ChatControllerCustomNavigationPanelNode.LayoutResult
var interfaceInteraction: ChatPanelInterfaceInteraction?
func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) -> LayoutResult {
preconditionFailure()
}
class ChatTitleAccessoryPanelNode: LegacyChatTitleAccessoryPanelNode {
}