mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-05 19:28:46 +02:00
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>
239 lines
10 KiB
Swift
239 lines
10 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import AsyncDisplayKit
|
|
import Display
|
|
import TelegramCore
|
|
import SwiftSignalKit
|
|
import Photos
|
|
import TelegramPresentationData
|
|
import TelegramUIPreferences
|
|
import TelegramStringFormatting
|
|
import AccountContext
|
|
import GalleryUI
|
|
import AppBundle
|
|
|
|
private let deleteImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionTrash"), color: .white)
|
|
private let actionImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionForward"), color: .white)
|
|
|
|
private let nameFont = Font.medium(15.0)
|
|
private let dateFont = Font.regular(14.0)
|
|
|
|
enum AvatarGalleryItemFooterContent {
|
|
case info
|
|
case own(Bool)
|
|
}
|
|
|
|
final class AvatarGalleryItemFooterContentNode: GalleryFooterContentNode {
|
|
private let context: AccountContext
|
|
private var presentationData: PresentationData
|
|
private var strings: PresentationStrings
|
|
private var dateTimeFormat: PresentationDateTimeFormat
|
|
|
|
private let deleteButton: UIButton
|
|
private let actionButton: UIButton
|
|
private let nameNode: ASTextNode
|
|
private let dateNode: ASTextNode
|
|
private let mainNode: ASTextNode
|
|
private let setMainButton: HighlightableButtonNode
|
|
|
|
private var currentNameText: String?
|
|
private var currentDateText: String?
|
|
private var currentTypeText: String?
|
|
|
|
private var validLayout: (CGSize, LayoutMetrics, CGFloat, CGFloat, CGFloat, CGFloat)?
|
|
|
|
var delete: (() -> Void)? {
|
|
didSet {
|
|
self.deleteButton.isHidden = self.delete == nil
|
|
}
|
|
}
|
|
|
|
var share: ((GalleryControllerInteraction) -> Void)?
|
|
|
|
var setMain: (() -> Void)?
|
|
|
|
init(context: AccountContext, presentationData: PresentationData) {
|
|
self.context = context
|
|
self.presentationData = presentationData
|
|
self.strings = presentationData.strings
|
|
self.dateTimeFormat = presentationData.dateTimeFormat
|
|
|
|
self.deleteButton = UIButton()
|
|
self.deleteButton.isHidden = true
|
|
|
|
self.actionButton = UIButton()
|
|
|
|
self.deleteButton.setImage(deleteImage, for: [.normal])
|
|
self.actionButton.setImage(actionImage, for: [.normal])
|
|
|
|
self.nameNode = ASTextNode()
|
|
self.nameNode.maximumNumberOfLines = 1
|
|
self.nameNode.isUserInteractionEnabled = false
|
|
self.nameNode.displaysAsynchronously = false
|
|
|
|
self.dateNode = ASTextNode()
|
|
self.dateNode.maximumNumberOfLines = 1
|
|
self.dateNode.isUserInteractionEnabled = false
|
|
self.dateNode.displaysAsynchronously = false
|
|
|
|
self.setMainButton = HighlightableButtonNode()
|
|
self.setMainButton.isHidden = true
|
|
|
|
self.mainNode = ASTextNode()
|
|
self.mainNode.maximumNumberOfLines = 1
|
|
self.mainNode.isUserInteractionEnabled = false
|
|
self.mainNode.displaysAsynchronously = false
|
|
|
|
super.init()
|
|
|
|
self.view.addSubview(self.deleteButton)
|
|
self.view.addSubview(self.actionButton)
|
|
|
|
self.addSubnode(self.nameNode)
|
|
self.addSubnode(self.dateNode)
|
|
self.addSubnode(self.setMainButton)
|
|
self.addSubnode(self.mainNode)
|
|
|
|
self.deleteButton.addTarget(self, action: #selector(self.deleteButtonPressed), for: [.touchUpInside])
|
|
self.actionButton.addTarget(self, action: #selector(self.actionButtonPressed), for: [.touchUpInside])
|
|
self.setMainButton.addTarget(self, action: #selector(self.setMainButtonPressed), forControlEvents: .touchUpInside)
|
|
}
|
|
|
|
func setEntry(_ entry: AvatarGalleryEntry, content: AvatarGalleryItemFooterContent) {
|
|
var nameText: String?
|
|
var dateText: String?
|
|
var typeText: String?
|
|
var buttonText: String?
|
|
var canShare = true
|
|
switch entry {
|
|
case let .image(_, _, _, videoRepresentations, peer, date, _, _, _, _, isFallback, _):
|
|
if date != 0 || isFallback {
|
|
nameText = peer?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? ""
|
|
}
|
|
if let date = date, date != 0 {
|
|
dateText = humanReadableStringForTimestamp(strings: self.strings, dateTimeFormat: self.dateTimeFormat, timestamp: date).string
|
|
} else if isFallback {
|
|
dateText = !videoRepresentations.isEmpty ? self.strings.ProfilePhoto_PublicVideo : self.strings.ProfilePhoto_PublicPhoto
|
|
}
|
|
|
|
if (!videoRepresentations.isEmpty) {
|
|
typeText = self.strings.ProfilePhoto_MainVideo
|
|
buttonText = self.strings.ProfilePhoto_SetMainVideo
|
|
} else {
|
|
typeText = self.strings.ProfilePhoto_MainPhoto
|
|
buttonText = self.strings.ProfilePhoto_SetMainPhoto
|
|
}
|
|
|
|
if let peer = peer {
|
|
canShare = !peer.isCopyProtectionEnabled
|
|
}
|
|
default:
|
|
break
|
|
}
|
|
|
|
if self.currentNameText != nameText || self.currentDateText != dateText {
|
|
self.currentNameText = nameText
|
|
self.currentDateText = dateText
|
|
|
|
if let nameText = nameText {
|
|
self.nameNode.attributedText = NSAttributedString(string: nameText, font: nameFont, textColor: .white)
|
|
} else {
|
|
self.nameNode.attributedText = nil
|
|
}
|
|
|
|
if let dateText = dateText {
|
|
self.dateNode.attributedText = NSAttributedString(string: dateText, font: dateFont, textColor: .white)
|
|
} else {
|
|
self.dateNode.attributedText = nil
|
|
}
|
|
}
|
|
|
|
if self.currentTypeText != typeText {
|
|
self.currentTypeText = typeText
|
|
|
|
self.mainNode.attributedText = NSAttributedString(string: typeText ?? "", font: Font.regular(17.0), textColor: UIColor(rgb: 0x808080))
|
|
self.setMainButton.setAttributedTitle(NSAttributedString(string: buttonText ?? "", font: Font.regular(17.0), textColor: .white), for: .normal)
|
|
|
|
if let validLayout = self.validLayout {
|
|
let _ = self.updateLayout(size: validLayout.0, metrics: validLayout.1, leftInset: validLayout.2, rightInset: validLayout.3, bottomInset: validLayout.4, contentInset: validLayout.5, transition: .immediate)
|
|
}
|
|
}
|
|
|
|
self.actionButton.isHidden = !canShare
|
|
|
|
switch content {
|
|
case .info:
|
|
self.nameNode.isHidden = false
|
|
self.dateNode.isHidden = false
|
|
self.mainNode.isHidden = true
|
|
self.setMainButton.isHidden = true
|
|
case let .own(isMainPhoto):
|
|
self.nameNode.isHidden = true
|
|
self.dateNode.isHidden = true
|
|
self.mainNode.isHidden = !isMainPhoto
|
|
self.setMainButton.isHidden = isMainPhoto
|
|
}
|
|
}
|
|
|
|
override func updateLayout(size: CGSize, metrics: LayoutMetrics, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, contentInset: CGFloat, transition: ContainedViewLayoutTransition) -> LayoutInfo {
|
|
self.validLayout = (size, metrics, leftInset, rightInset, bottomInset, contentInset)
|
|
|
|
let width = size.width
|
|
var panelHeight: CGFloat = 44.0 + bottomInset
|
|
panelHeight += contentInset
|
|
|
|
self.actionButton.frame = CGRect(origin: CGPoint(x: leftInset, y: panelHeight - bottomInset - 44.0), size: CGSize(width: 44.0, height: 44.0))
|
|
self.deleteButton.frame = CGRect(origin: CGPoint(x: width - 44.0 - rightInset, y: panelHeight - bottomInset - 44.0), size: CGSize(width: 44.0, height: 44.0))
|
|
|
|
let constrainedSize = CGSize(width: width - 44.0 * 2.0 - 8.0 * 2.0 - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude)
|
|
let nameSize = self.nameNode.measure(constrainedSize)
|
|
let dateSize = self.dateNode.measure(constrainedSize)
|
|
|
|
if nameSize.height.isZero {
|
|
self.dateNode.frame = CGRect(origin: CGPoint(x: floor((width - dateSize.width) / 2.0), y: panelHeight - bottomInset - 44.0 + floor((44.0 - dateSize.height) / 2.0)), size: dateSize)
|
|
} else {
|
|
let labelsSpacing: CGFloat = 0.0
|
|
self.nameNode.frame = CGRect(origin: CGPoint(x: floor((width - nameSize.width) / 2.0), y: panelHeight - bottomInset - 44.0 + floor((44.0 - dateSize.height - nameSize.height - labelsSpacing) / 2.0)), size: nameSize)
|
|
self.dateNode.frame = CGRect(origin: CGPoint(x: floor((width - dateSize.width) / 2.0), y: panelHeight - bottomInset - 44.0 + floor((44.0 - dateSize.height - nameSize.height - labelsSpacing) / 2.0) + nameSize.height + labelsSpacing), size: dateSize)
|
|
}
|
|
|
|
let mainSize = self.mainNode.measure(constrainedSize)
|
|
self.mainNode.frame = CGRect(origin: CGPoint(x: floor((width - mainSize.width) / 2.0), y: panelHeight - bottomInset - 44.0 + floor((44.0 - mainSize.height) / 2.0)), size: mainSize)
|
|
|
|
let mainButtonSize = self.setMainButton.measure(constrainedSize)
|
|
self.setMainButton.frame = CGRect(origin: CGPoint(x: floor((width - mainButtonSize.width) / 2.0), y: panelHeight - bottomInset - 44.0 + floor((44.0 - mainButtonSize.height) / 2.0)), size: mainButtonSize)
|
|
|
|
return LayoutInfo(height: panelHeight, needsShadow: false)
|
|
}
|
|
|
|
override func animateIn(fromHeight: CGFloat, previousContentNode: GalleryFooterContentNode, transition: ContainedViewLayoutTransition) {
|
|
self.deleteButton.alpha = 1.0
|
|
self.actionButton.alpha = 1.0
|
|
self.nameNode.alpha = 1.0
|
|
self.dateNode.alpha = 1.0
|
|
self.setMainButton.alpha = 1.0
|
|
}
|
|
|
|
override func animateOut(toHeight: CGFloat, nextContentNode: GalleryFooterContentNode, transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) {
|
|
self.deleteButton.alpha = 0.0
|
|
self.actionButton.alpha = 0.0
|
|
self.nameNode.alpha = 0.0
|
|
self.dateNode.alpha = 0.0
|
|
self.setMainButton.alpha = 0.0
|
|
completion()
|
|
}
|
|
|
|
@objc private func deleteButtonPressed() {
|
|
self.delete?()
|
|
}
|
|
|
|
@objc private func actionButtonPressed() {
|
|
if let controllerInteraction = self.controllerInteraction {
|
|
self.share?(controllerInteraction)
|
|
}
|
|
}
|
|
|
|
@objc private func setMainButtonPressed() {
|
|
self.setMain?()
|
|
}
|
|
}
|