Ilya Laktyushin f137e27944 Update API
2025-01-16 02:16:28 +04:00

2473 lines
142 KiB
Swift

import Foundation
import UIKit
import AsyncDisplayKit
import Display
import Postbox
import TelegramCore
import AvatarNode
import AccountContext
import SwiftSignalKit
import TelegramPresentationData
import PhotoResources
import PeerAvatarGalleryUI
import TelegramStringFormatting
import PhoneNumberFormat
import ActivityIndicator
import TelegramUniversalVideoContent
import GalleryUI
import UniversalMediaPlayer
import RadialStatusNode
import TelegramUIPreferences
import PeerInfoAvatarListNode
import AnimationUI
import ContextUI
import ManagedAnimationNode
import ComponentFlow
import EmojiStatusComponent
import AnimationCache
import MultiAnimationRenderer
import ComponentDisplayAdapters
import ChatTitleView
import AppBundle
import AvatarVideoNode
import PeerInfoVisualMediaPaneNode
import AvatarStoryIndicatorComponent
import ComponentDisplayAdapters
import ChatAvatarNavigationNode
import MultiScaleTextNode
import PeerInfoCoverComponent
import PeerInfoPaneNode
import MultilineTextComponent
final class PeerInfoHeaderNavigationTransition {
let sourceNavigationBar: NavigationBar
let sourceTitleView: ChatTitleView
let sourceTitleFrame: CGRect
let sourceSubtitleFrame: CGRect
let previousAvatarView: UIView?
let fraction: CGFloat
init(sourceNavigationBar: NavigationBar, sourceTitleView: ChatTitleView, sourceTitleFrame: CGRect, sourceSubtitleFrame: CGRect, previousAvatarView: UIView?, fraction: CGFloat) {
self.sourceNavigationBar = sourceNavigationBar
self.sourceTitleView = sourceTitleView
self.sourceTitleFrame = sourceTitleFrame
self.sourceSubtitleFrame = sourceSubtitleFrame
self.previousAvatarView = previousAvatarView
self.fraction = fraction
}
}
final class PeerInfoHeaderRegularContentNode: ASDisplayNode {
}
enum PeerInfoHeaderTextFieldNodeKey: Equatable {
case firstName
case lastName
case title
case description
}
protocol PeerInfoHeaderTextFieldNode: ASDisplayNode {
var text: String { get }
func update(width: CGFloat, safeInset: CGFloat, isSettings: Bool, hasPrevious: Bool, hasNext: Bool, placeholder: String, isEnabled: Bool, presentationData: PresentationData, updateText: String?) -> CGFloat
}
private let TitleNodeStateRegular = 0
private let TitleNodeStateExpanded = 1
final class PeerInfoHeaderNode: ASDisplayNode {
private var context: AccountContext
private let isPremiumDisabled: Bool
private weak var controller: PeerInfoScreenImpl?
private var presentationData: PresentationData?
private var state: PeerInfoState?
private var peer: Peer?
private var threadData: MessageHistoryThreadData?
private var avatarSize: CGFloat?
private let isOpenedFromChat: Bool
private let isSettings: Bool
private let isMyProfile: Bool
private let videoCallsEnabled: Bool
private let forumTopicThreadId: Int64?
private let chatLocation: ChatLocation
private(set) var isAvatarExpanded: Bool
var skipCollapseCompletion = false
var ignoreCollapse = false
let avatarClippingNode: SparseNode
let avatarListNode: PeerInfoAvatarListNode
let backgroundBannerView: UIView
let backgroundCover = ComponentView<Empty>()
var didSetupBackgroundCover = false
let buttonsContainerNode: SparseNode
let buttonsBackgroundNode: NavigationBackgroundNode
let buttonsMaskView: UIView
let regularContentNode: PeerInfoHeaderRegularContentNode
let editingContentNode: PeerInfoHeaderEditingContentNode
let avatarOverlayNode: PeerInfoEditingAvatarOverlayNode
let titleNodeContainer: ASDisplayNode
let titleNodeRawContainer: ASDisplayNode
let titleNode: MultiScaleTextNode
var standardTitle: ComponentView<Empty>?
let titleCredibilityIconView: ComponentHostView<Empty>
var credibilityIconSize: CGSize?
let titleExpandedCredibilityIconView: ComponentHostView<Empty>
var titleExpandedCredibilityIconSize: CGSize?
let titleVerifiedIconView: ComponentHostView<Empty>
var verifiedIconSize: CGSize?
let titleExpandedVerifiedIconView: ComponentHostView<Empty>
var titleExpandedVerifiedIconSize: CGSize?
let titleStatusIconView: ComponentHostView<Empty>
var statusIconSize: CGSize?
let titleExpandedStatusIconView: ComponentHostView<Empty>
var titleExpandedStatusIconSize: CGSize?
let subtitleNodeContainer: ASDisplayNode
let subtitleNodeRawContainer: ASDisplayNode
let subtitleNode: MultiScaleTextNode
var subtitleBackgroundNode: ASDisplayNode?
var subtitleBackgroundButton: HighlightTrackingButtonNode?
var subtitleArrowNode: ASImageNode?
var subtitleBadgeView: PeerInfoSubtitleBadgeView?
let panelSubtitleNode: MultiScaleTextNode
let usernameNodeContainer: ASDisplayNode
let usernameNodeRawContainer: ASDisplayNode
let usernameNode: MultiScaleTextNode
var actionButtonNodes: [PeerInfoHeaderButtonKey: PeerInfoHeaderActionButtonNode] = [:]
var buttonNodes: [PeerInfoHeaderButtonKey: PeerInfoHeaderButtonNode] = [:]
let backgroundNode: NavigationBackgroundNode
let expandedBackgroundNode: NavigationBackgroundNode
let separatorNode: ASDisplayNode
let navigationBackgroundNode: ASDisplayNode
let navigationBackgroundBackgroundNode: ASDisplayNode
var navigationTitle: String?
let navigationTitleNode: ImmediateTextNode
let navigationSeparatorNode: ASDisplayNode
let navigationButtonContainer: PeerInfoHeaderNavigationButtonContainerNode
let editingNavigationBackgroundNode: NavigationBackgroundNode
let editingNavigationBackgroundSeparator: ASDisplayNode
var performButtonAction: ((PeerInfoHeaderButtonKey, ContextGesture?) -> Void)?
var requestAvatarExpansion: ((Bool, [AvatarGalleryEntry], AvatarGalleryEntry?, (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?) -> Void)?
var requestOpenAvatarForEditing: ((Bool) -> Void)?
var cancelUpload: (() -> Void)?
var requestUpdateLayout: ((Bool) -> Void)?
var animateOverlaysFadeIn: (() -> Void)?
var displayAvatarContextMenu: ((ASDisplayNode, ContextGesture?) -> Void)?
var displayCopyContextMenu: ((ASDisplayNode, Bool, Bool) -> Void)?
var displayEmojiPackTooltip: (() -> Void)?
var displayPremiumIntro: ((UIView, PeerEmojiStatus?, Signal<(TelegramMediaFile, LoadedStickerPack)?, NoError>, Bool) -> Void)?
var displayStatusPremiumIntro: (() -> Void)?
var displayUniqueGiftInfo: ((UIView, String) -> Void)?
var openUniqueGift: ((String) -> Void)?
var navigateToForum: (() -> Void)?
var navigationTransition: PeerInfoHeaderNavigationTransition?
var backgroundAlpha: CGFloat = 1.0
var updateHeaderAlpha: ((CGFloat, ContainedViewLayoutTransition) -> Void)?
private(set) var contentButtonBackgroundColor: UIColor?
let animationCache: AnimationCache
let animationRenderer: MultiAnimationRenderer
var emojiStatusPackDisposable = MetaDisposable()
var emojiStatusFileAndPackTitle = Promise<(TelegramMediaFile, LoadedStickerPack)?>()
var customNavigationContentNode: PeerInfoPanelNodeNavigationContentNode?
private var appliedCustomNavigationContentNode: PeerInfoPanelNodeNavigationContentNode?
private var validLayout: (width: CGFloat, statusBarHeight: CGFloat, deviceMetrics: DeviceMetrics)?
init(context: AccountContext, controller: PeerInfoScreenImpl, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, isMediaOnly: Bool, isSettings: Bool, isMyProfile: Bool, forumTopicThreadId: Int64?, chatLocation: ChatLocation) {
self.context = context
self.controller = controller
self.isAvatarExpanded = avatarInitiallyExpanded
self.isOpenedFromChat = isOpenedFromChat
self.isSettings = isSettings
self.isMyProfile = isMyProfile
self.videoCallsEnabled = true
self.forumTopicThreadId = forumTopicThreadId
self.chatLocation = chatLocation
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
self.isPremiumDisabled = premiumConfiguration.isPremiumDisabled
self.avatarClippingNode = SparseNode()
self.avatarClippingNode.alpha = 0.996
self.avatarClippingNode.clipsToBounds = true
self.avatarListNode = PeerInfoAvatarListNode(context: context, readyWhenGalleryLoads: avatarInitiallyExpanded, isSettings: isSettings)
self.titleNodeContainer = ASDisplayNode()
self.titleNodeRawContainer = ASDisplayNode()
self.titleNode = MultiScaleTextNode(stateKeys: [TitleNodeStateRegular, TitleNodeStateExpanded])
self.titleNode.displaysAsynchronously = false
self.titleCredibilityIconView = ComponentHostView<Empty>()
self.titleNode.stateNode(forKey: TitleNodeStateRegular)?.view.addSubview(self.titleCredibilityIconView)
self.titleExpandedCredibilityIconView = ComponentHostView<Empty>()
self.titleNode.stateNode(forKey: TitleNodeStateExpanded)?.view.addSubview(self.titleExpandedCredibilityIconView)
self.titleVerifiedIconView = ComponentHostView<Empty>()
self.titleNode.stateNode(forKey: TitleNodeStateRegular)?.view.addSubview(self.titleVerifiedIconView)
self.titleExpandedVerifiedIconView = ComponentHostView<Empty>()
self.titleNode.stateNode(forKey: TitleNodeStateExpanded)?.view.addSubview(self.titleExpandedVerifiedIconView)
self.titleStatusIconView = ComponentHostView<Empty>()
self.titleNode.stateNode(forKey: TitleNodeStateRegular)?.view.addSubview(self.titleStatusIconView)
self.titleExpandedStatusIconView = ComponentHostView<Empty>()
self.titleNode.stateNode(forKey: TitleNodeStateExpanded)?.view.addSubview(self.titleExpandedStatusIconView)
self.subtitleNodeContainer = ASDisplayNode()
self.subtitleNodeRawContainer = ASDisplayNode()
self.subtitleNode = MultiScaleTextNode(stateKeys: [TitleNodeStateRegular, TitleNodeStateExpanded])
self.subtitleNode.displaysAsynchronously = false
self.panelSubtitleNode = MultiScaleTextNode(stateKeys: [TitleNodeStateRegular, TitleNodeStateExpanded])
self.panelSubtitleNode.displaysAsynchronously = false
self.usernameNodeContainer = ASDisplayNode()
self.usernameNodeRawContainer = ASDisplayNode()
self.usernameNode = MultiScaleTextNode(stateKeys: [TitleNodeStateRegular, TitleNodeStateExpanded])
self.usernameNode.displaysAsynchronously = false
self.backgroundBannerView = UIView()
self.backgroundBannerView.clipsToBounds = true
self.backgroundBannerView.isUserInteractionEnabled = false
self.buttonsContainerNode = SparseNode()
self.buttonsContainerNode.clipsToBounds = true
self.buttonsBackgroundNode = NavigationBackgroundNode(color: .clear, enableBlur: true, enableSaturation: false)
self.buttonsContainerNode.addSubnode(self.buttonsBackgroundNode)
self.buttonsMaskView = UIView()
self.buttonsBackgroundNode.view.mask = self.buttonsMaskView
self.regularContentNode = PeerInfoHeaderRegularContentNode()
var requestUpdateLayoutImpl: (() -> Void)?
self.editingContentNode = PeerInfoHeaderEditingContentNode(context: context, requestUpdateLayout: {
requestUpdateLayoutImpl?()
})
self.editingContentNode.alpha = 0.0
self.avatarOverlayNode = PeerInfoEditingAvatarOverlayNode(context: context)
self.avatarOverlayNode.isUserInteractionEnabled = false
self.navigationBackgroundNode = ASDisplayNode()
self.navigationBackgroundNode.isHidden = true
self.navigationBackgroundNode.isUserInteractionEnabled = false
self.navigationBackgroundBackgroundNode = ASDisplayNode()
self.navigationBackgroundBackgroundNode.isUserInteractionEnabled = false
self.navigationTitleNode = ImmediateTextNode()
self.navigationSeparatorNode = ASDisplayNode()
self.navigationButtonContainer = PeerInfoHeaderNavigationButtonContainerNode()
self.editingNavigationBackgroundNode = NavigationBackgroundNode(color: .clear, enableBlur: true)
self.editingNavigationBackgroundSeparator = ASDisplayNode()
self.backgroundNode = NavigationBackgroundNode(color: .clear)
self.backgroundNode.isHidden = true
self.backgroundNode.isUserInteractionEnabled = false
self.expandedBackgroundNode = NavigationBackgroundNode(color: .clear)
self.expandedBackgroundNode.isHidden = false
self.expandedBackgroundNode.isUserInteractionEnabled = false
self.separatorNode = ASDisplayNode()
self.separatorNode.isLayerBacked = true
self.animationCache = context.animationCache
self.animationRenderer = context.animationRenderer
super.init()
requestUpdateLayoutImpl = { [weak self] in
self?.requestUpdateLayout?(false)
}
self.addSubnode(self.backgroundNode)
self.addSubnode(self.expandedBackgroundNode)
self.view.addSubview(self.backgroundBannerView)
self.titleNodeContainer.addSubnode(self.titleNode)
self.subtitleNodeContainer.addSubnode(self.subtitleNode)
self.subtitleNodeContainer.addSubnode(self.panelSubtitleNode)
self.usernameNodeContainer.addSubnode(self.usernameNode)
self.regularContentNode.addSubnode(self.avatarClippingNode)
self.avatarClippingNode.addSubnode(self.avatarListNode)
self.regularContentNode.addSubnode(self.avatarListNode.listContainerNode.controlsClippingOffsetNode)
self.regularContentNode.addSubnode(self.titleNodeContainer)
self.regularContentNode.addSubnode(self.subtitleNodeContainer)
self.regularContentNode.addSubnode(self.subtitleNodeRawContainer)
self.regularContentNode.addSubnode(self.usernameNodeContainer)
self.regularContentNode.addSubnode(self.usernameNodeRawContainer)
self.addSubnode(self.regularContentNode)
if !isMediaOnly {
self.regularContentNode.addSubnode(self.buttonsContainerNode)
}
self.addSubnode(self.editingContentNode)
self.addSubnode(self.avatarOverlayNode)
self.addSubnode(self.navigationBackgroundNode)
self.navigationBackgroundNode.addSubnode(self.navigationBackgroundBackgroundNode)
self.navigationBackgroundNode.addSubnode(self.navigationTitleNode)
self.navigationBackgroundNode.addSubnode(self.navigationSeparatorNode)
self.addSubnode(self.editingNavigationBackgroundNode)
self.addSubnode(self.editingNavigationBackgroundSeparator)
self.addSubnode(self.navigationButtonContainer)
self.addSubnode(self.separatorNode)
self.avatarListNode.avatarContainerNode.tapped = { [weak self] in
self?.initiateAvatarExpansion(gallery: false, first: false)
}
self.avatarListNode.avatarContainerNode.contextAction = { [weak self] node, gesture in
self?.displayAvatarContextMenu?(node, gesture)
}
self.avatarListNode.avatarContainerNode.emojiTapped = { [weak self] in
self?.displayEmojiPackTooltip?()
}
self.editingContentNode.avatarNode.tapped = { [weak self] confirm in
self?.initiateAvatarExpansion(gallery: true, first: true)
}
self.editingContentNode.requestEditing = { [weak self] in
self?.requestOpenAvatarForEditing?(true)
}
self.avatarListNode.itemsUpdated = { [weak self] items in
guard let strongSelf = self, let state = strongSelf.state, let peer = strongSelf.peer, let presentationData = strongSelf.presentationData, let avatarSize = strongSelf.avatarSize else {
return
}
strongSelf.editingContentNode.avatarNode.update(peer: peer, threadData: strongSelf.threadData, chatLocation: chatLocation, item: strongSelf.avatarListNode.item, updatingAvatar: state.updatingAvatar, uploadProgress: state.avatarUploadProgress, theme: presentationData.theme, avatarSize: avatarSize, isEditing: state.isEditing)
}
self.avatarListNode.animateOverlaysFadeIn = { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.navigationButtonContainer.layer.animateAlpha(from: 0.0, to: strongSelf.navigationButtonContainer.alpha, duration: 0.25)
strongSelf.avatarListNode.listContainerNode.topShadowNode.layer.animateAlpha(from: 0.0, to: strongSelf.avatarListNode.listContainerNode.topShadowNode.alpha, duration: 0.25)
strongSelf.avatarListNode.listContainerNode.bottomShadowNode.alpha = 1.0
strongSelf.avatarListNode.listContainerNode.bottomShadowNode.layer.animateAlpha(from: 0.0, to: strongSelf.avatarListNode.listContainerNode.bottomShadowNode.alpha, duration: 0.25)
strongSelf.avatarListNode.listContainerNode.controlsContainerNode.layer.animateAlpha(from: 0.0, to: strongSelf.avatarListNode.listContainerNode.controlsContainerNode.alpha, duration: 0.25)
strongSelf.titleNode.layer.animateAlpha(from: 0.0, to: strongSelf.titleNode.alpha, duration: 0.25)
strongSelf.subtitleNode.layer.animateAlpha(from: 0.0, to: strongSelf.subtitleNode.alpha, duration: 0.25)
strongSelf.animateOverlaysFadeIn?()
}
}
deinit {
self.emojiStatusPackDisposable.dispose()
}
override func didLoad() {
super.didLoad()
let usernameGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(self.handleUsernameLongPress(_:)))
self.usernameNodeRawContainer.view.addGestureRecognizer(usernameGestureRecognizer)
let phoneGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(self.handlePhoneLongPress(_:)))
self.subtitleNodeRawContainer.view.addGestureRecognizer(phoneGestureRecognizer)
}
@objc private func handleUsernameLongPress(_ gestureRecognizer: UILongPressGestureRecognizer) {
if gestureRecognizer.state == .began {
self.displayCopyContextMenu?(self.usernameNodeRawContainer, !self.isAvatarExpanded, true)
}
}
@objc private func handlePhoneLongPress(_ gestureRecognizer: UILongPressGestureRecognizer) {
if gestureRecognizer.state == .began {
self.displayCopyContextMenu?(self.subtitleNodeRawContainer, true, !self.isAvatarExpanded)
}
}
@objc private func subtitleBackgroundPressed() {
self.navigateToForum?()
}
func invokeDisplayPremiumIntro() {
self.displayPremiumIntro?(self.isAvatarExpanded ? self.titleExpandedCredibilityIconView : self.titleCredibilityIconView, nil, .never(), self.isAvatarExpanded)
}
func invokeDisplayGiftInfo() {
guard case let .emojiStatus(status) = self.currentStatusIcon, case let .starGift(_, _, title, _, _, _, _, _, _) = status.content else {
return
}
self.displayUniqueGiftInfo?(self.isAvatarExpanded ? self.titleExpandedStatusIconView : self.titleStatusIconView, title)
}
func initiateAvatarExpansion(gallery: Bool, first: Bool) {
if let peer = self.peer, peer.profileImageRepresentations.isEmpty && gallery {
self.requestOpenAvatarForEditing?(false)
return
}
if self.isAvatarExpanded || gallery {
if let currentEntry = self.avatarListNode.listContainerNode.currentEntry, let firstEntry = self.avatarListNode.listContainerNode.galleryEntries.first {
let entry = first ? firstEntry : currentEntry
self.requestAvatarExpansion?(true, self.avatarListNode.listContainerNode.galleryEntries, entry, self.avatarTransitionArguments(entry: currentEntry))
}
} else if let entry = self.avatarListNode.listContainerNode.galleryEntries.first {
self.requestAvatarExpansion?(false, self.avatarListNode.listContainerNode.galleryEntries, nil, self.avatarTransitionArguments(entry: entry))
} else if let storyParams = self.avatarListNode.listContainerNode.storyParams, storyParams.count != 0 {
self.requestAvatarExpansion?(false, self.avatarListNode.listContainerNode.galleryEntries, nil, nil)
} else {
self.cancelUpload?()
}
}
func avatarTransitionArguments(entry: AvatarGalleryEntry) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? {
if self.isAvatarExpanded {
if let avatarNode = self.avatarListNode.listContainerNode.currentItemNode?.imageNode {
return (avatarNode, avatarNode.bounds, { [weak avatarNode] in
return (avatarNode?.view.snapshotContentTree(unhide: true), nil)
})
} else {
return nil
}
} else if entry == self.avatarListNode.listContainerNode.galleryEntries.first {
let avatarNode = self.avatarListNode.avatarContainerNode.avatarNode
return (avatarNode, avatarNode.bounds, { [weak avatarNode] in
return (avatarNode?.view.snapshotContentTree(unhide: true), nil)
})
} else {
return nil
}
}
func addToAvatarTransitionSurface(view: UIView) {
if self.isAvatarExpanded {
self.avatarListNode.listContainerNode.view.addSubview(view)
} else {
self.view.addSubview(view)
}
}
func updateAvatarIsHidden(entry: AvatarGalleryEntry?) {
if let entry = entry {
self.avatarListNode.avatarContainerNode.containerNode.isHidden = entry == self.avatarListNode.listContainerNode.galleryEntries.first
self.editingContentNode.avatarNode.isHidden = entry == self.avatarListNode.listContainerNode.galleryEntries.first
} else {
self.avatarListNode.avatarContainerNode.containerNode.isHidden = false
self.editingContentNode.avatarNode.isHidden = false
}
self.avatarListNode.listContainerNode.updateEntryIsHidden(entry: entry)
}
private enum CredibilityIcon: Equatable {
case none
case premium
case verified
case fake
case scam
case emojiStatus(PeerEmojiStatus)
}
private var currentCredibilityIcon: CredibilityIcon?
private var currentVerifiedIcon: CredibilityIcon?
private var currentStatusIcon: CredibilityIcon?
private var currentPanelStatusData: PeerInfoStatusData?
func update(width: CGFloat, containerHeight: CGFloat, containerInset: CGFloat, statusBarHeight: CGFloat, navigationHeight: CGFloat, isModalOverlay: Bool, isMediaOnly: Bool, contentOffset: CGFloat, paneContainerY: CGFloat, presentationData: PresentationData, peer: Peer?, cachedData: CachedPeerData?, threadData: MessageHistoryThreadData?, peerNotificationSettings: TelegramPeerNotificationSettings?, threadNotificationSettings: TelegramPeerNotificationSettings?, globalNotificationSettings: EngineGlobalNotificationSettings?, statusData: PeerInfoStatusData?, panelStatusData: (PeerInfoStatusData?, PeerInfoStatusData?, CGFloat?), isSecretChat: Bool, isContact: Bool, isSettings: Bool, state: PeerInfoState, metrics: LayoutMetrics, deviceMetrics: DeviceMetrics, transition: ContainedViewLayoutTransition, additive: Bool, animateHeader: Bool) -> CGFloat {
if self.appliedCustomNavigationContentNode !== self.customNavigationContentNode {
if let previous = self.appliedCustomNavigationContentNode {
transition.updateAlpha(node: previous, alpha: 0.0, completion: { [weak previous] _ in
previous?.removeFromSupernode()
})
}
self.appliedCustomNavigationContentNode = self.customNavigationContentNode
if let customNavigationContentNode = self.customNavigationContentNode {
self.addSubnode(customNavigationContentNode)
customNavigationContentNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: navigationHeight))
customNavigationContentNode.alpha = 0.0
transition.updateAlpha(node: customNavigationContentNode, alpha: 1.0)
}
} else if let customNavigationContentNode = self.customNavigationContentNode {
transition.updateFrame(node: customNavigationContentNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: navigationHeight)))
}
var threadData = threadData
if case let .replyThread(replyThreadMessage) = self.chatLocation, replyThreadMessage.peerId == self.context.account.peerId {
threadData = nil
}
self.state = state
self.peer = peer
self.threadData = threadData
self.avatarListNode.listContainerNode.peer = peer.flatMap(EnginePeer.init)
let isFirstTime = self.validLayout == nil
self.validLayout = (width, statusBarHeight, deviceMetrics)
let previousPanelStatusData = self.currentPanelStatusData
self.currentPanelStatusData = panelStatusData.0
let avatarSize: CGFloat = isModalOverlay ? 200.0 : 100.0
self.avatarSize = avatarSize
var contentOffset = contentOffset
if isMediaOnly {
if isModalOverlay {
contentOffset = 312.0
} else {
contentOffset = 212.0
}
}
let isLandscape = containerInset > 16.0
let themeUpdated = self.presentationData?.theme !== presentationData.theme
self.presentationData = presentationData
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: self.context.currentAppConfiguration.with { $0 })
var credibilityIcon: CredibilityIcon = .none
var verifiedIcon: CredibilityIcon = .none
var statusIcon: CredibilityIcon = .none
if let peer {
if peer.id == self.context.account.peerId && !self.isSettings && !self.isMyProfile {
credibilityIcon = .none
} else if peer.isFake {
credibilityIcon = .fake
} else if peer.isScam {
credibilityIcon = .scam
} else if let emojiStatus = peer.emojiStatus, !premiumConfiguration.isPremiumDisabled {
statusIcon = .emojiStatus(emojiStatus)
} else if peer.isPremium && !premiumConfiguration.isPremiumDisabled && (peer.id != self.context.account.peerId || self.isSettings || self.isMyProfile) {
credibilityIcon = .premium
} else {
credibilityIcon = .none
}
if peer.isVerified {
credibilityIcon = .verified
}
if let verificationIconFileId = peer.verificationIconFileId {
verifiedIcon = .emojiStatus(PeerEmojiStatus(content: .emoji(fileId: verificationIconFileId), expirationDate: nil))
}
}
var isForum = false
if let channel = peer as? TelegramChannel, channel.flags.contains(.isForum) {
isForum = true
}
transition.updateAlpha(node: self.regularContentNode, alpha: (state.isEditing || self.customNavigationContentNode != nil) ? 0.0 : 1.0)
if self.navigationTransition == nil {
transition.updateAlpha(node: self.navigationButtonContainer, alpha: self.customNavigationContentNode != nil ? 0.0 : 1.0)
}
self.editingContentNode.alpha = state.isEditing ? 1.0 : 0.0
let editingContentHeight = self.editingContentNode.update(width: width, safeInset: containerInset, statusBarHeight: statusBarHeight, navigationHeight: navigationHeight, isModalOverlay: isModalOverlay, peer: state.isEditing ? peer : nil, threadData: threadData, chatLocation: self.chatLocation, cachedData: cachedData, isContact: isContact, isSettings: isSettings || isMyProfile, presentationData: presentationData, transition: transition)
transition.updateFrame(node: self.editingContentNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -contentOffset), size: CGSize(width: width, height: editingContentHeight)))
let avatarOverlayFarme = self.editingContentNode.convert(self.editingContentNode.avatarNode.frame, to: self)
transition.updateFrame(node: self.avatarOverlayNode, frame: avatarOverlayFarme)
var transitionSourceHeight: CGFloat = 0.0
var transitionFraction: CGFloat = 0.0
var transitionSourceAvatarFrame: CGRect?
var transitionSourceTitleFrame = CGRect()
var transitionSourceSubtitleFrame = CGRect()
let avatarFrame = CGRect(origin: CGPoint(x: floor((width - avatarSize) / 2.0), y: statusBarHeight + 22.0), size: CGSize(width: avatarSize, height: avatarSize))
self.backgroundNode.updateColor(color: presentationData.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate)
let headerBackgroundColor: UIColor = presentationData.theme.list.blocksBackgroundColor
let regularNavigationContentsAccentColor: UIColor = peer?.profileColor != nil ? .white : presentationData.theme.list.itemAccentColor
let collapsedHeaderNavigationContentsAccentColor = presentationData.theme.list.itemAccentColor
let expandedAvatarNavigationContentsAccentColor: UIColor = .white
let regularNavigationContentsPrimaryColor: UIColor = peer?.profileColor != nil ? .white : presentationData.theme.list.itemPrimaryTextColor
let collapsedHeaderNavigationContentsPrimaryColor = presentationData.theme.list.itemPrimaryTextColor
let expandedAvatarNavigationContentsPrimaryColor: UIColor = .white
let regularContentButtonBackgroundColor: UIColor
let collapsedHeaderContentButtonBackgroundColor = presentationData.theme.list.itemBlocksBackgroundColor
let expandedAvatarContentButtonBackgroundColor: UIColor = UIColor(white: 0.0, alpha: 0.1)
let regularHeaderButtonBackgroundColor: UIColor
let collapsedHeaderButtonBackgroundColor: UIColor = .clear
let expandedAvatarHeaderButtonBackgroundColor: UIColor = UIColor(white: 0.0, alpha: 0.1)
let regularContentButtonForegroundColor: UIColor = peer?.profileColor != nil ? UIColor.white : presentationData.theme.list.itemAccentColor
let collapsedHeaderContentButtonForegroundColor = presentationData.theme.list.itemAccentColor
let expandedAvatarContentButtonForegroundColor: UIColor = .white
let regularNavigationContentsSecondaryColor: UIColor
if let emojiStatus = peer?.emojiStatus, case let .starGift(_, _, _, _, _, innerColor, outerColor, _, _) = emojiStatus.content {
let mainColor = UIColor(rgb: UInt32(bitPattern: innerColor))
let secondaryColor = UIColor(rgb: UInt32(bitPattern: outerColor))
regularNavigationContentsSecondaryColor = UIColor(white: 1.0, alpha: 0.6).blitOver(mainColor.withMultiplied(hue: 1.0, saturation: 2.2, brightness: 1.5), alpha: 1.0)
let baseButtonBackgroundColor: UIColor
if presentationData.theme.overallDarkAppearance {
baseButtonBackgroundColor = UIColor(white: 0.0, alpha: 0.25)
} else {
baseButtonBackgroundColor = UIColor(white: 1.0, alpha: 0.25)
}
regularContentButtonBackgroundColor = baseButtonBackgroundColor.blendOver(background: mainColor)
regularHeaderButtonBackgroundColor = baseButtonBackgroundColor.blendOver(background: secondaryColor.mixedWith(mainColor, alpha: 0.1))
} else if let profileColor = peer?.profileColor {
let backgroundColors = self.context.peerNameColors.getProfile(profileColor, dark: presentationData.theme.overallDarkAppearance)
regularNavigationContentsSecondaryColor = UIColor(white: 1.0, alpha: 0.6).blitOver(backgroundColors.main.withMultiplied(hue: 1.0, saturation: 2.2, brightness: 1.5), alpha: 1.0)
let baseButtonBackgroundColor: UIColor
if presentationData.theme.overallDarkAppearance {
baseButtonBackgroundColor = UIColor(white: 0.0, alpha: 0.25)
} else {
baseButtonBackgroundColor = UIColor(white: 1.0, alpha: 0.25)
}
regularContentButtonBackgroundColor = baseButtonBackgroundColor.blendOver(background: backgroundColors.main)
regularHeaderButtonBackgroundColor = baseButtonBackgroundColor.blendOver(background: (backgroundColors.secondary ?? backgroundColors.main).mixedWith(backgroundColors.main, alpha: 0.1))
} else {
regularNavigationContentsSecondaryColor = presentationData.theme.list.itemSecondaryTextColor
regularContentButtonBackgroundColor = presentationData.theme.list.itemBlocksBackgroundColor
regularHeaderButtonBackgroundColor = .clear
}
self.contentButtonBackgroundColor = regularContentButtonBackgroundColor
let collapsedHeaderNavigationContentsSecondaryColor = presentationData.theme.list.itemSecondaryTextColor
let expandedAvatarNavigationContentsSecondaryColor: UIColor = .white
let navigationContentsAccentColor: UIColor
let navigationContentsPrimaryColor: UIColor
let navigationContentsSecondaryColor: UIColor
let navigationContentsCanBeExpanded: Bool
let contentButtonBackgroundColor: UIColor
let contentButtonForegroundColor: UIColor
let headerButtonBackgroundColor: UIColor
var panelWithAvatarHeight: CGFloat = 35.0 + avatarSize
if threadData != nil {
panelWithAvatarHeight += 10.0
}
let innerBackgroundTransitionFraction: CGFloat
let navigationTransition: ContainedViewLayoutTransition
if transition.isAnimated {
navigationTransition = transition
} else {
navigationTransition = animateHeader ? .animated(duration: 0.2, curve: .easeInOut) : .immediate
}
let editingBackgroundAlpha: CGFloat
if state.isEditing {
editingBackgroundAlpha = max(0.0, min(1.0, contentOffset / 20.0))
} else {
editingBackgroundAlpha = 0.0
}
self.editingNavigationBackgroundSeparator.backgroundColor = presentationData.theme.rootController.navigationBar.separatorColor
self.editingNavigationBackgroundNode.updateColor(color: presentationData.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate)
let editingNavigationBackgroundFrame = CGRect(origin: CGPoint(), size: CGSize(width: width, height: navigationHeight))
transition.updateFrame(node: self.editingNavigationBackgroundNode, frame: editingNavigationBackgroundFrame)
self.editingNavigationBackgroundNode.update(size: editingNavigationBackgroundFrame.size, transition: transition)
transition.updateFrame(node: self.editingNavigationBackgroundSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: editingNavigationBackgroundFrame.maxY), size: CGSize(width: width, height: UIScreenPixel)))
transition.updateAlpha(node: self.editingNavigationBackgroundNode, alpha: editingBackgroundAlpha)
transition.updateAlpha(node: self.editingNavigationBackgroundSeparator, alpha: editingBackgroundAlpha)
let backgroundBannerAlpha: CGFloat
var effectiveSeparatorAlpha: CGFloat
if let navigationTransition = self.navigationTransition {
transitionSourceHeight = navigationTransition.sourceNavigationBar.backgroundNode.bounds.height
transitionFraction = navigationTransition.fraction
innerBackgroundTransitionFraction = 0.0
backgroundBannerAlpha = 1.0
if let avatarNavigationNode = navigationTransition.sourceNavigationBar.rightButtonNode.singleCustomNode as? ChatAvatarNavigationNode {
if let statusView = avatarNavigationNode.statusView.view {
transitionSourceAvatarFrame = statusView.convert(statusView.bounds, to: navigationTransition.sourceNavigationBar.view)
} else {
transitionSourceAvatarFrame = avatarNavigationNode.avatarNode.view.convert(avatarNavigationNode.avatarNode.view.bounds, to: navigationTransition.sourceNavigationBar.view)
}
transition.updateAlpha(node: self.avatarListNode.avatarContainerNode.avatarNode, alpha: 1.0 - transitionFraction)
} else {
if deviceMetrics.hasDynamicIsland && statusBarHeight > 0.0 && !isLandscape {
transitionSourceAvatarFrame = CGRect(origin: CGPoint(x: avatarFrame.minX, y: -20.0), size: avatarFrame.size).insetBy(dx: avatarSize * 0.4, dy: avatarSize * 0.4)
} else {
transitionSourceAvatarFrame = avatarFrame.offsetBy(dx: 0.0, dy: -avatarFrame.maxY).insetBy(dx: avatarSize * 0.4, dy: avatarSize * 0.4)
}
}
transitionSourceTitleFrame = navigationTransition.sourceTitleFrame
transitionSourceSubtitleFrame = navigationTransition.sourceSubtitleFrame
transition.updateAlpha(layer: self.backgroundBannerView.layer, alpha: 1.0 - transitionFraction)
self.expandedBackgroundNode.updateColor(color: presentationData.theme.rootController.navigationBar.blurredBackgroundColor.mixedWith(headerBackgroundColor, alpha: 1.0 - transitionFraction), forceKeepBlur: true, transition: transition)
effectiveSeparatorAlpha = transitionFraction
if self.isAvatarExpanded, case .animated = transition, transitionFraction == 1.0 {
self.avatarListNode.animateAvatarCollapse(transition: transition)
}
self.avatarClippingNode.clipsToBounds = false
} else {
let backgroundTransitionStepDistance: CGFloat = 50.0
var backgroundTransitionDistance: CGFloat = navigationHeight + panelWithAvatarHeight - backgroundTransitionStepDistance
if self.isSettings || self.isMyProfile {
backgroundTransitionDistance -= 100.0
}
if isMediaOnly {
innerBackgroundTransitionFraction = 1.0
} else {
let contentOffset = max(0.0, contentOffset - backgroundTransitionDistance)
innerBackgroundTransitionFraction = max(0.0, min(1.0, contentOffset / backgroundTransitionStepDistance))
}
self.expandedBackgroundNode.updateColor(color: presentationData.theme.rootController.navigationBar.opaqueBackgroundColor.mixedWith(headerBackgroundColor, alpha: 1.0 - innerBackgroundTransitionFraction), forceKeepBlur: true, transition: transition)
navigationTransition.updateAlpha(node: self.expandedBackgroundNode, alpha: state.isEditing ? 0.0 : 1.0)
if state.isEditing {
backgroundBannerAlpha = 0.0
} else {
if 1.0 - innerBackgroundTransitionFraction < 0.5 {
backgroundBannerAlpha = 0.0
} else {
backgroundBannerAlpha = 1.0
}
}
navigationTransition.updateAlpha(layer: self.backgroundBannerView.layer, alpha: backgroundBannerAlpha)
effectiveSeparatorAlpha = innerBackgroundTransitionFraction
self.avatarClippingNode.clipsToBounds = true
}
if state.isEditing {
navigationContentsAccentColor = collapsedHeaderNavigationContentsAccentColor
navigationContentsPrimaryColor = collapsedHeaderNavigationContentsPrimaryColor
navigationContentsSecondaryColor = collapsedHeaderNavigationContentsSecondaryColor
navigationContentsCanBeExpanded = true
contentButtonBackgroundColor = collapsedHeaderContentButtonBackgroundColor
contentButtonForegroundColor = collapsedHeaderContentButtonForegroundColor
headerButtonBackgroundColor = collapsedHeaderButtonBackgroundColor
} else if self.isAvatarExpanded {
navigationContentsAccentColor = expandedAvatarNavigationContentsAccentColor
navigationContentsPrimaryColor = expandedAvatarNavigationContentsPrimaryColor
navigationContentsSecondaryColor = expandedAvatarNavigationContentsSecondaryColor
contentButtonBackgroundColor = expandedAvatarContentButtonBackgroundColor
contentButtonForegroundColor = expandedAvatarContentButtonForegroundColor
navigationContentsCanBeExpanded = false
headerButtonBackgroundColor = expandedAvatarHeaderButtonBackgroundColor
} else {
let effectiveTransitionFraction: CGFloat = innerBackgroundTransitionFraction < 0.5 ? 0.0 : 1.0
navigationContentsAccentColor = regularNavigationContentsAccentColor.mixedWith(collapsedHeaderNavigationContentsAccentColor, alpha: effectiveTransitionFraction)
navigationContentsPrimaryColor = regularNavigationContentsPrimaryColor.mixedWith(collapsedHeaderNavigationContentsPrimaryColor, alpha: effectiveTransitionFraction)
navigationContentsSecondaryColor = regularNavigationContentsSecondaryColor.mixedWith(collapsedHeaderNavigationContentsSecondaryColor, alpha: effectiveTransitionFraction)
if peer?.profileColor != nil {
navigationContentsCanBeExpanded = effectiveTransitionFraction == 1.0
} else {
navigationContentsCanBeExpanded = true
}
contentButtonBackgroundColor = regularContentButtonBackgroundColor//.mixedWith(collapsedHeaderContentButtonBackgroundColor, alpha: effectiveTransitionFraction)
contentButtonForegroundColor = regularContentButtonForegroundColor//.mixedWith(collapsedHeaderContentButtonForegroundColor, alpha: effectiveTransitionFraction)
headerButtonBackgroundColor = regularHeaderButtonBackgroundColor.mixedWith(collapsedHeaderButtonBackgroundColor, alpha: effectiveTransitionFraction)
}
do {
self.currentCredibilityIcon = credibilityIcon
var currentEmojiStatus: PeerEmojiStatus?
let emojiRegularStatusContent: EmojiStatusComponent.Content
let emojiExpandedStatusContent: EmojiStatusComponent.Content
switch credibilityIcon {
case .none:
emojiRegularStatusContent = .none
emojiExpandedStatusContent = .none
case .premium:
emojiRegularStatusContent = .premium(color: navigationContentsAccentColor)
emojiExpandedStatusContent = .premium(color: navigationContentsAccentColor)
case .verified:
emojiRegularStatusContent = .verified(fillColor: presentationData.theme.list.itemCheckColors.fillColor, foregroundColor: presentationData.theme.list.itemCheckColors.foregroundColor, sizeType: .large)
emojiExpandedStatusContent = .verified(fillColor: navigationContentsAccentColor, foregroundColor: .clear, sizeType: .large)
case .fake:
emojiRegularStatusContent = .text(color: presentationData.theme.chat.message.incoming.scamColor, string: presentationData.strings.Message_FakeAccount.uppercased())
emojiExpandedStatusContent = emojiRegularStatusContent
case .scam:
emojiRegularStatusContent = .text(color: presentationData.theme.chat.message.incoming.scamColor, string: presentationData.strings.Message_ScamAccount.uppercased())
emojiExpandedStatusContent = emojiRegularStatusContent
case let .emojiStatus(emojiStatus):
currentEmojiStatus = emojiStatus
emojiRegularStatusContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 80.0, height: 80.0), placeholderColor: presentationData.theme.list.mediaPlaceholderColor, themeColor: navigationContentsAccentColor, loopMode: .forever)
emojiExpandedStatusContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 80.0, height: 80.0), placeholderColor: navigationContentsAccentColor, themeColor: navigationContentsAccentColor, loopMode: .forever)
}
let iconSize = self.titleCredibilityIconView.update(
transition: ComponentTransition(navigationTransition),
component: AnyComponent(EmojiStatusComponent(
context: self.context,
animationCache: self.animationCache,
animationRenderer: self.animationRenderer,
content: emojiRegularStatusContent,
isVisibleForAnimations: true,
useSharedAnimation: true,
action: { [weak self] in
guard let strongSelf = self else {
return
}
if case .premium = strongSelf.currentCredibilityIcon {
strongSelf.displayPremiumIntro?(strongSelf.titleCredibilityIconView, currentEmojiStatus, strongSelf.emojiStatusFileAndPackTitle.get(), false)
}
}
)),
environment: {},
containerSize: CGSize(width: 26.0, height: 26.0)
)
let expandedIconSize = self.titleExpandedCredibilityIconView.update(
transition: ComponentTransition(navigationTransition),
component: AnyComponent(EmojiStatusComponent(
context: self.context,
animationCache: self.animationCache,
animationRenderer: self.animationRenderer,
content: emojiExpandedStatusContent,
isVisibleForAnimations: true,
useSharedAnimation: true,
action: { [weak self] in
guard let strongSelf = self else {
return
}
if case .premium = strongSelf.currentCredibilityIcon {
strongSelf.displayPremiumIntro?(strongSelf.titleExpandedCredibilityIconView, currentEmojiStatus, strongSelf.emojiStatusFileAndPackTitle.get(), true)
}
}
)),
environment: {},
containerSize: CGSize(width: 26.0, height: 26.0)
)
self.credibilityIconSize = iconSize
self.titleExpandedCredibilityIconSize = expandedIconSize
}
do {
self.currentStatusIcon = statusIcon
var currentEmojiStatus: PeerEmojiStatus?
var particleColor: UIColor?
var uniqueGiftSlug: String?
let emojiRegularStatusContent: EmojiStatusComponent.Content
let emojiExpandedStatusContent: EmojiStatusComponent.Content
switch statusIcon {
case let .emojiStatus(emojiStatus):
currentEmojiStatus = emojiStatus
emojiRegularStatusContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 80.0, height: 80.0), placeholderColor: presentationData.theme.list.mediaPlaceholderColor, themeColor: navigationContentsAccentColor, loopMode: .forever)
emojiExpandedStatusContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 80.0, height: 80.0), placeholderColor: navigationContentsAccentColor, themeColor: navigationContentsAccentColor, loopMode: .forever)
if case let .starGift(_, _, _, slug, _, _, _, _, _) = emojiStatus.content {
particleColor = UIColor.white
uniqueGiftSlug = slug
}
default:
emojiRegularStatusContent = .none
emojiExpandedStatusContent = .none
}
let iconSize = self.titleStatusIconView.update(
transition: ComponentTransition(navigationTransition),
component: AnyComponent(EmojiStatusComponent(
context: self.context,
animationCache: self.animationCache,
animationRenderer: self.animationRenderer,
content: emojiRegularStatusContent,
particleColor: particleColor,
isVisibleForAnimations: true,
useSharedAnimation: true,
action: { [weak self] in
guard let strongSelf = self else {
return
}
if let uniqueGiftSlug {
strongSelf.openUniqueGift?(uniqueGiftSlug)
} else {
strongSelf.displayPremiumIntro?(strongSelf.titleStatusIconView, currentEmojiStatus, strongSelf.emojiStatusFileAndPackTitle.get(), false)
}
},
emojiFileUpdated: { [weak self] emojiFile in
guard let strongSelf = self else {
return
}
if let emojiFile = emojiFile {
strongSelf.emojiStatusFileAndPackTitle.set(.never())
for attribute in emojiFile.attributes {
if case let .CustomEmoji(_, _, _, packReference) = attribute, let packReference = packReference {
strongSelf.emojiStatusPackDisposable.set((strongSelf.context.engine.stickers.loadedStickerPack(reference: packReference, forceActualized: false)
|> filter { result in
if case .result = result {
return true
} else {
return false
}
}
|> mapToSignal { result -> Signal<(TelegramMediaFile, LoadedStickerPack)?, NoError> in
if case let .result(_, items, _) = result {
return .single(items.first.flatMap { ($0.file, result) })
} else {
return .complete()
}
}).startStrict(next: { fileAndPackTitle in
guard let strongSelf = self else {
return
}
strongSelf.emojiStatusFileAndPackTitle.set(.single(fileAndPackTitle))
}))
break
}
}
} else {
strongSelf.emojiStatusFileAndPackTitle.set(.never())
}
}
)),
environment: {},
containerSize: CGSize(width: 26.0, height: 26.0)
)
let expandedIconSize = self.titleExpandedStatusIconView.update(
transition: ComponentTransition(navigationTransition),
component: AnyComponent(EmojiStatusComponent(
context: self.context,
animationCache: self.animationCache,
animationRenderer: self.animationRenderer,
content: emojiExpandedStatusContent,
particleColor: particleColor,
isVisibleForAnimations: true,
useSharedAnimation: true,
action: { [weak self] in
guard let strongSelf = self else {
return
}
if let uniqueGiftSlug {
strongSelf.openUniqueGift?(uniqueGiftSlug)
} else {
strongSelf.displayPremiumIntro?(strongSelf.titleExpandedStatusIconView, currentEmojiStatus, strongSelf.emojiStatusFileAndPackTitle.get(), true)
}
}
)),
environment: {},
containerSize: CGSize(width: 26.0, height: 26.0)
)
self.statusIconSize = iconSize
self.titleExpandedStatusIconSize = expandedIconSize
}
do {
self.currentVerifiedIcon = verifiedIcon
let emojiRegularStatusContent: EmojiStatusComponent.Content
let emojiExpandedStatusContent: EmojiStatusComponent.Content
switch verifiedIcon {
case .verified:
emojiRegularStatusContent = .verified(fillColor: presentationData.theme.list.itemCheckColors.fillColor, foregroundColor: presentationData.theme.list.itemCheckColors.foregroundColor, sizeType: .large)
emojiExpandedStatusContent = .verified(fillColor: navigationContentsAccentColor, foregroundColor: .clear, sizeType: .large)
case let .emojiStatus(emojiStatus):
emojiRegularStatusContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 80.0, height: 80.0), placeholderColor: presentationData.theme.list.mediaPlaceholderColor, themeColor: navigationContentsAccentColor, loopMode: .forever)
emojiExpandedStatusContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 80.0, height: 80.0), placeholderColor: navigationContentsAccentColor, themeColor: navigationContentsAccentColor, loopMode: .forever)
default:
emojiRegularStatusContent = .none
emojiExpandedStatusContent = .none
}
let iconSize = self.titleVerifiedIconView.update(
transition: ComponentTransition(navigationTransition),
component: AnyComponent(EmojiStatusComponent(
context: self.context,
animationCache: self.animationCache,
animationRenderer: self.animationRenderer,
content: emojiRegularStatusContent,
isVisibleForAnimations: true,
useSharedAnimation: true,
action: nil,
emojiFileUpdated: nil
)),
environment: {},
containerSize: CGSize(width: 26.0, height: 26.0)
)
let expandedIconSize = self.titleExpandedVerifiedIconView.update(
transition: ComponentTransition(navigationTransition),
component: AnyComponent(EmojiStatusComponent(
context: self.context,
animationCache: self.animationCache,
animationRenderer: self.animationRenderer,
content: emojiExpandedStatusContent,
isVisibleForAnimations: true,
useSharedAnimation: true,
action: {}
)),
environment: {},
containerSize: CGSize(width: 26.0, height: 26.0)
)
self.verifiedIconSize = iconSize
self.titleExpandedVerifiedIconSize = expandedIconSize
}
self.navigationButtonContainer.updateContentsColor(backgroundContentColor: headerButtonBackgroundColor, contentsColor: navigationContentsAccentColor, canBeExpanded: navigationContentsCanBeExpanded, transition: navigationTransition)
self.titleNode.updateTintColor(color: navigationContentsPrimaryColor, transition: navigationTransition)
self.subtitleNode.updateTintColor(color: navigationContentsSecondaryColor, transition: navigationTransition)
self.panelSubtitleNode.updateTintColor(color: navigationContentsSecondaryColor, transition: navigationTransition)
if let navigationBar = self.controller?.navigationBar {
if let mainContentNode = navigationBar.backButtonNode.mainContentNode {
navigationTransition.updateTintColor(layer: mainContentNode.layer, color: navigationContentsAccentColor)
}
navigationTransition.updateTintColor(layer: navigationBar.backButtonArrow.layer, color: navigationContentsAccentColor)
if let mainContentNode = navigationBar.leftButtonNode.mainContentNode {
navigationTransition.updateTintColor(layer: mainContentNode.layer, color: navigationContentsAccentColor)
}
navigationBar.rightButtonNode.contentsColor = navigationContentsAccentColor
navigationBar.leftButtonNode.contentsColor = navigationContentsAccentColor
navigationBar.backButtonNode.contentsColor = navigationContentsAccentColor
}
var titleBrightness: CGFloat = 0.0
navigationContentsPrimaryColor.getHue(nil, saturation: nil, brightness: &titleBrightness, alpha: nil)
self.controller?.setStatusBarStyle(titleBrightness > 0.5 ? .White : .Black, animated: !isFirstTime && animateHeader)
self.avatarListNode.avatarContainerNode.updateTransitionFraction(transitionFraction, transition: transition)
self.avatarListNode.listContainerNode.currentItemNode?.updateTransitionFraction(transitionFraction, transition: transition)
self.avatarOverlayNode.updateTransitionFraction(transitionFraction, transition: transition)
if self.navigationTitle != presentationData.strings.EditProfile_Title || themeUpdated {
self.navigationTitleNode.attributedText = NSAttributedString(string: presentationData.strings.EditProfile_Title, font: Font.semibold(17.0), textColor: .white)
}
let navigationTitleSize = self.navigationTitleNode.updateLayout(CGSize(width: width, height: navigationHeight))
self.navigationTitleNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((width - navigationTitleSize.width) / 2.0), y: navigationHeight - 44.0 + floorToScreenPixels((44.0 - navigationTitleSize.height) / 2.0)), size: navigationTitleSize)
self.navigationBackgroundNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: width, height: navigationHeight))
self.navigationBackgroundBackgroundNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: width, height: navigationHeight))
self.navigationSeparatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: navigationHeight), size: CGSize(width: width, height: UIScreenPixel))
self.navigationBackgroundBackgroundNode.backgroundColor = presentationData.theme.rootController.navigationBar.opaqueBackgroundColor
self.navigationSeparatorNode.backgroundColor = presentationData.theme.rootController.navigationBar.separatorColor
let navigationSeparatorAlpha: CGFloat = 0.0
transition.updateAlpha(node: self.navigationBackgroundBackgroundNode, alpha: 1.0 - navigationSeparatorAlpha)
transition.updateAlpha(node: self.navigationSeparatorNode, alpha: navigationSeparatorAlpha)
self.separatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor
let expandedAvatarControlsHeight: CGFloat = 61.0
var expandedAvatarListHeight = min(width, containerHeight - expandedAvatarControlsHeight)
if self.isSettings || self.isMyProfile {
expandedAvatarListHeight = expandedAvatarListHeight + 60.0
} else {
expandedAvatarListHeight = expandedAvatarListHeight + 98.0
}
let expandedAvatarListSize = CGSize(width: width, height: expandedAvatarListHeight)
let actionButtonKeys: [PeerInfoHeaderButtonKey] = (self.isSettings || self.isMyProfile) ? [] : peerInfoHeaderActionButtons(peer: peer, isSecretChat: isSecretChat, isContact: isContact)
let buttonKeys: [PeerInfoHeaderButtonKey] = (self.isSettings || self.isMyProfile) ? [] : peerInfoHeaderButtons(peer: peer, cachedData: cachedData, isOpenedFromChat: self.isOpenedFromChat, isExpanded: true, videoCallsEnabled: width > 320.0 && self.videoCallsEnabled, isSecretChat: isSecretChat, isContact: isContact, threadInfo: threadData?.info)
var isPremium = false
var isVerified = false
var isFake = false
let titleStringText: String
let smallTitleAttributes: MultiScaleTextState.Attributes
let titleAttributes: MultiScaleTextState.Attributes
let subtitleStringText: String
let smallSubtitleAttributes: MultiScaleTextState.Attributes
let subtitleAttributes: MultiScaleTextState.Attributes
var subtitleIsButton: Bool = false
var panelSubtitleString: (text: String, attributes: MultiScaleTextState.Attributes)?
let usernameString: (text: String, attributes: MultiScaleTextState.Attributes)
if let peer = peer {
isPremium = peer.isPremium
isVerified = peer.isVerified
isFake = peer.isFake || peer.isScam
}
let titleShadowColor: UIColor? = nil
var displayStandardTitle = false
if let peer = peer {
var title: String
if peer.id == self.context.account.peerId && !self.isSettings && !self.isMyProfile {
if case .replyThread = self.chatLocation {
title = presentationData.strings.Conversation_MyNotes
} else {
displayStandardTitle = true
title = presentationData.strings.Conversation_SavedMessages
}
} else if peer.id.isAnonymousSavedMessages {
title = presentationData.strings.ChatList_AuthorHidden
} else if let threadData = threadData {
title = threadData.info.title
} else {
title = EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
}
title = title.replacingOccurrences(of: "\u{1160}", with: "").replacingOccurrences(of: "\u{3164}", with: "")
if title.isEmpty {
if let peer = peer as? TelegramUser, let phone = peer.phone {
title = formatPhoneNumber(context: self.context, number: phone)
} else if let addressName = peer.addressName {
title = "@\(addressName)"
} else {
title = " "
}
}
titleStringText = title
titleAttributes = MultiScaleTextState.Attributes(font: Font.medium(28.0), color: .white)
smallTitleAttributes = MultiScaleTextState.Attributes(font: Font.medium(28.0), color: .white, shadowColor: titleShadowColor)
if self.isSettings, let user = peer as? TelegramUser {
var subtitle = formatPhoneNumber(context: self.context, number: user.phone ?? "")
if let mainUsername = user.addressName, !mainUsername.isEmpty {
subtitle = "\(subtitle) • @\(mainUsername)"
}
subtitleStringText = subtitle
subtitleAttributes = MultiScaleTextState.Attributes(font: Font.regular(17.0), color: .white)
smallSubtitleAttributes = MultiScaleTextState.Attributes(font: Font.regular(16.0), color: .white, shadowColor: titleShadowColor)
usernameString = ("", MultiScaleTextState.Attributes(font: Font.regular(16.0), color: .white))
} else if self.isMyProfile {
let subtitleColor: UIColor
subtitleColor = .white
subtitleStringText = presentationData.strings.Presence_online
subtitleAttributes = MultiScaleTextState.Attributes(font: Font.regular(17.0), color: subtitleColor)
smallSubtitleAttributes = MultiScaleTextState.Attributes(font: Font.regular(16.0), color: .white, shadowColor: titleShadowColor)
usernameString = ("", MultiScaleTextState.Attributes(font: Font.regular(16.0), color: .white))
let (maybePanelStatusData, _, _) = panelStatusData
if let panelStatusData = maybePanelStatusData {
let subtitleColor: UIColor
if panelStatusData.isActivity {
subtitleColor = UIColor.white
} else {
subtitleColor = UIColor.white
}
panelSubtitleString = (panelStatusData.text, MultiScaleTextState.Attributes(font: Font.regular(17.0), color: subtitleColor))
}
} else if let _ = threadData {
let subtitleColor: UIColor
subtitleColor = UIColor.white
let statusText: String
statusText = peer.debugDisplayTitle
subtitleStringText = statusText
subtitleAttributes = MultiScaleTextState.Attributes(font: Font.semibold(16.0), color: subtitleColor)
smallSubtitleAttributes = MultiScaleTextState.Attributes(font: Font.regular(16.0), color: .white, shadowColor: titleShadowColor)
usernameString = ("", MultiScaleTextState.Attributes(font: Font.regular(16.0), color: .white))
subtitleIsButton = true
let (maybePanelStatusData, _, _) = panelStatusData
if let panelStatusData = maybePanelStatusData {
let subtitleColor: UIColor
if panelStatusData.isActivity {
subtitleColor = UIColor.white
} else {
subtitleColor = UIColor.white
}
panelSubtitleString = (panelStatusData.text, MultiScaleTextState.Attributes(font: Font.regular(17.0), color: subtitleColor))
}
} else if let statusData = statusData {
let subtitleColor: UIColor
if statusData.isActivity {
subtitleColor = UIColor.white
} else {
subtitleColor = UIColor.white
}
subtitleStringText = statusData.text
subtitleAttributes = MultiScaleTextState.Attributes(font: Font.regular(17.0), color: subtitleColor)
smallSubtitleAttributes = MultiScaleTextState.Attributes(font: Font.regular(16.0), color: .white, shadowColor: titleShadowColor)
usernameString = ("", MultiScaleTextState.Attributes(font: Font.regular(16.0), color: .white))
let (maybePanelStatusData, _, _) = panelStatusData
if let panelStatusData = maybePanelStatusData {
let subtitleColor: UIColor
if panelStatusData.isActivity {
subtitleColor = UIColor.white
} else {
subtitleColor = UIColor.white
}
panelSubtitleString = (panelStatusData.text, MultiScaleTextState.Attributes(font: Font.regular(17.0), color: subtitleColor))
}
} else {
subtitleStringText = " "
subtitleAttributes = MultiScaleTextState.Attributes(font: Font.regular(16.0), color: .white)
smallSubtitleAttributes = MultiScaleTextState.Attributes(font: Font.regular(16.0), color: .white, shadowColor: titleShadowColor)
usernameString = ("", MultiScaleTextState.Attributes(font: Font.regular(16.0), color: .white))
let (maybePanelStatusData, _, _) = panelStatusData
if let panelStatusData = maybePanelStatusData {
let subtitleColor: UIColor
if panelStatusData.isActivity {
subtitleColor = UIColor.white
} else {
subtitleColor = UIColor.white
}
panelSubtitleString = (panelStatusData.text, MultiScaleTextState.Attributes(font: Font.regular(17.0), color: subtitleColor))
}
}
} else {
titleStringText = " "
titleAttributes = MultiScaleTextState.Attributes(font: Font.regular(24.0), color: .white)
smallTitleAttributes = MultiScaleTextState.Attributes(font: Font.regular(24.0), color: .white, shadowColor: titleShadowColor)
subtitleStringText = " "
subtitleAttributes = MultiScaleTextState.Attributes(font: Font.regular(16.0), color: .white)
smallSubtitleAttributes = MultiScaleTextState.Attributes(font: Font.regular(16.0), color: .white, shadowColor: titleShadowColor)
usernameString = ("", MultiScaleTextState.Attributes(font: Font.regular(16.0), color: .white))
}
let textSideInset: CGFloat = 36.0
let expandedAvatarHeight: CGFloat = expandedAvatarListSize.height
let titleConstrainedSize = CGSize(width: width - textSideInset * 2.0 - (isPremium || isVerified || isFake ? 20.0 : 0.0), height: .greatestFiniteMagnitude)
let titleNodeLayout = self.titleNode.updateLayout(text: titleStringText, states: [
TitleNodeStateRegular: MultiScaleTextState(attributes: titleAttributes, constrainedSize: titleConstrainedSize),
TitleNodeStateExpanded: MultiScaleTextState(attributes: smallTitleAttributes, constrainedSize: titleConstrainedSize)
], mainState: TitleNodeStateRegular)
let subtitleNodeLayout = self.subtitleNode.updateLayout(text: subtitleStringText, states: [
TitleNodeStateRegular: MultiScaleTextState(attributes: subtitleAttributes, constrainedSize: titleConstrainedSize),
TitleNodeStateExpanded: MultiScaleTextState(attributes: smallSubtitleAttributes, constrainedSize: titleConstrainedSize)
], mainState: TitleNodeStateRegular)
self.subtitleNode.accessibilityLabel = subtitleStringText
if subtitleIsButton {
let subtitleBackgroundNode: ASDisplayNode
if let current = self.subtitleBackgroundNode {
subtitleBackgroundNode = current
} else {
subtitleBackgroundNode = ASDisplayNode()
self.subtitleBackgroundNode = subtitleBackgroundNode
self.subtitleNode.insertSubnode(subtitleBackgroundNode, at: 0)
}
let subtitleBackgroundButton: HighlightTrackingButtonNode
if let current = self.subtitleBackgroundButton {
subtitleBackgroundButton = current
} else {
subtitleBackgroundButton = HighlightTrackingButtonNode()
self.subtitleBackgroundButton = subtitleBackgroundButton
self.subtitleNode.addSubnode(subtitleBackgroundButton)
subtitleBackgroundButton.addTarget(self, action: #selector(self.subtitleBackgroundPressed), forControlEvents: .touchUpInside)
subtitleBackgroundButton.highligthedChanged = { [weak self] highlighted in
guard let self else {
return
}
if highlighted {
self.subtitleNode.layer.removeAnimation(forKey: "opacity")
self.subtitleNode.alpha = 0.4
} else {
self.subtitleNode.alpha = 1.0
self.subtitleNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
}
}
}
let subtitleArrowNode: ASImageNode
if let current = self.subtitleArrowNode {
subtitleArrowNode = current
if themeUpdated {
subtitleArrowNode.image = generateTintedImage(image: UIImage(bundleImageName: "Item List/DisclosureArrow"), color: .white)?.withRenderingMode(.alwaysTemplate)
}
} else {
subtitleArrowNode = ASImageNode()
self.subtitleArrowNode = subtitleArrowNode
self.subtitleNode.insertSubnode(subtitleArrowNode, at: 1)
subtitleArrowNode.image = generateTintedImage(image: UIImage(bundleImageName: "Item List/DisclosureArrow"), color: .white)?.withRenderingMode(.alwaysTemplate)
}
subtitleBackgroundNode.backgroundColor = .white.withMultipliedAlpha(0.1)
let subtitleSize = subtitleNodeLayout[TitleNodeStateRegular]!.size
var subtitleBackgroundFrame = CGRect(origin: CGPoint(), size: subtitleSize).offsetBy(dx: -subtitleSize.width * 0.5, dy: -subtitleSize.height * 0.5).insetBy(dx: -6.0, dy: -4.0)
subtitleBackgroundFrame.size.width += 12.0
transition.updateFrame(node: subtitleBackgroundNode, frame: subtitleBackgroundFrame)
transition.updateCornerRadius(node: subtitleBackgroundNode, cornerRadius: subtitleBackgroundFrame.height * 0.5)
transition.updateFrame(node: subtitleBackgroundButton, frame: subtitleBackgroundFrame)
if let arrowImage = subtitleArrowNode.image {
let scaleFactor: CGFloat = 0.8
let arrowSize = CGSize(width: floorToScreenPixels(arrowImage.size.width * scaleFactor), height: floorToScreenPixels(arrowImage.size.height * scaleFactor))
subtitleArrowNode.frame = CGRect(origin: CGPoint(x: subtitleBackgroundFrame.maxX - arrowSize.width - 1.0, y: subtitleBackgroundFrame.minY + floor((subtitleBackgroundFrame.height - arrowSize.height) / 2.0)), size: arrowSize)
}
} else {
if let subtitleBackgroundNode = self.subtitleBackgroundNode {
self.subtitleBackgroundNode = nil
subtitleBackgroundNode.removeFromSupernode()
}
if let subtitleArrowNode = self.subtitleArrowNode {
self.subtitleArrowNode = nil
subtitleArrowNode.removeFromSupernode()
}
if let subtitleBackgroundButton = self.subtitleBackgroundButton {
self.subtitleBackgroundButton = nil
subtitleBackgroundButton.removeFromSupernode()
}
}
if let previousPanelStatusData = previousPanelStatusData, let currentPanelStatusData = panelStatusData.0, let previousPanelStatusDataKey = previousPanelStatusData.key, let currentPanelStatusDataKey = currentPanelStatusData.key, previousPanelStatusDataKey != currentPanelStatusDataKey {
if let snapshotView = self.panelSubtitleNode.view.snapshotContentTree() {
let direction: CGFloat = previousPanelStatusDataKey.rawValue > currentPanelStatusDataKey.rawValue ? 1.0 : -1.0
self.panelSubtitleNode.view.superview?.addSubview(snapshotView)
snapshotView.frame = self.panelSubtitleNode.frame
snapshotView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 100.0 * direction, y: 0.0), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true, completion: { [weak snapshotView] _ in
snapshotView?.removeFromSuperview()
})
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
self.panelSubtitleNode.layer.animatePosition(from: CGPoint(x: 100.0 * direction * -1.0, y: 0.0), to: CGPoint(), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
self.panelSubtitleNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
}
}
let panelSubtitleNodeLayout = self.panelSubtitleNode.updateLayout(text: panelSubtitleString?.text ?? subtitleStringText, states: [
TitleNodeStateRegular: MultiScaleTextState(attributes: panelSubtitleString?.attributes ?? subtitleAttributes, constrainedSize: titleConstrainedSize),
TitleNodeStateExpanded: MultiScaleTextState(attributes: panelSubtitleString?.attributes ?? subtitleAttributes, constrainedSize: titleConstrainedSize)
], mainState: TitleNodeStateRegular)
self.panelSubtitleNode.accessibilityLabel = panelSubtitleString?.text ?? subtitleStringText
let usernameNodeLayout = self.usernameNode.updateLayout(text: usernameString.text, states: [
TitleNodeStateRegular: MultiScaleTextState(attributes: usernameString.attributes, constrainedSize: CGSize(width: titleConstrainedSize.width, height: titleConstrainedSize.height)),
TitleNodeStateExpanded: MultiScaleTextState(attributes: usernameString.attributes, constrainedSize: CGSize(width: width - titleNodeLayout[TitleNodeStateExpanded]!.size.width - 8.0, height: titleConstrainedSize.height))
], mainState: TitleNodeStateRegular)
self.usernameNode.accessibilityLabel = usernameString.text
let avatarCenter: CGPoint
if let transitionSourceAvatarFrame = transitionSourceAvatarFrame {
avatarCenter = CGPoint(x: (1.0 - transitionFraction) * avatarFrame.midX + transitionFraction * transitionSourceAvatarFrame.midX, y: (1.0 - transitionFraction) * avatarFrame.midY + transitionFraction * transitionSourceAvatarFrame.midY)
} else {
avatarCenter = avatarFrame.center
}
let titleSize = titleNodeLayout[TitleNodeStateRegular]!.size
let titleExpandedSize = titleNodeLayout[TitleNodeStateExpanded]!.size
let subtitleSize = subtitleNodeLayout[TitleNodeStateRegular]!.size
var subtitleBadgeSize: CGSize?
let _ = panelSubtitleNodeLayout[TitleNodeStateRegular]!.size
let usernameSize = usernameNodeLayout[TitleNodeStateRegular]!.size
if let statusData, statusData.isHiddenStatus, !self.isPremiumDisabled {
let subtitleBadgeView: PeerInfoSubtitleBadgeView
if let current = self.subtitleBadgeView {
subtitleBadgeView = current
} else {
subtitleBadgeView = PeerInfoSubtitleBadgeView(action: { [weak self] in
guard let self else {
return
}
self.displayStatusPremiumIntro?()
})
self.subtitleBadgeView = subtitleBadgeView
self.subtitleNodeContainer.view.addSubview(subtitleBadgeView)
}
subtitleBadgeSize = subtitleBadgeView.update(title: presentationData.strings.PeerInfo_HiddenStatusBadge, fillColor: contentButtonBackgroundColor, foregroundColor: contentButtonForegroundColor)
} else if let subtitleBadgeView = self.subtitleBadgeView {
subtitleBadgeView.removeFromSuperview()
}
var titleHorizontalOffset: CGFloat = 0.0
var titleExpandedHorizontalOffset: CGFloat = 0.0
var nextIconX: CGFloat = titleSize.width
var nextExpandedIconX: CGFloat = titleExpandedSize.width
if let statusIconSize = self.statusIconSize, let titleExpandedStatusIconSize = self.titleExpandedStatusIconSize, statusIconSize.width > 0.0 {
let offset = (statusIconSize.width + 4.0) / 2.0
let leftOffset: CGFloat = nextIconX + 4.0
let leftExpandedOffset: CGFloat = nextExpandedIconX + 4.0
titleHorizontalOffset -= offset
var collapsedTransitionOffset: CGFloat = 0.0
if let navigationTransition = self.navigationTransition {
collapsedTransitionOffset = -10.0 * navigationTransition.fraction
}
transition.updateFrame(view: self.titleStatusIconView, frame: CGRect(origin: CGPoint(x: leftOffset + collapsedTransitionOffset, y: floor((titleSize.height - statusIconSize.height) / 2.0)), size: statusIconSize))
transition.updateFrame(view: self.titleExpandedStatusIconView, frame: CGRect(origin: CGPoint(x: leftExpandedOffset, y: floor((titleExpandedSize.height - titleExpandedStatusIconSize.height) / 2.0) + 1.0), size: titleExpandedStatusIconSize))
nextIconX += 4.0 + statusIconSize.width
nextExpandedIconX += 4.0 + titleExpandedStatusIconSize.width
}
if let credibilityIconSize = self.credibilityIconSize, let titleExpandedCredibilityIconSize = self.titleExpandedCredibilityIconSize, credibilityIconSize.width > 0.0 {
let offset = (credibilityIconSize.width + 4.0) / 2.0
let leftOffset: CGFloat = nextIconX + 4.0
let leftExpandedOffset: CGFloat = nextExpandedIconX + 4.0
titleHorizontalOffset -= offset
var collapsedTransitionOffset: CGFloat = 0.0
if let navigationTransition = self.navigationTransition {
collapsedTransitionOffset = -10.0 * navigationTransition.fraction
}
transition.updateFrame(view: self.titleCredibilityIconView, frame: CGRect(origin: CGPoint(x: leftOffset + collapsedTransitionOffset, y: floor((titleSize.height - credibilityIconSize.height) / 2.0)), size: credibilityIconSize))
transition.updateFrame(view: self.titleExpandedCredibilityIconView, frame: CGRect(origin: CGPoint(x: leftExpandedOffset, y: floor((titleExpandedSize.height - titleExpandedCredibilityIconSize.height) / 2.0) + 1.0), size: titleExpandedCredibilityIconSize))
nextIconX += 4.0 + credibilityIconSize.width
nextExpandedIconX += 4.0 + titleExpandedCredibilityIconSize.width
}
if let verifiedIconSize = self.verifiedIconSize, let titleExpandedVerifiedIconSize = self.titleExpandedVerifiedIconSize, verifiedIconSize.width > 0.0 {
let leftOffset: CGFloat
let leftExpandedOffset: CGFloat
if case .verified = verifiedIcon {
titleHorizontalOffset -= (verifiedIconSize.width + 4.0) / 2.0
leftOffset = nextIconX + 4.0
leftExpandedOffset = nextExpandedIconX + 4.0
} else {
titleHorizontalOffset += (verifiedIconSize.width + 4.0) / 2.0
titleExpandedHorizontalOffset += titleExpandedVerifiedIconSize.width - 2.0
leftOffset = -verifiedIconSize.width - 4.0
leftExpandedOffset = -titleExpandedVerifiedIconSize.width - 4.0
}
var collapsedTransitionOffset: CGFloat = 0.0
if let navigationTransition = self.navigationTransition {
collapsedTransitionOffset = -10.0 * navigationTransition.fraction
}
transition.updateFrame(view: self.titleVerifiedIconView, frame: CGRect(origin: CGPoint(x: leftOffset + collapsedTransitionOffset, y: floor((titleSize.height - verifiedIconSize.height) / 2.0)), size: verifiedIconSize))
transition.updateFrame(view: self.titleExpandedVerifiedIconView, frame: CGRect(origin: CGPoint(x: leftExpandedOffset, y: floor((titleExpandedSize.height - titleExpandedVerifiedIconSize.height) / 2.0) + 1.0), size: titleExpandedVerifiedIconSize))
if case .verified = verifiedIcon {
nextIconX += 4.0 + verifiedIconSize.width
nextExpandedIconX += 4.0 + titleExpandedVerifiedIconSize.width
}
}
var titleFrame: CGRect
var subtitleFrame: CGRect
let usernameFrame: CGRect
let usernameSpacing: CGFloat = 4.0
let expandedTitleScale: CGFloat = 0.8
var bottomShadowHeight: CGFloat = 88.0
if !self.isSettings && !self.isMyProfile {
bottomShadowHeight += 100.0
}
let bottomShadowFrame = CGRect(origin: CGPoint(x: 0.0, y: expandedAvatarHeight - bottomShadowHeight), size: CGSize(width: width, height: bottomShadowHeight))
transition.updateFrame(node: self.avatarListNode.listContainerNode.bottomShadowNode, frame: bottomShadowFrame, beginWithCurrentState: true)
self.avatarListNode.listContainerNode.bottomShadowNode.update(size: bottomShadowFrame.size, transition: transition)
let singleTitleLockOffset: CGFloat = ((peer?.id == self.context.account.peerId && !self.isMyProfile) || subtitleSize.height.isZero) ? 8.0 : 0.0
let titleLockOffset: CGFloat = 7.0 + singleTitleLockOffset
let titleMaxLockOffset: CGFloat = 7.0
let titleOffset: CGFloat
let titleCollapseFraction: CGFloat
if self.isAvatarExpanded {
let minTitleSize = CGSize(width: titleSize.width * expandedTitleScale, height: titleSize.height * expandedTitleScale)
var minTitleFrame = CGRect(origin: CGPoint(x: 16.0, y: expandedAvatarHeight - 58.0 - UIScreenPixel + (subtitleSize.height.isZero ? 10.0 : 0.0)), size: minTitleSize)
if !self.isSettings && !self.isMyProfile {
minTitleFrame.origin.y -= 83.0
}
titleFrame = CGRect(origin: CGPoint(x: minTitleFrame.midX - titleSize.width / 2.0, y: minTitleFrame.midY - titleSize.height / 2.0), size: titleSize)
var titleCollapseOffset = titleFrame.midY - statusBarHeight - titleLockOffset
if case .regular = metrics.widthClass, !isSettings, !isMyProfile {
titleCollapseOffset -= 7.0
}
titleOffset = -min(titleCollapseOffset, contentOffset)
titleCollapseFraction = max(0.0, min(1.0, contentOffset / titleCollapseOffset))
subtitleFrame = CGRect(origin: CGPoint(x: 16.0, y: minTitleFrame.maxY + 2.0), size: subtitleSize)
usernameFrame = CGRect(origin: CGPoint(x: width - usernameSize.width - 16.0, y: minTitleFrame.midY - usernameSize.height / 2.0), size: usernameSize)
} else {
titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((width - titleSize.width) / 2.0), y: avatarFrame.maxY + 9.0 + (subtitleSize.height.isZero ? 11.0 : 0.0)), size: titleSize)
var titleCollapseOffset = titleFrame.midY - statusBarHeight - titleLockOffset
if case .regular = metrics.widthClass, !isSettings, !isMyProfile {
titleCollapseOffset -= 7.0
}
titleOffset = -min(titleCollapseOffset, contentOffset)
titleCollapseFraction = max(0.0, min(1.0, contentOffset / titleCollapseOffset))
var effectiveSubtitleWidth = subtitleSize.width
if let subtitleBadgeSize {
effectiveSubtitleWidth += (subtitleBadgeSize.width + 7.0) * (1.0 - titleCollapseFraction)
}
let totalSubtitleWidth = effectiveSubtitleWidth + usernameSpacing + usernameSize.width
if usernameSize.width == 0.0 {
subtitleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((width - effectiveSubtitleWidth) / 2.0), y: titleFrame.maxY + 1.0), size: subtitleSize)
usernameFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((width - usernameSize.width) / 2.0), y: subtitleFrame.maxY + 1.0), size: usernameSize)
} else {
subtitleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((width - totalSubtitleWidth) / 2.0), y: titleFrame.maxY + 1.0), size: subtitleSize)
usernameFrame = CGRect(origin: CGPoint(x: subtitleFrame.maxX + usernameSpacing, y: titleFrame.maxY + 1.0), size: usernameSize)
}
}
let titleMinScale: CGFloat = 0.6
let subtitleMinScale: CGFloat = 0.8
let avatarMinScale: CGFloat = 0.55
let apparentTitleLockOffset = (1.0 - titleCollapseFraction) * 0.0 + titleCollapseFraction * titleMaxLockOffset
let paneAreaExpansionDistance: CGFloat = 32.0
let effectiveAreaExpansionFraction: CGFloat
if state.isEditing {
effectiveAreaExpansionFraction = 0.0
} else if isSettings || isMyProfile {
var paneAreaExpansionDelta = (self.frame.maxY - navigationHeight) - contentOffset
paneAreaExpansionDelta = max(0.0, min(paneAreaExpansionDelta, paneAreaExpansionDistance))
effectiveAreaExpansionFraction = 1.0 - paneAreaExpansionDelta / paneAreaExpansionDistance
} else {
var paneAreaExpansionDelta = (paneContainerY - navigationHeight) - contentOffset
paneAreaExpansionDelta = max(0.0, min(paneAreaExpansionDelta, paneAreaExpansionDistance))
effectiveAreaExpansionFraction = 1.0 - paneAreaExpansionDelta / paneAreaExpansionDistance
}
let secondarySeparatorAlpha = 1.0 - effectiveAreaExpansionFraction
if self.navigationTransition == nil && !self.isSettings && effectiveSeparatorAlpha == 1.0 && secondarySeparatorAlpha < 1.0 {
effectiveSeparatorAlpha = secondarySeparatorAlpha
}
if self.customNavigationContentNode != nil {
effectiveSeparatorAlpha = 0.0
}
if state.isEditing {
effectiveSeparatorAlpha = 0.0
}
transition.updateAlpha(node: self.separatorNode, alpha: effectiveSeparatorAlpha)
self.titleNode.update(stateFractions: [
TitleNodeStateRegular: self.isAvatarExpanded ? 0.0 : 1.0,
TitleNodeStateExpanded: self.isAvatarExpanded ? 1.0 : 0.0
], transition: transition)
let subtitleAlpha: CGFloat
var subtitleOffset: CGFloat = 0.0
let panelSubtitleAlpha: CGFloat
var panelSubtitleOffset: CGFloat = 0.0
if self.isSettings {
subtitleAlpha = 1.0 - titleCollapseFraction
panelSubtitleAlpha = 0.0
} else {
if (panelSubtitleString?.text ?? subtitleStringText) != subtitleStringText {
subtitleAlpha = 1.0 - effectiveAreaExpansionFraction
panelSubtitleAlpha = effectiveAreaExpansionFraction
subtitleOffset = -effectiveAreaExpansionFraction * 5.0
panelSubtitleOffset = (1.0 - effectiveAreaExpansionFraction) * 5.0
} else {
if self.navigationTransition != nil {
subtitleAlpha = 1.0
panelSubtitleAlpha = 0.0
} else {
if effectiveAreaExpansionFraction == 1.0 {
subtitleAlpha = 0.0
panelSubtitleAlpha = 1.0
} else {
subtitleAlpha = 1.0
panelSubtitleAlpha = 0.0
}
}
}
}
self.subtitleNode.update(stateFractions: [
TitleNodeStateRegular: self.isAvatarExpanded ? 0.0 : 1.0,
TitleNodeStateExpanded: self.isAvatarExpanded ? 1.0 : 0.0
], alpha: subtitleAlpha, transition: transition)
self.panelSubtitleNode.update(stateFractions: [
TitleNodeStateRegular: self.isAvatarExpanded ? 0.0 : 1.0,
TitleNodeStateExpanded: self.isAvatarExpanded ? 1.0 : 0.0
], alpha: panelSubtitleAlpha, transition: transition)
self.usernameNode.update(stateFractions: [
TitleNodeStateRegular: self.isAvatarExpanded ? 0.0 : 1.0,
TitleNodeStateExpanded: self.isAvatarExpanded ? 1.0 : 0.0
], alpha: subtitleAlpha, transition: transition)
let avatarScale: CGFloat
let avatarOffset: CGFloat
if self.navigationTransition != nil {
if let transitionSourceAvatarFrame = transitionSourceAvatarFrame {
var trueAvatarSize = transitionSourceAvatarFrame.size
if let storyStats = self.avatarListNode.avatarContainerNode.avatarNode.storyStats, storyStats.unseenCount != 0 {
trueAvatarSize.width -= 1.33 * 4.0
trueAvatarSize.height -= 1.33 * 4.0
}
avatarScale = ((1.0 - transitionFraction) * avatarFrame.width + transitionFraction * trueAvatarSize.width) / avatarFrame.width
} else {
avatarScale = 1.0
}
avatarOffset = 0.0
} else {
//
avatarScale = 1.0 * (1.0 - titleCollapseFraction) + avatarMinScale * titleCollapseFraction
avatarOffset = apparentTitleLockOffset + 0.0 * (1.0 - titleCollapseFraction) + 10.0 * titleCollapseFraction
}
if let previousAvatarView = self.navigationTransition?.previousAvatarView, let transitionSourceAvatarFrame {
let previousScale = ((1.0 - transitionFraction) * avatarFrame.width + transitionFraction * transitionSourceAvatarFrame.width) / transitionSourceAvatarFrame.width
transition.updateAlpha(layer: previousAvatarView.layer, alpha: transitionFraction)
transition.updateTransformScale(layer: previousAvatarView.layer, scale: previousScale)
transition.updatePosition(layer: previousAvatarView.layer, position: self.view.convert(CGPoint(x: avatarCenter.x - (27.0 * (1.0 - transitionFraction) + 10 * transitionFraction), y: avatarCenter.y - (2.66 * (1.0 - transitionFraction) + 1.0 * transitionFraction)), to: previousAvatarView.superview))
}
if subtitleIsButton {
subtitleFrame.origin.y += 11.0 * (1.0 - titleCollapseFraction)
if let subtitleBackgroundButton = self.subtitleBackgroundButton {
transition.updateAlpha(node: subtitleBackgroundButton, alpha: (1.0 - titleCollapseFraction))
}
if let subtitleBackgroundNode = self.subtitleBackgroundNode {
transition.updateAlpha(node: subtitleBackgroundNode, alpha: (1.0 - titleCollapseFraction))
}
if let subtitleArrowNode = self.subtitleArrowNode {
transition.updateAlpha(node: subtitleArrowNode, alpha: (1.0 - titleCollapseFraction))
}
}
let avatarCornerRadius: CGFloat = isForum ? floor(avatarSize * 0.25) : avatarSize / 2.0
if self.isAvatarExpanded {
self.avatarListNode.listContainerNode.isHidden = false
if let transitionSourceAvatarFrame = transitionSourceAvatarFrame {
var trueAvatarSize = transitionSourceAvatarFrame.size
if let storyStats = self.avatarListNode.avatarContainerNode.avatarNode.storyStats, storyStats.unseenCount != 0 {
trueAvatarSize.width -= 1.33 * 4.0
trueAvatarSize.height -= 1.33 * 4.0
}
transition.updateCornerRadius(node: self.avatarListNode.listContainerNode, cornerRadius: transitionFraction * trueAvatarSize.width / 2.0)
transition.updateCornerRadius(node: self.avatarListNode.listContainerNode.controlsClippingNode, cornerRadius: transitionFraction * trueAvatarSize.width / 2.0)
} else {
transition.updateCornerRadius(node: self.avatarListNode.listContainerNode, cornerRadius: 0.0)
transition.updateCornerRadius(node: self.avatarListNode.listContainerNode.controlsClippingNode, cornerRadius: 0.0)
}
} else if self.avatarListNode.listContainerNode.cornerRadius != avatarCornerRadius {
transition.updateCornerRadius(node: self.avatarListNode.listContainerNode.controlsClippingNode, cornerRadius: avatarCornerRadius)
transition.updateCornerRadius(node: self.avatarListNode.listContainerNode, cornerRadius: avatarCornerRadius, completion: { [weak self] _ in
guard let strongSelf = self else {
return
}
strongSelf.avatarListNode.avatarContainerNode.canAttachVideo = true
strongSelf.avatarListNode.listContainerNode.isHidden = true
if !strongSelf.skipCollapseCompletion {
DispatchQueue.main.async {
strongSelf.avatarListNode.listContainerNode.isCollapsing = false
}
}
})
}
self.avatarListNode.update(size: CGSize(), avatarSize: avatarSize, isExpanded: self.isAvatarExpanded, peer: peer, isForum: isForum, threadId: self.forumTopicThreadId, threadInfo: threadData?.info, theme: presentationData.theme, transition: transition)
self.editingContentNode.avatarNode.update(peer: peer, threadData: threadData, chatLocation: self.chatLocation, item: self.avatarListNode.item, updatingAvatar: state.updatingAvatar, uploadProgress: state.avatarUploadProgress, theme: presentationData.theme, avatarSize: avatarSize, isEditing: state.isEditing)
self.avatarOverlayNode.update(peer: peer, threadData: threadData, chatLocation: self.chatLocation, item: self.avatarListNode.item, updatingAvatar: state.updatingAvatar, uploadProgress: state.avatarUploadProgress, theme: presentationData.theme, avatarSize: avatarSize, isEditing: state.isEditing)
if additive {
transition.updateSublayerTransformScaleAdditive(node: self.avatarListNode.avatarContainerNode, scale: avatarScale)
transition.updateSublayerTransformScaleAdditive(node: self.avatarOverlayNode, scale: avatarScale)
} else {
transition.updateSublayerTransformScale(node: self.avatarListNode.avatarContainerNode, scale: avatarScale)
transition.updateSublayerTransformScale(node: self.avatarOverlayNode, scale: avatarScale)
}
if let avatarStoryView = self.avatarListNode.avatarContainerNode.avatarStoryView?.view {
transition.updateAlpha(layer: avatarStoryView.layer, alpha: 1.0 - transitionFraction)
}
var apparentAvatarFrame: CGRect
var apparentAvatarListFrame: CGRect
let controlsClippingFrame: CGRect
if self.isAvatarExpanded {
let expandedAvatarCenter = CGPoint(x: expandedAvatarListSize.width / 2.0, y: expandedAvatarListSize.width / 2.0 - contentOffset / 2.0)
apparentAvatarFrame = CGRect(origin: CGPoint(x: expandedAvatarCenter.x * (1.0 - transitionFraction) + transitionFraction * avatarCenter.x, y: expandedAvatarCenter.y * (1.0 - transitionFraction) + transitionFraction * avatarCenter.y), size: CGSize())
let expandedAvatarListCenter = CGPoint(x: expandedAvatarListSize.width / 2.0, y: expandedAvatarListSize.height / 2.0 - contentOffset / 2.0)
apparentAvatarListFrame = CGRect(origin: CGPoint(x: expandedAvatarListCenter.x * (1.0 - transitionFraction) + transitionFraction * avatarCenter.x, y: expandedAvatarListCenter.y * (1.0 - transitionFraction) + transitionFraction * avatarCenter.y), size: CGSize())
if let transitionSourceAvatarFrame = transitionSourceAvatarFrame {
var trueAvatarSize = transitionSourceAvatarFrame.size
if let storyStats = self.avatarListNode.avatarContainerNode.avatarNode.storyStats, storyStats.unseenCount != 0 {
trueAvatarSize.width -= 1.33 * 4.0
trueAvatarSize.height -= 1.33 * 4.0
}
let trueAvatarFrame = trueAvatarSize.centered(around: transitionSourceAvatarFrame.center)
let expandedFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: expandedAvatarListSize)
controlsClippingFrame = CGRect(origin: CGPoint(x: transitionFraction * trueAvatarFrame.minX + (1.0 - transitionFraction) * expandedFrame.minX, y: transitionFraction * trueAvatarFrame.minY + (1.0 - transitionFraction) * expandedFrame.minY), size: CGSize(width: transitionFraction * trueAvatarFrame.width + (1.0 - transitionFraction) * expandedFrame.width, height: transitionFraction * trueAvatarFrame.height + (1.0 - transitionFraction) * expandedFrame.height))
} else {
controlsClippingFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: expandedAvatarListSize)
}
} else {
var trueAvatarSize = avatarFrame.size
if let storyStats = self.avatarListNode.avatarContainerNode.avatarNode.storyStats, storyStats.totalCount != 0 {
trueAvatarSize.width -= 3.0 * 4.0
trueAvatarSize.height -= 3.0 * 4.0
}
apparentAvatarFrame = CGRect(origin: CGPoint(x: avatarCenter.x - trueAvatarSize.width / 2.0, y: -contentOffset + avatarOffset + avatarCenter.y - trueAvatarSize.height / 2.0), size: trueAvatarSize)
apparentAvatarListFrame = apparentAvatarFrame
controlsClippingFrame = apparentAvatarFrame
}
let avatarClipOffset: CGFloat = !self.isAvatarExpanded && deviceMetrics.hasDynamicIsland && statusBarHeight > 0.0 && self.avatarClippingNode.clipsToBounds && !isLandscape ? 47.0 : 0.0
let clippingNodeTransition = ContainedViewLayoutTransition.immediate
clippingNodeTransition.updateFrame(layer: self.avatarClippingNode.layer, frame: CGRect(origin: CGPoint(x: 0.0, y: avatarClipOffset), size: CGSize(width: width, height: 1000.0)))
clippingNodeTransition.updateSublayerTransformOffset(layer: self.avatarClippingNode.layer, offset: CGPoint(x: 0.0, y: -avatarClipOffset))
let clippingNodeRadiusTransition = ContainedViewLayoutTransition.animated(duration: 0.15, curve: .easeInOut)
clippingNodeRadiusTransition.updateCornerRadius(node: self.avatarClippingNode, cornerRadius: avatarClipOffset > 0.0 ? width / 2.5 : 0.0)
let _ = apparentAvatarListFrame
transition.updateFrameAdditive(node: self.avatarListNode, frame: CGRect(origin: apparentAvatarFrame.center, size: CGSize()))
transition.updateFrameAdditive(node: self.avatarOverlayNode, frame: CGRect(origin: apparentAvatarFrame.center, size: CGSize()))
let avatarListContainerFrame: CGRect
let avatarListContainerScale: CGFloat
if self.isAvatarExpanded {
if let transitionSourceAvatarFrame = transitionSourceAvatarFrame {
let neutralAvatarListContainerSize = expandedAvatarListSize
var avatarListContainerSize = CGSize(width: neutralAvatarListContainerSize.width * (1.0 - transitionFraction) + transitionSourceAvatarFrame.width * transitionFraction, height: neutralAvatarListContainerSize.height * (1.0 - transitionFraction) + transitionSourceAvatarFrame.height * transitionFraction)
if let storyStats = self.avatarListNode.avatarContainerNode.avatarNode.storyStats, storyStats.unseenCount != 0 {
avatarListContainerSize.width -= 1.33 * 5.0
avatarListContainerSize.height -= 1.33 * 5.0
}
avatarListContainerFrame = CGRect(origin: CGPoint(x: -avatarListContainerSize.width / 2.0, y: -avatarListContainerSize.width / 2.0), size: avatarListContainerSize)
} else {
avatarListContainerFrame = CGRect(origin: CGPoint(x: -expandedAvatarListSize.width / 2.0, y: -expandedAvatarListSize.width / 2.0), size: expandedAvatarListSize)
}
avatarListContainerScale = 1.0 + max(0.0, -contentOffset / avatarListContainerFrame.width)
} else {
let expandHeightFraction = expandedAvatarListSize.height / expandedAvatarListSize.width
avatarListContainerFrame = CGRect(origin: CGPoint(x: -apparentAvatarFrame.width / 2.0, y: -apparentAvatarFrame.width / 2.0 + expandHeightFraction * 0.0 * apparentAvatarFrame.width), size: apparentAvatarFrame.size)
avatarListContainerScale = avatarScale
}
transition.updateFrame(node: self.avatarListNode.listContainerNode, frame: avatarListContainerFrame)
let innerScale = avatarListContainerFrame.width / expandedAvatarListSize.width
let innerDeltaX = (avatarListContainerFrame.width - expandedAvatarListSize.width) / 2.0
var innerDeltaY = (avatarListContainerFrame.height - expandedAvatarListSize.height) / 2.0
if !self.isAvatarExpanded {
innerDeltaY += (expandedAvatarListSize.height - expandedAvatarListSize.width) * 0.5
}
transition.updateSublayerTransformScale(node: self.avatarListNode.listContainerNode, scale: innerScale)
transition.updateFrameAdditive(node: self.avatarListNode.listContainerNode.contentNode, frame: CGRect(origin: CGPoint(x: innerDeltaX + expandedAvatarListSize.width / 2.0, y: innerDeltaY + expandedAvatarListSize.height / 2.0), size: CGSize()))
self.avatarListNode.listContainerNode.contentNode.update(size: expandedAvatarListSize)
transition.updateFrameAdditive(node: self.avatarListNode.listContainerNode.controlsClippingOffsetNode, frame: CGRect(origin: controlsClippingFrame.center, size: CGSize()))
transition.updateFrame(node: self.avatarListNode.listContainerNode.controlsClippingNode, frame: CGRect(origin: CGPoint(x: -controlsClippingFrame.width / 2.0, y: -controlsClippingFrame.height / 2.0), size: controlsClippingFrame.size))
transition.updateFrameAdditive(node: self.avatarListNode.listContainerNode.controlsContainerNode, frame: CGRect(origin: CGPoint(x: -controlsClippingFrame.minX, y: -controlsClippingFrame.minY), size: CGSize(width: expandedAvatarListSize.width, height: expandedAvatarListSize.height)))
transition.updateFrame(node: self.avatarListNode.listContainerNode.topShadowNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: expandedAvatarListSize.width, height: navigationHeight + 20.0)))
transition.updateFrame(node: self.avatarListNode.listContainerNode.stripContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: statusBarHeight < 25.0 ? (statusBarHeight + 2.0) : (statusBarHeight - 3.0)), size: CGSize(width: expandedAvatarListSize.width, height: 2.0)))
transition.updateFrame(node: self.avatarListNode.listContainerNode.highlightContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: expandedAvatarListSize.width, height: expandedAvatarListSize.height)))
transition.updateAlpha(node: self.avatarListNode.listContainerNode.controlsContainerNode, alpha: self.isAvatarExpanded ? (1.0 - transitionFraction) : 0.0)
if additive {
transition.updateSublayerTransformScaleAdditive(node: self.avatarListNode.listContainerTransformNode, scale: avatarListContainerScale)
} else {
transition.updateSublayerTransformScale(node: self.avatarListNode.listContainerTransformNode, scale: avatarListContainerScale)
}
if deviceMetrics.hasDynamicIsland && statusBarHeight > 0.0 && self.forumTopicThreadId == nil && self.navigationTransition == nil && !isLandscape {
let maskValue = max(0.0, min(1.0, contentOffset / 120.0))
self.avatarListNode.containerNode.view.mask = self.avatarListNode.maskNode.view
if maskValue > 0.03 {
self.avatarListNode.bottomCoverNode.isHidden = false
self.avatarListNode.topCoverNode.isHidden = false
self.avatarListNode.maskNode.backgroundColor = .clear
} else {
self.avatarListNode.bottomCoverNode.isHidden = true
self.avatarListNode.topCoverNode.isHidden = true
self.avatarListNode.maskNode.backgroundColor = .white
}
self.avatarListNode.topCoverNode.update(maskValue)
self.avatarListNode.maskNode.update(maskValue)
self.avatarListNode.bottomCoverNode.backgroundColor = UIColor(white: 0.0, alpha: maskValue)
self.avatarListNode.listContainerNode.topShadowNode.isHidden = !self.isAvatarExpanded
var avatarMaskOffset: CGFloat = 0.0
if contentOffset < 0.0 {
avatarMaskOffset -= contentOffset
}
self.avatarListNode.maskNode.position = CGPoint(x: 0.0, y: -self.avatarListNode.frame.minY + 48.0 + 85.0 + avatarMaskOffset)
self.avatarListNode.maskNode.bounds = CGRect(origin: .zero, size: CGSize(width: 171.0, height: 171.0))
self.avatarListNode.bottomCoverNode.position = self.avatarListNode.maskNode.position
self.avatarListNode.bottomCoverNode.bounds = self.avatarListNode.maskNode.bounds
self.avatarListNode.topCoverNode.position = self.avatarListNode.maskNode.position
self.avatarListNode.topCoverNode.bounds = self.avatarListNode.maskNode.bounds
} else {
self.avatarListNode.bottomCoverNode.isHidden = true
self.avatarListNode.topCoverNode.isHidden = true
self.avatarListNode.containerNode.view.mask = nil
}
self.avatarListNode.listContainerNode.update(size: expandedAvatarListSize, peer: peer.flatMap(EnginePeer.init), isExpanded: self.isAvatarExpanded, transition: transition)
if self.avatarListNode.listContainerNode.isCollapsing && !self.ignoreCollapse {
self.avatarListNode.avatarContainerNode.canAttachVideo = false
}
let rawHeight: CGFloat
let height: CGFloat
let maxY: CGFloat
let backgroundHeight: CGFloat
if self.isAvatarExpanded {
rawHeight = expandedAvatarHeight
height = max(navigationHeight, rawHeight - contentOffset)
maxY = height - 98.0
backgroundHeight = height
} else {
rawHeight = navigationHeight + panelWithAvatarHeight
var expandablePart: CGFloat = panelWithAvatarHeight - contentOffset
if self.isSettings || self.isMyProfile {
expandablePart += 20.0
} else {
if case let .replyThread(replyThreadMessage) = self.chatLocation, replyThreadMessage.peerId == self.context.account.peerId {
expandablePart = 0.0
} else if peer?.id == self.context.account.peerId && !self.isMyProfile {
expandablePart = 0.0
} else {
expandablePart += 99.0
}
}
height = navigationHeight + max(0.0, expandablePart)
maxY = navigationHeight + panelWithAvatarHeight - contentOffset
backgroundHeight = height
}
let _ = maxY
let apparentHeight = (1.0 - transitionFraction) * backgroundHeight + transitionFraction * transitionSourceHeight
let apparentBackgroundHeight = (1.0 - transitionFraction) * backgroundHeight + transitionFraction * transitionSourceHeight
if !titleSize.width.isZero && !titleSize.height.isZero {
if self.navigationTransition != nil {
var neutralTitleScale: CGFloat = 1.0
var neutralSubtitleScale: CGFloat = 1.0
if self.isAvatarExpanded {
neutralTitleScale = expandedTitleScale
neutralSubtitleScale = 1.0
}
let titleScale = (transitionFraction * transitionSourceTitleFrame.height + (1.0 - transitionFraction) * titleFrame.height * neutralTitleScale) / (titleFrame.height)
let subtitleScale = max(0.01, min(10.0, (transitionFraction * transitionSourceSubtitleFrame.height + (1.0 - transitionFraction) * subtitleFrame.height * neutralSubtitleScale) / (subtitleFrame.height)))
var titleFrame = titleFrame
if !self.isAvatarExpanded {
titleFrame = titleFrame.offsetBy(dx: titleHorizontalOffset * titleScale, dy: 0.0)
} else {
titleFrame = titleFrame.offsetBy(dx: titleExpandedHorizontalOffset, dy: 0.0)
}
let titleCenter = CGPoint(x: transitionFraction * transitionSourceTitleFrame.midX + (1.0 - transitionFraction) * titleFrame.midX, y: transitionFraction * transitionSourceTitleFrame.midY + (1.0 - transitionFraction) * titleFrame.midY)
let subtitleCenter = CGPoint(x: transitionFraction * transitionSourceSubtitleFrame.midX + (1.0 - transitionFraction) * subtitleFrame.midX, y: transitionFraction * transitionSourceSubtitleFrame.midY + (1.0 - transitionFraction) * subtitleFrame.midY)
let rawTitleFrame = CGRect(origin: CGPoint(x: titleCenter.x - titleFrame.size.width * neutralTitleScale / 2.0, y: titleCenter.y - titleFrame.size.height * neutralTitleScale / 2.0), size: CGSize(width: titleFrame.size.width * neutralTitleScale, height: titleFrame.size.height * neutralTitleScale))
self.titleNodeRawContainer.frame = rawTitleFrame
transition.updateFrameAdditiveToCenter(node: self.titleNodeContainer, frame: CGRect(origin: rawTitleFrame.center, size: CGSize()))
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(), size: CGSize()))
let rawSubtitleFrame = CGRect(origin: CGPoint(x: subtitleCenter.x - subtitleFrame.size.width / 2.0, y: subtitleCenter.y - subtitleFrame.size.height / 2.0), size: subtitleFrame.size)
self.subtitleNodeRawContainer.frame = rawSubtitleFrame
transition.updateFrameAdditiveToCenter(node: self.subtitleNodeContainer, frame: CGRect(origin: rawSubtitleFrame.center, size: CGSize()))
transition.updateFrame(node: self.subtitleNode, frame: CGRect(origin: CGPoint(x: 0.0, y: subtitleOffset), size: CGSize()))
transition.updateFrame(node: self.panelSubtitleNode, frame: CGRect(origin: CGPoint(x: 0.0, y: panelSubtitleOffset - 1.0), size: CGSize()))
transition.updateFrame(node: self.usernameNode, frame: CGRect(origin: CGPoint(), size: CGSize()))
transition.updateSublayerTransformScale(node: self.titleNodeContainer, scale: titleScale)
transition.updateSublayerTransformScale(node: self.subtitleNodeContainer, scale: subtitleScale)
transition.updateSublayerTransformScale(node: self.usernameNodeContainer, scale: subtitleScale)
if let subtitleBadgeView = self.subtitleBadgeView, let subtitleBadgeSize {
let subtitleBadgeFrame = CGRect(origin: CGPoint(x: (subtitleSize.width + 8.0) * 0.5, y: floor((-subtitleBadgeSize.height) * 0.5)), size: subtitleBadgeSize)
transition.updateFrameAdditive(view: subtitleBadgeView, frame: subtitleBadgeFrame)
transition.updateAlpha(layer: subtitleBadgeView.layer, alpha: (1.0 - transitionFraction))
}
} else {
let titleScale: CGFloat
let subtitleScale: CGFloat
var subtitleOffset: CGFloat = 0.0
let subtitleBadgeFraction: CGFloat
if self.isAvatarExpanded {
titleScale = expandedTitleScale
subtitleScale = 1.0
subtitleBadgeFraction = 1.0
} else {
titleScale = (1.0 - titleCollapseFraction) * 1.0 + titleCollapseFraction * titleMinScale
subtitleScale = (1.0 - titleCollapseFraction) * 1.0 + titleCollapseFraction * subtitleMinScale
subtitleOffset = titleCollapseFraction * -1.0
subtitleBadgeFraction = (1.0 - titleCollapseFraction)
}
let rawTitleFrame = titleFrame.offsetBy(dx: self.isAvatarExpanded ? titleExpandedHorizontalOffset : titleHorizontalOffset * titleScale, dy: 0.0)
self.titleNodeRawContainer.frame = rawTitleFrame
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(), size: CGSize()))
let rawSubtitleFrame = subtitleFrame
self.subtitleNodeRawContainer.frame = rawSubtitleFrame
let rawUsernameFrame = usernameFrame
self.usernameNodeRawContainer.frame = rawUsernameFrame
if self.isAvatarExpanded {
transition.updateFrameAdditive(node: self.titleNodeContainer, frame: CGRect(origin: rawTitleFrame.center, size: CGSize()).offsetBy(dx: 0.0, dy: titleOffset + apparentTitleLockOffset))
transition.updateFrameAdditive(node: self.subtitleNodeContainer, frame: CGRect(origin: rawSubtitleFrame.center, size: CGSize()).offsetBy(dx: 0.0, dy: titleOffset))
transition.updateFrameAdditive(node: self.usernameNodeContainer, frame: CGRect(origin: rawUsernameFrame.center, size: CGSize()).offsetBy(dx: 0.0, dy: titleOffset))
} else {
transition.updateFrameAdditiveToCenter(node: self.titleNodeContainer, frame: CGRect(origin: rawTitleFrame.center, size: CGSize()).offsetBy(dx: 0.0, dy: titleOffset + apparentTitleLockOffset))
var subtitleCenter = rawSubtitleFrame.center
subtitleCenter.x = rawTitleFrame.center.x + (subtitleCenter.x - rawTitleFrame.center.x) * subtitleScale
subtitleCenter.y += subtitleOffset
transition.updateFrameAdditiveToCenter(node: self.subtitleNodeContainer, frame: CGRect(origin: subtitleCenter, size: CGSize()).offsetBy(dx: 0.0, dy: titleOffset))
var usernameCenter = rawUsernameFrame.center
usernameCenter.x = rawTitleFrame.center.x + (usernameCenter.x - rawTitleFrame.center.x) * subtitleScale
transition.updateFrameAdditiveToCenter(node: self.usernameNodeContainer, frame: CGRect(origin: usernameCenter, size: CGSize()).offsetBy(dx: 0.0, dy: titleOffset))
}
transition.updateFrame(node: self.subtitleNode, frame: CGRect(origin: CGPoint(x: 0.0, y: subtitleOffset), size: CGSize()))
transition.updateFrame(node: self.panelSubtitleNode, frame: CGRect(origin: CGPoint(x: 0.0, y: panelSubtitleOffset - 1.0), size: CGSize()))
transition.updateFrame(node: self.usernameNode, frame: CGRect(origin: CGPoint(), size: CGSize()))
transition.updateSublayerTransformScaleAdditive(node: self.titleNodeContainer, scale: titleScale)
transition.updateSublayerTransformScaleAdditive(node: self.subtitleNodeContainer, scale: subtitleScale)
transition.updateSublayerTransformScaleAdditive(node: self.usernameNodeContainer, scale: subtitleScale)
if let subtitleBadgeView = self.subtitleBadgeView, let subtitleBadgeSize {
let subtitleBadgeFrame = CGRect(origin: CGPoint(x: (subtitleSize.width + 8.0) * 0.5, y: floor((-subtitleBadgeSize.height) * 0.5)), size: subtitleBadgeSize)
transition.updateFrameAdditive(view: subtitleBadgeView, frame: subtitleBadgeFrame)
transition.updateAlpha(layer: subtitleBadgeView.layer, alpha: (1.0 - transitionFraction) * subtitleBadgeFraction)
}
}
}
if displayStandardTitle {
self.titleNode.isHidden = true
let standardTitle: ComponentView<Empty>
if let current = self.standardTitle {
standardTitle = current
} else {
standardTitle = ComponentView()
self.standardTitle = standardTitle
}
let titleSize = standardTitle.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: titleStringText, font: Font.semibold(17.0), textColor: navigationContentsPrimaryColor))
)),
environment: {},
containerSize: CGSize(width: width, height: navigationHeight)
)
if let standardTitleView = standardTitle.view {
if standardTitleView.superview == nil {
self.regularContentNode.view.addSubview(standardTitleView)
}
let standardTitleFrame = titleSize.centered(in: self.titleNodeContainer.frame).offsetBy(dx: 2.0, dy: 0.0)
standardTitleView.frame = standardTitleFrame
}
} else {
if let standardTitle = self.standardTitle {
self.standardTitle = nil
standardTitle.view?.removeFromSuperview()
self.titleNode.isHidden = false
}
}
let buttonsTransitionDistance: CGFloat = -min(0.0, apparentBackgroundHeight - backgroundHeight)
let buttonsTransitionDistanceNorm: CGFloat = 40.0
let innerContentOffset = max(0.0, contentOffset - 140.0)
let backgroundTransitionFraction: CGFloat = 1.0 - max(0.0, min(1.0, innerContentOffset / 30.0))
let innerButtonsTransitionStepDistance: CGFloat = 58.0
let innerButtonsTransitionStepInset: CGFloat = 28.0
let innerButtonsTransitionDistance: CGFloat = navigationHeight + panelWithAvatarHeight - innerButtonsTransitionStepDistance - innerButtonsTransitionStepInset
let innerButtonsContentOffset = max(0.0, contentOffset - innerButtonsTransitionDistance)
let innerButtonsTransitionFraction = max(0.0, min(1.0, innerButtonsContentOffset / innerButtonsTransitionStepDistance))
let buttonsTransitionFraction: CGFloat = 1.0 - max(0.0, min(1.0, buttonsTransitionDistance / buttonsTransitionDistanceNorm))
let buttonSpacing: CGFloat = 8.0
let buttonSideInset = max(16.0, containerInset)
let actionButtonWidth = (width - buttonSideInset * 2.0 + buttonSpacing) / CGFloat(actionButtonKeys.count) - buttonSpacing
let actionButtonSize = CGSize(width: actionButtonWidth, height: 40.0)
var actionButtonRightOrigin = CGPoint(x: width - buttonSideInset, y: backgroundHeight - 16.0 - actionButtonSize.height)
for buttonKey in actionButtonKeys.reversed() {
let buttonNode: PeerInfoHeaderActionButtonNode
var wasAdded = false
if let current = self.actionButtonNodes[buttonKey] {
buttonNode = current
} else {
wasAdded = true
buttonNode = PeerInfoHeaderActionButtonNode(key: buttonKey, action: { [weak self] buttonNode, gesture in
self?.actionButtonPressed(buttonNode, gesture: gesture)
})
self.actionButtonNodes[buttonKey] = buttonNode
self.buttonsContainerNode.addSubnode(buttonNode)
}
let buttonFrame = CGRect(origin: CGPoint(x: actionButtonRightOrigin.x - actionButtonSize.width, y: actionButtonRightOrigin.y), size: actionButtonSize)
let buttonTransition: ContainedViewLayoutTransition = wasAdded ? .immediate : transition
if additive {
buttonTransition.updateFrameAdditiveToCenter(node: buttonNode, frame: buttonFrame)
} else {
buttonTransition.updateFrame(node: buttonNode, frame: buttonFrame)
}
let buttonText: String
switch buttonKey {
case .message:
buttonText = "Message"
case .addContact:
buttonText = "Add"
default:
fatalError()
}
buttonNode.update(size: buttonFrame.size, text: buttonText, presentationData: presentationData, transition: buttonTransition)
if wasAdded {
buttonNode.alpha = 0.0
}
transition.updateAlpha(node: buttonNode, alpha: 1.0)
actionButtonRightOrigin.x -= actionButtonSize.width + buttonSpacing
}
for key in self.actionButtonNodes.keys {
if !actionButtonKeys.contains(key) {
if let buttonNode = self.actionButtonNodes[key] {
self.actionButtonNodes.removeValue(forKey: key)
transition.updateAlpha(node: buttonNode, alpha: 0.0) { [weak buttonNode] _ in
buttonNode?.removeFromSupernode()
}
}
}
}
let buttonWidth = (width - buttonSideInset * 2.0 + buttonSpacing) / CGFloat(buttonKeys.count) - buttonSpacing
let buttonSize = CGSize(width: buttonWidth, height: 58.0)
var buttonRightOrigin = CGPoint(x: width - buttonSideInset, y: backgroundHeight - 16.0 - buttonSize.height)
if !actionButtonKeys.isEmpty {
buttonRightOrigin.y += actionButtonSize.height + 24.0
}
transition.updateFrameAdditive(node: self.buttonsBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: buttonRightOrigin.y), size: CGSize(width: width, height: buttonSize.height)))
self.buttonsBackgroundNode.update(size: self.buttonsBackgroundNode.bounds.size, transition: transition)
self.buttonsBackgroundNode.updateColor(color: contentButtonBackgroundColor, enableBlur: true, transition: transition)
for buttonKey in buttonKeys.reversed() {
let buttonNode: PeerInfoHeaderButtonNode
var wasAdded = false
if let current = self.buttonNodes[buttonKey] {
buttonNode = current
} else {
wasAdded = true
buttonNode = PeerInfoHeaderButtonNode(key: buttonKey, action: { [weak self] buttonNode, gesture in
self?.buttonPressed(buttonNode, gesture: gesture)
})
self.buttonNodes[buttonKey] = buttonNode
self.buttonsContainerNode.addSubnode(buttonNode)
self.buttonsMaskView.addSubview(buttonNode.backgroundContainerView)
}
let buttonFrame = CGRect(origin: CGPoint(x: buttonRightOrigin.x - buttonSize.width, y: buttonRightOrigin.y), size: buttonSize)
let buttonTransition: ContainedViewLayoutTransition = wasAdded ? .immediate : transition
if additive {
buttonTransition.updateFrameAdditiveToCenter(node: buttonNode, frame: buttonFrame)
} else {
buttonTransition.updateFrame(node: buttonNode, frame: buttonFrame)
}
buttonTransition.updateFrame(view: buttonNode.backgroundContainerView, frame: buttonFrame.offsetBy(dx: 0.0, dy: -buttonFrame.minY))
let buttonText: String
let buttonIcon: PeerInfoHeaderButtonIcon
switch buttonKey {
case .message:
buttonText = presentationData.strings.PeerInfo_ButtonMessage
buttonIcon = .message
case .discussion:
buttonText = presentationData.strings.PeerInfo_ButtonDiscuss
buttonIcon = .message
case .call:
buttonText = presentationData.strings.PeerInfo_ButtonCall
buttonIcon = .call
case .videoCall:
buttonText = presentationData.strings.PeerInfo_ButtonVideoCall
buttonIcon = .videoCall
case .voiceChat:
if let channel = peer as? TelegramChannel, case .broadcast = channel.info {
buttonText = presentationData.strings.PeerInfo_ButtonLiveStream
} else {
buttonText = presentationData.strings.PeerInfo_ButtonVoiceChat
}
buttonIcon = .voiceChat
case .mute:
let chatIsMuted = peerInfoIsChatMuted(peer: peer, peerNotificationSettings: peerNotificationSettings, threadNotificationSettings: threadNotificationSettings, globalNotificationSettings: globalNotificationSettings)
if chatIsMuted {
buttonText = presentationData.strings.PeerInfo_ButtonUnmute
buttonIcon = .unmute
} else {
buttonText = presentationData.strings.PeerInfo_ButtonMute
buttonIcon = .mute
}
case .more:
buttonText = presentationData.strings.PeerInfo_ButtonMore
buttonIcon = .more
case .addMember:
buttonText = presentationData.strings.PeerInfo_ButtonAddMember
buttonIcon = .addMember
case .search:
buttonText = presentationData.strings.PeerInfo_ButtonSearch
buttonIcon = .search
case .leave:
buttonText = presentationData.strings.PeerInfo_ButtonLeave
buttonIcon = .leave
case .stop:
buttonText = presentationData.strings.PeerInfo_ButtonStop
buttonIcon = .stop
case .addContact:
fatalError()
}
var isActive = true
if let highlightedButton = state.highlightedButton {
isActive = buttonKey == highlightedButton
}
buttonNode.update(size: buttonFrame.size, text: buttonText, icon: buttonIcon, isActive: isActive, presentationData: presentationData, backgroundColor: contentButtonBackgroundColor, foregroundColor: contentButtonForegroundColor, fraction: 1.0 - innerButtonsTransitionFraction, transition: buttonTransition)
if wasAdded {
buttonNode.alpha = 0.0
buttonNode.backgroundContainerView.alpha = 0.0
}
transition.updateAlpha(node: buttonNode, alpha: buttonsTransitionFraction)
transition.updateAlpha(layer: buttonNode.backgroundContainerView.layer, alpha: buttonsTransitionFraction)
if case .mute = buttonKey, buttonNode.containerNode.alpha.isZero, additive {
if case let .animated(duration, curve) = transition {
ContainedViewLayoutTransition.animated(duration: duration * 0.3, curve: curve).updateAlpha(node: buttonNode.containerNode, alpha: 1.0)
} else {
transition.updateAlpha(node: buttonNode.containerNode, alpha: 1.0)
}
} else {
transition.updateAlpha(node: buttonNode.containerNode, alpha: 1.0)
}
buttonRightOrigin.x -= buttonSize.width + buttonSpacing
}
for key in self.buttonNodes.keys {
if !buttonKeys.contains(key) {
if let buttonNode = self.buttonNodes[key] {
self.buttonNodes.removeValue(forKey: key)
transition.updateAlpha(layer: buttonNode.backgroundContainerView.layer, alpha: 0.0)
transition.updateAlpha(node: buttonNode, alpha: 0.0) { [weak buttonNode] _ in
buttonNode?.backgroundContainerView.removeFromSuperview()
buttonNode?.removeFromSupernode()
}
}
}
}
let resolvedRegularHeight: CGFloat
if self.isAvatarExpanded {
resolvedRegularHeight = expandedAvatarListSize.height
} else {
resolvedRegularHeight = panelWithAvatarHeight + navigationHeight
}
let backgroundFrame: CGRect
let separatorFrame: CGRect
var resolvedHeight: CGFloat
if state.isEditing {
resolvedHeight = editingContentHeight
backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: -2000.0 + max(navigationHeight, resolvedHeight - contentOffset)), size: CGSize(width: width, height: 2000.0))
separatorFrame = CGRect(origin: CGPoint(x: 0.0, y: max(navigationHeight, resolvedHeight - contentOffset)), size: CGSize(width: width, height: UIScreenPixel))
} else {
resolvedHeight = resolvedRegularHeight
backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: -2000.0 + apparentHeight), size: CGSize(width: width, height: 2000.0))
separatorFrame = CGRect(origin: CGPoint(x: 0.0, y: apparentHeight), size: CGSize(width: width, height: UIScreenPixel))
}
transition.updateFrame(node: self.regularContentNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: width, height: resolvedHeight)))
transition.updateFrameAdditive(node: self.buttonsContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: apparentBackgroundHeight - backgroundHeight), size: CGSize(width: width, height: 1000.0)))
navigationTransition.updateAlpha(node: self.buttonsContainerNode, alpha: backgroundBannerAlpha)
let bannerInset: CGFloat = 3.0
let bannerFrame = CGRect(origin: CGPoint(x: -bannerInset, y: -2000.0 + apparentBackgroundHeight), size: CGSize(width: width + bannerInset * 2.0, height: 2000.0))
if additive {
transition.updateFrameAdditive(view: self.backgroundBannerView, frame: bannerFrame)
} else {
transition.updateFrame(view: self.backgroundBannerView, frame: bannerFrame)
}
let backgroundCoverSubject: PeerInfoCoverComponent.Subject?
var backgroundCoverAnimateIn = false
if let status = peer?.emojiStatus, case .starGift = status.content {
backgroundCoverSubject = .status(status)
if !self.didSetupBackgroundCover {
backgroundCoverAnimateIn = true
self.didSetupBackgroundCover = true
}
} else if let peer {
backgroundCoverSubject = .peer(EnginePeer(peer))
} else {
backgroundCoverSubject = nil
}
let backgroundCoverSize = self.backgroundCover.update(
transition: ComponentTransition(transition),
component: AnyComponent(PeerInfoCoverComponent(
context: self.context,
subject: backgroundCoverSubject,
files: [:],
isDark: presentationData.theme.overallDarkAppearance,
avatarCenter: apparentAvatarFrame.center,
avatarScale: avatarScale,
defaultHeight: 254.0,
avatarTransitionFraction: max(0.0, min(1.0, titleCollapseFraction + transitionFraction * 2.0)),
patternTransitionFraction: buttonsTransitionFraction * backgroundTransitionFraction
)),
environment: {},
containerSize: CGSize(width: width + bannerInset * 2.0, height: apparentBackgroundHeight + bannerInset)
)
if let backgroundCoverView = self.backgroundCover.view as? PeerInfoCoverComponent.View {
if backgroundCoverView.superview == nil {
self.backgroundBannerView.addSubview(backgroundCoverView)
}
if additive {
transition.updateFrameAdditive(view: backgroundCoverView, frame: CGRect(origin: CGPoint(x: -3.0, y: bannerFrame.height - backgroundCoverSize.height - bannerInset), size: backgroundCoverSize))
} else {
transition.updateFrame(view: backgroundCoverView, frame: CGRect(origin: CGPoint(x: 0.0, y: bannerFrame.height - backgroundCoverSize.height - bannerInset), size: backgroundCoverSize))
}
if backgroundCoverAnimateIn {
if !self.isAvatarExpanded {
backgroundCoverView.willAnimateIn()
Queue.mainQueue().after(0.2) {
backgroundCoverView.animateIn()
}
Queue.mainQueue().after(0.44) {
self.invokeDisplayGiftInfo()
}
} else {
Queue.mainQueue().after(0.44) {
self.invokeDisplayGiftInfo()
}
}
}
}
if additive {
transition.updateFrameAdditive(node: self.backgroundNode, frame: backgroundFrame)
self.backgroundNode.update(size: self.backgroundNode.bounds.size, transition: transition)
transition.updateFrameAdditive(node: self.expandedBackgroundNode, frame: backgroundFrame)
self.expandedBackgroundNode.update(size: self.expandedBackgroundNode.bounds.size, transition: transition)
transition.updateFrameAdditive(node: self.separatorNode, frame: separatorFrame)
} else {
transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame)
self.backgroundNode.update(size: self.backgroundNode.bounds.size, transition: transition)
transition.updateFrame(node: self.expandedBackgroundNode, frame: backgroundFrame)
self.expandedBackgroundNode.update(size: self.expandedBackgroundNode.bounds.size, transition: transition)
transition.updateFrame(node: self.separatorNode, frame: separatorFrame)
}
if !state.isEditing {
if !isSettings && !isMyProfile {
if self.isAvatarExpanded {
resolvedHeight -= 21.0
} else {
resolvedHeight += 79.0
if !actionButtonKeys.isEmpty {
resolvedHeight += 64.0
}
}
} else {
if self.isAvatarExpanded {
resolvedHeight -= 21.0
}
}
}
if isFirstTime {
self.updateAvatarMask(transition: .immediate)
}
return resolvedHeight
}
private func buttonPressed(_ buttonNode: PeerInfoHeaderButtonNode, gesture: ContextGesture?) {
self.performButtonAction?(buttonNode.key, gesture)
}
private func actionButtonPressed(_ buttonNode: PeerInfoHeaderActionButtonNode, gesture: ContextGesture?) {
self.performButtonAction?(buttonNode.key, gesture)
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
guard let result = super.hitTest(point, with: event) else {
return nil
}
if !self.backgroundNode.frame.contains(point) {
return nil
}
if let customNavigationContentNode = self.customNavigationContentNode {
if let result = customNavigationContentNode.view.hitTest(self.view.convert(point, to: customNavigationContentNode.view), with: event) {
return result
}
return self.view
}
let setByFrame = self.avatarListNode.listContainerNode.setByYouNode.view.convert(self.avatarListNode.listContainerNode.setByYouNode.bounds, to: self.view).insetBy(dx: -44.0, dy: 0.0)
if self.avatarListNode.listContainerNode.setByYouNode.alpha > 0.0, setByFrame.contains(point) {
return self.avatarListNode.listContainerNode.setByYouNode.view
}
if !(self.state?.isEditing ?? false) {
switch self.currentCredibilityIcon {
case .premium:
let iconFrame = self.titleCredibilityIconView.convert(self.titleCredibilityIconView.bounds, to: self.view)
let expandedIconFrame = self.titleExpandedCredibilityIconView.convert(self.titleExpandedCredibilityIconView.bounds, to: self.view)
if expandedIconFrame.contains(point) && self.isAvatarExpanded {
return self.titleExpandedCredibilityIconView.hitTest(self.view.convert(point, to: self.titleExpandedCredibilityIconView), with: event)
} else if iconFrame.contains(point) {
return self.titleCredibilityIconView.hitTest(self.view.convert(point, to: self.titleCredibilityIconView), with: event)
}
default:
break
}
switch self.currentStatusIcon {
case .emojiStatus:
let iconFrame = self.titleStatusIconView.convert(self.titleStatusIconView.bounds, to: self.view)
let expandedIconFrame = self.titleExpandedStatusIconView.convert(self.titleExpandedStatusIconView.bounds, to: self.view)
if expandedIconFrame.contains(point) && self.isAvatarExpanded {
return self.titleExpandedStatusIconView.hitTest(self.view.convert(point, to: self.titleExpandedStatusIconView), with: event)
} else if iconFrame.contains(point) {
return self.titleStatusIconView.hitTest(self.view.convert(point, to: self.titleStatusIconView), with: event)
}
default:
break
}
}
if let subtitleBackgroundButton = self.subtitleBackgroundButton, subtitleBackgroundButton.view.convert(subtitleBackgroundButton.bounds, to: self.view).contains(point) {
if let result = subtitleBackgroundButton.view.hitTest(self.view.convert(point, to: subtitleBackgroundButton.view), with: event) {
return result
}
}
if let subtitleBadgeView = self.subtitleBadgeView, let result = subtitleBadgeView.hitTest(self.view.convert(point, to: subtitleBadgeView), with: event) {
return result
}
if result.isDescendant(of: self.navigationButtonContainer.view) {
return result
}
if self.isSettings {
if self.subtitleNodeRawContainer.bounds.contains(self.view.convert(point, to: self.subtitleNodeRawContainer.view)) {
return self.subtitleNodeRawContainer.view
}
}
if let result = self.buttonsContainerNode.view.hitTest(self.view.convert(point, to: self.buttonsContainerNode.view), with: event) {
return result
}
if result == self.view || result == self.regularContentNode.view || result == self.editingContentNode.view {
return nil
}
return result
}
func updateIsAvatarExpanded(_ isAvatarExpanded: Bool, transition: ContainedViewLayoutTransition) {
if self.isAvatarExpanded != isAvatarExpanded {
self.isAvatarExpanded = isAvatarExpanded
if isAvatarExpanded {
self.avatarListNode.listContainerNode.selectFirstItem()
}
if case .animated = transition, !isAvatarExpanded {
self.avatarListNode.animateAvatarCollapse(transition: transition)
}
self.updateAvatarMask(transition: transition)
}
}
private func updateAvatarMask(transition: ContainedViewLayoutTransition) {
guard let (width, statusBarHeight, deviceMetrics) = self.validLayout, deviceMetrics.hasDynamicIsland && statusBarHeight > 0.0 else {
return
}
let maskScale: CGFloat = isAvatarExpanded ? width / 100.0 : 1.0
transition.updateTransformScale(layer: self.avatarListNode.maskNode.layer, scale: maskScale)
transition.updateTransformScale(layer: self.avatarListNode.bottomCoverNode.layer, scale: maskScale)
transition.updateTransformScale(layer: self.avatarListNode.topCoverNode.layer, scale: maskScale)
let maskAnchorPoint = CGPoint(x: 0.5, y: self.isAvatarExpanded ? 0.37 : 0.5)
transition.updateAnchorPoint(layer: self.avatarListNode.maskNode.layer, anchorPoint: maskAnchorPoint)
}
}