Telegram-iOS/submodules/PassportUI/Sources/SecureIdAuthHeaderNode.swift
isaac d1aa0db537 Postbox -> TelegramEngine waves 46-93 (squashed)
Squash of 63 commits spanning waves 46-93 (plus interspersed docs commits)
of the gradual Postbox->TelegramEngine consumer-side migration.

Scope: 139 files changed, 2123 insertions(+), 452 deletions(-).

## Themes by wave-block

**Waves 46-58 — Peer field migrations + facade additions**
Foundational EnginePeer convenience init additions (PeerReference, RenderedPeer,
SelectivePrivacyPeer). Multiple `peer: Peer` field migrations across PeerInfo,
ChatList, and SettingsUI components.

**Waves 59-73 — peer field cascade + EnginePeer wrap drops**
Series of single- to two-file peer-field migrations; consumer-side wrap
removal (`EnginePeer(peer)` -> direct EnginePeer use); `as? TelegramUser`
cast conversion to `case let .user(...)` enum match. Wave 64: RenderedPeer
convenience init. Wave 68: SelectivePrivacyPeer convenience init.

**Waves 74-83 — controller-Node bridge cleanup + small migrations**
Wave-71 shadow-pattern cleanup at controller->Node bridges. Migrations of
ChatRecentActionsController.peer (74), PeerInfoMember (75), MentionChatInputPanelItem
(76), PassportUI SecureIdAuthController (77), AccountWithInfo + ShareController
(78), peerInputActivitiesPromise (79), InactiveChannel (80), BlockedPeers (81),
openHashtag resolveSignal (82), NotificationExceptionsList (83).

**Waves 84-90 — TelegramEngine.Resources facade migrations**
Per-method Shape-A/B sweeps converting `<ctx>.account.postbox.mediaBox.X(...)`
to `<ctx>.engine.resources.X(...)`. Wave 90 was a single-commit big sweep:
40 fetchedMediaResource sites in 25 files migrated to engine.resources.fetch
facade in one atomic pass with first-pass-clean build.

Methods covered: storeResourceData, completedResourcePath, cancelInteractiveResourceFetch,
resourceRangesStatus, resourceStatus, fetch (fetchedMediaResource).

**Waves 91-92 — additional type migrations**
Wave 91: ItemListWebsiteItem.peer + RecentSessionsController enum-case payload
+ openWebSession callback Peer? -> EnginePeer?.
Wave 92: ChatListController StateHolder.EntryContext status type
MediaResourceStatus -> EngineMediaResource.FetchStatus.

**Wave 93 — speculative `import Postbox` drop sweep**
Drop import from 7 wave-touched files where it became unused; restore in 5
files where bare PeerId/Message/MediaId/StoryId references escaped the
pre-flight regex. Includes one MediaId(...) -> EngineMedia.Id(...) swap in
InAppPurchaseManager to unlock its import drop.

## Build state

Final state at squash: clean Telegram/Telegram build at debug_sim_arm64.

## Persistent-state notes

- Pre-existing WIP unchanged across the squashed range:
  - build-system/bazel-rules/sourcekit-bazel-bsp submodule marker
  - Untracked: build-system/tulsi/, submodules/TgVoip/, third-party/libx264/

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 20:48:15 +04:00

128 lines
5.7 KiB
Swift

import Foundation
import UIKit
import AsyncDisplayKit
import Display
import TelegramCore
import SwiftSignalKit
import TelegramPresentationData
import TelegramUIPreferences
import AvatarNode
import AppBundle
import AccountContext
private let avatarFont = avatarPlaceholderFont(size: 26.0)
private let titleFont = Font.semibold(14.0)
private let textFont = Font.regular(14.0)
final class SecureIdAuthHeaderNode: ASDisplayNode {
private let context: AccountContext
private let theme: PresentationTheme
private let strings: PresentationStrings
private let nameDisplayOrder: PresentationPersonNameOrder
private let serviceAvatarNode: AvatarNode
private let titleNode: ImmediateTextNode
private let iconNode: ASImageNode
private var verificationState: SecureIdAuthControllerVerificationState?
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder) {
self.context = context
self.theme = theme
self.strings = strings
self.nameDisplayOrder = nameDisplayOrder
self.serviceAvatarNode = AvatarNode(font: avatarFont)
self.titleNode = ImmediateTextNode()
self.titleNode.maximumNumberOfLines = 0
self.titleNode.textAlignment = .center
self.iconNode = ASImageNode()
self.iconNode.isLayerBacked = true
self.iconNode.displaysAsynchronously = false
self.iconNode.displayWithoutProcessing = true
self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Secure ID/ViewPassportIcon"), color: theme.list.freeMonoIconColor)
super.init()
self.addSubnode(self.serviceAvatarNode)
self.addSubnode(self.titleNode)
self.addSubnode(self.iconNode)
}
func updateState(formData: SecureIdEncryptedFormData?, verificationState: SecureIdAuthControllerVerificationState) {
if let formData = formData {
self.serviceAvatarNode.setPeer(context: self.context, theme: self.theme, peer: formData.servicePeer)
let titleData = self.strings.Passport_RequestHeader(formData.servicePeer.displayTitle(strings: self.strings, displayOrder: self.nameDisplayOrder))
let titleString = NSMutableAttributedString()
titleString.append(NSAttributedString(string: titleData.string, font: textFont, textColor: self.theme.list.freeTextColor))
for range in titleData.ranges {
titleString.addAttribute(.font, value: titleFont, range: range.range)
}
self.titleNode.attributedText = titleString
self.iconNode.isHidden = true
} else {
self.iconNode.isHidden = false
self.titleNode.isHidden = true
self.serviceAvatarNode.isHidden = true
}
self.verificationState = verificationState
}
func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> (compact: CGFloat, expanded: CGFloat, apply: (Bool) -> Void) {
if !self.iconNode.isHidden {
guard let image = self.iconNode.image else {
return (1.0, 1.0, { _ in
})
}
return (image.size.height, image.size.height, { [weak self] _ in
guard let strongSelf = self else {
return
}
strongSelf.iconNode.frame = CGRect(origin: CGPoint(x: floor((width - image.size.width) / 2.0), y: 0.0), size: image.size)
})
} else {
let avatarSize = CGSize(width: 70.0, height: 70.0)
let avatarTitleSpacing: CGFloat = 20.0
let titleSize = self.titleNode.updateLayout(CGSize(width: width - 20.0, height: 1000.0))
if let verificationState = self.verificationState, case .noChallenge = verificationState {
self.serviceAvatarNode.isHidden = true
} else {
self.serviceAvatarNode.isHidden = false
}
var expandedHeight: CGFloat = titleSize.height
if !self.serviceAvatarNode.isHidden {
expandedHeight += avatarSize.height + avatarTitleSpacing
}
let compactHeight = titleSize.height
return (compactHeight, expandedHeight, { [weak self] expanded in
guard let strongSelf = self else {
return
}
transition.updateAlpha(node: strongSelf.serviceAvatarNode, alpha: expanded ? 1.0 : 0.0)
var titleOffset: CGFloat = 0.0
if expanded && !strongSelf.serviceAvatarNode.isHidden && !strongSelf.serviceAvatarNode.alpha.isZero {
titleOffset = avatarSize.height + avatarTitleSpacing
}
let titleFrame = CGRect(origin: CGPoint(x: floor((width - titleSize.width) / 2.0), y: titleOffset), size: titleSize)
let previousTitleFrame = strongSelf.titleNode.frame
ContainedViewLayoutTransition.immediate.updateFrame(node: strongSelf.titleNode, frame: titleFrame)
transition.animatePositionAdditive(node: strongSelf.titleNode, offset: CGPoint(x: 0.0, y: previousTitleFrame.midY - titleFrame.midY))
let serviceAvatarFrame = CGRect(origin: CGPoint(x: floor((width - avatarSize.width) / 2.0), y: titleFrame.minY - avatarTitleSpacing - avatarSize.height), size: avatarSize)
transition.updateFrame(node: strongSelf.serviceAvatarNode, frame: serviceAvatarFrame)
})
}
}
}