2023-11-23 19:29:49 +04:00

1889 lines
111 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
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 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 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>()
let buttonsContainerNode: SparseNode
let regularContentNode: PeerInfoHeaderRegularContentNode
let editingContentNode: PeerInfoHeaderEditingContentNode
let avatarOverlayNode: PeerInfoEditingAvatarOverlayNode
let titleNodeContainer: ASDisplayNode
let titleNodeRawContainer: ASDisplayNode
let titleNode: MultiScaleTextNode
let titleCredibilityIconView: ComponentHostView<Empty>
var credibilityIconSize: CGSize?
let titleExpandedCredibilityIconView: ComponentHostView<Empty>
var titleExpandedCredibilityIconSize: CGSize?
let subtitleNodeContainer: ASDisplayNode
let subtitleNodeRawContainer: ASDisplayNode
let subtitleNode: MultiScaleTextNode
var subtitleBackgroundNode: ASDisplayNode?
var subtitleBackgroundButton: HighlightTrackingButtonNode?
var subtitleArrowNode: ASImageNode?
let panelSubtitleNode: MultiScaleTextNode
let nextPanelSubtitleNode: 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
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 navigateToForum: (() -> Void)?
var navigationTransition: PeerInfoHeaderNavigationTransition?
var backgroundAlpha: CGFloat = 1.0
var updateHeaderAlpha: ((CGFloat, ContainedViewLayoutTransition) -> Void)?
let animationCache: AnimationCache
let animationRenderer: MultiAnimationRenderer
var emojiStatusPackDisposable = MetaDisposable()
var emojiStatusFileAndPackTitle = Promise<(TelegramMediaFile, LoadedStickerPack)?>()
private var validLayout: (width: CGFloat, deviceMetrics: DeviceMetrics)?
init(context: AccountContext, controller: PeerInfoScreenImpl, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, isMediaOnly: Bool, isSettings: Bool, forumTopicThreadId: Int64?, chatLocation: ChatLocation) {
self.context = context
self.controller = controller
self.isAvatarExpanded = avatarInitiallyExpanded
self.isOpenedFromChat = isOpenedFromChat
self.isSettings = isSettings
self.videoCallsEnabled = true
self.forumTopicThreadId = forumTopicThreadId
self.chatLocation = chatLocation
self.avatarClippingNode = SparseNode()
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.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.nextPanelSubtitleNode = MultiScaleTextNode(stateKeys: [TitleNodeStateRegular, TitleNodeStateExpanded])
self.nextPanelSubtitleNode.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.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.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.subtitleNodeContainer.addSubnode(self.nextPanelSubtitleNode)
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.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 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 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 {
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, 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 })
let credibilityIcon: CredibilityIcon
if let peer = peer {
if peer.isFake {
credibilityIcon = .fake
} else if peer.isScam {
credibilityIcon = .scam
} else if let user = peer as? TelegramUser, let emojiStatus = user.emojiStatus, !premiumConfiguration.isPremiumDisabled {
credibilityIcon = .emojiStatus(emojiStatus)
} else if peer.isVerified {
credibilityIcon = .verified
} else if peer.isPremium && !premiumConfiguration.isPremiumDisabled && (peer.id != self.context.account.peerId || self.isSettings) {
credibilityIcon = .premium
} else {
credibilityIcon = .none
}
} else {
credibilityIcon = .none
}
var isForum = false
if let channel = peer as? TelegramChannel, channel.flags.contains(.isForum) {
isForum = true
}
self.regularContentNode.alpha = state.isEditing ? 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, 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: 1.0, alpha: 0.3)
let regularHeaderButtonBackgroundColor: UIColor
let collapsedHeaderButtonBackgroundColor: UIColor = .clear
let expandedAvatarHeaderButtonBackgroundColor: UIColor = UIColor(white: 1.0, alpha: 0.3)
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 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)
} else {
regularNavigationContentsSecondaryColor = presentationData.theme.list.itemSecondaryTextColor
regularContentButtonBackgroundColor = presentationData.theme.list.itemBlocksBackgroundColor
regularHeaderButtonBackgroundColor = .clear
}
let collapsedHeaderNavigationContentsSecondaryColor = presentationData.theme.list.itemSecondaryTextColor
let expandedAvatarNavigationContentsSecondaryColor: UIColor = .white
let navigationContentsAccentColor: UIColor
let navigationContentsPrimaryColor: UIColor
let navigationContentsSecondaryColor: UIColor
let contentButtonBackgroundColor: UIColor
let contentButtonForegroundColor: UIColor
let headerButtonBackgroundColor: UIColor
var effectiveSeparatorAlpha: CGFloat
if let navigationTransition = self.navigationTransition {
transitionSourceHeight = navigationTransition.sourceNavigationBar.backgroundNode.bounds.height
transitionFraction = navigationTransition.fraction
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 && !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 {
navigationContentsAccentColor = expandedAvatarNavigationContentsAccentColor
navigationContentsPrimaryColor = expandedAvatarNavigationContentsPrimaryColor
navigationContentsSecondaryColor = expandedAvatarNavigationContentsSecondaryColor
contentButtonBackgroundColor = expandedAvatarContentButtonBackgroundColor
contentButtonForegroundColor = expandedAvatarContentButtonForegroundColor
headerButtonBackgroundColor = expandedAvatarHeaderButtonBackgroundColor
} else {
navigationContentsAccentColor = regularNavigationContentsAccentColor
navigationContentsPrimaryColor = regularNavigationContentsPrimaryColor
navigationContentsSecondaryColor = regularNavigationContentsSecondaryColor
contentButtonBackgroundColor = regularContentButtonBackgroundColor
contentButtonForegroundColor = regularContentButtonForegroundColor
headerButtonBackgroundColor = regularHeaderButtonBackgroundColor
}
if self.isAvatarExpanded, case .animated = transition, transitionFraction == 1.0 {
self.avatarListNode.animateAvatarCollapse(transition: transition)
}
self.avatarClippingNode.clipsToBounds = false
} else {
let contentOffset = max(0.0, contentOffset - 140.0)
let backgroundTransitionFraction: CGFloat = max(0.0, min(1.0, contentOffset / 30.0))
self.expandedBackgroundNode.updateColor(color: presentationData.theme.rootController.navigationBar.opaqueBackgroundColor.mixedWith(headerBackgroundColor, alpha: 1.0 - backgroundTransitionFraction), forceKeepBlur: true, transition: transition)
transition.updateAlpha(layer: self.backgroundBannerView.layer, alpha: state.isEditing ? 0.0 : (1.0 - backgroundTransitionFraction))
if state.isEditing {
navigationContentsAccentColor = collapsedHeaderNavigationContentsAccentColor
navigationContentsPrimaryColor = collapsedHeaderNavigationContentsPrimaryColor
navigationContentsSecondaryColor = collapsedHeaderNavigationContentsSecondaryColor
contentButtonBackgroundColor = collapsedHeaderContentButtonBackgroundColor
contentButtonForegroundColor = collapsedHeaderContentButtonForegroundColor
headerButtonBackgroundColor = collapsedHeaderButtonBackgroundColor
} else if self.isAvatarExpanded {
navigationContentsAccentColor = expandedAvatarNavigationContentsAccentColor
navigationContentsPrimaryColor = expandedAvatarNavigationContentsPrimaryColor
navigationContentsSecondaryColor = expandedAvatarNavigationContentsSecondaryColor
contentButtonBackgroundColor = expandedAvatarContentButtonBackgroundColor
contentButtonForegroundColor = expandedAvatarContentButtonForegroundColor
headerButtonBackgroundColor = expandedAvatarHeaderButtonBackgroundColor
} else {
navigationContentsAccentColor = regularNavigationContentsAccentColor.mixedWith(collapsedHeaderNavigationContentsAccentColor, alpha: backgroundTransitionFraction)
navigationContentsPrimaryColor = regularNavigationContentsPrimaryColor.mixedWith(collapsedHeaderNavigationContentsPrimaryColor, alpha: backgroundTransitionFraction)
navigationContentsSecondaryColor = regularNavigationContentsSecondaryColor.mixedWith(collapsedHeaderNavigationContentsSecondaryColor, alpha: backgroundTransitionFraction)
contentButtonBackgroundColor = regularContentButtonBackgroundColor.mixedWith(collapsedHeaderContentButtonBackgroundColor, alpha: backgroundTransitionFraction)
contentButtonForegroundColor = regularContentButtonForegroundColor.mixedWith(collapsedHeaderContentButtonForegroundColor, alpha: backgroundTransitionFraction)
headerButtonBackgroundColor = regularHeaderButtonBackgroundColor.mixedWith(collapsedHeaderButtonBackgroundColor, alpha: backgroundTransitionFraction)
}
effectiveSeparatorAlpha = backgroundTransitionFraction
self.avatarClippingNode.clipsToBounds = true
}
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 animateStatusIcon = !self.titleCredibilityIconView.bounds.isEmpty
let iconSize = self.titleCredibilityIconView.update(
transition: Transition(transition),
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
}
strongSelf.displayPremiumIntro?(strongSelf.titleCredibilityIconView, 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: 34.0, height: 34.0)
)
let expandedIconSize = self.titleExpandedCredibilityIconView.update(
transition: Transition(transition),
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
}
strongSelf.displayPremiumIntro?(strongSelf.titleExpandedCredibilityIconView, currentEmojiStatus, strongSelf.emojiStatusFileAndPackTitle.get(), true)
}
)),
environment: {},
containerSize: CGSize(width: 34.0, height: 34.0)
)
self.credibilityIconSize = iconSize
self.titleExpandedCredibilityIconSize = expandedIconSize
}
self.navigationButtonContainer.updateContentsColor(backgroundContentColor: headerButtonBackgroundColor, contentsColor: navigationContentsAccentColor, transition: transition)
self.titleNode.updateTintColor(color: navigationContentsPrimaryColor, transition: transition)
self.subtitleNode.updateTintColor(color: navigationContentsSecondaryColor, transition: transition)
self.panelSubtitleNode.updateTintColor(color: navigationContentsSecondaryColor, transition: transition)
self.nextPanelSubtitleNode.updateTintColor(color: navigationContentsSecondaryColor, transition: transition)
if let navigationBar = self.controller?.navigationBar {
if let mainContentNode = navigationBar.backButtonNode.mainContentNode {
transition.updateTintColor(layer: mainContentNode.layer, color: navigationContentsAccentColor)
}
transition.updateTintColor(layer: navigationBar.backButtonArrow.layer, color: navigationContentsAccentColor)
if let mainContentNode = navigationBar.leftButtonNode.mainContentNode {
transition.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 = state.isEditing && self.isSettings ? min(1.0, contentOffset / (navigationHeight * 0.5)) : 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
let expandedAvatarListHeight = min(width, containerHeight - expandedAvatarControlsHeight)
let expandedAvatarListSize = CGSize(width: width, height: expandedAvatarListHeight)
let actionButtonKeys: [PeerInfoHeaderButtonKey] = self.isSettings ? [] : peerInfoHeaderActionButtons(peer: peer, isSecretChat: isSecretChat, isContact: isContact)
let buttonKeys: [PeerInfoHeaderButtonKey] = self.isSettings ? [] : 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)?
var nextPanelSubtitleString: (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
}
if let peer = peer {
var title: String
if peer.id == self.context.account.peerId && !self.isSettings {
title = presentationData.strings.Conversation_SavedMessages
} else if peer.id == self.context.account.peerId && !self.isSettings {
title = presentationData.strings.DialogList_Replies
} 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)
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)
usernameString = ("", MultiScaleTextState.Attributes(font: Font.regular(16.0), color: .white))
} 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)
usernameString = ("", MultiScaleTextState.Attributes(font: Font.regular(16.0), color: .white))
subtitleIsButton = true
let (maybePanelStatusData, maybeNextPanelStatusData, _) = 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))
}
if let nextPanelStatusData = maybeNextPanelStatusData {
nextPanelSubtitleString = (nextPanelStatusData.text, MultiScaleTextState.Attributes(font: Font.regular(17.0), color: .white))
}
} 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)
usernameString = ("", MultiScaleTextState.Attributes(font: Font.regular(16.0), color: .white))
let (maybePanelStatusData, maybeNextPanelStatusData, _) = 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))
}
if let nextPanelStatusData = maybeNextPanelStatusData {
nextPanelSubtitleString = (nextPanelStatusData.text, MultiScaleTextState.Attributes(font: Font.regular(17.0), color: .white))
}
} else {
subtitleStringText = " "
subtitleAttributes = MultiScaleTextState.Attributes(font: Font.regular(16.0), color: .white)
smallSubtitleAttributes = MultiScaleTextState.Attributes(font: Font.regular(16.0), color: .white)
usernameString = ("", MultiScaleTextState.Attributes(font: Font.regular(16.0), color: .white))
}
} else {
titleStringText = " "
titleAttributes = MultiScaleTextState.Attributes(font: Font.regular(24.0), color: .white)
smallTitleAttributes = MultiScaleTextState.Attributes(font: Font.regular(24.0), color: .white)
subtitleStringText = " "
subtitleAttributes = MultiScaleTextState.Attributes(font: Font.regular(16.0), color: .white)
smallSubtitleAttributes = MultiScaleTextState.Attributes(font: Font.regular(16.0), color: .white)
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 nextPanelSubtitleNodeLayout = self.nextPanelSubtitleNode.updateLayout(text: nextPanelSubtitleString?.text ?? subtitleStringText, states: [
TitleNodeStateRegular: MultiScaleTextState(attributes: nextPanelSubtitleString?.attributes ?? subtitleAttributes, constrainedSize: titleConstrainedSize),
TitleNodeStateExpanded: MultiScaleTextState(attributes: nextPanelSubtitleString?.attributes ?? subtitleAttributes, constrainedSize: titleConstrainedSize)
], mainState: TitleNodeStateRegular)
if let _ = nextPanelSubtitleString {
self.nextPanelSubtitleNode.isHidden = false
}
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
let _ = panelSubtitleNodeLayout[TitleNodeStateRegular]!.size
let _ = nextPanelSubtitleNodeLayout[TitleNodeStateRegular]!.size
let usernameSize = usernameNodeLayout[TitleNodeStateRegular]!.size
var titleHorizontalOffset: CGFloat = 0.0
if let credibilityIconSize = self.credibilityIconSize, let titleExpandedCredibilityIconSize = self.titleExpandedCredibilityIconSize {
titleHorizontalOffset = -(credibilityIconSize.width + 4.0) / 2.0
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: titleSize.width + 4.0 + collapsedTransitionOffset, y: floor((titleSize.height - credibilityIconSize.height) / 2.0)), size: credibilityIconSize))
transition.updateFrame(view: self.titleExpandedCredibilityIconView, frame: CGRect(origin: CGPoint(x: titleExpandedSize.width + 4.0, y: floor((titleExpandedSize.height - titleExpandedCredibilityIconSize.height) / 2.0) + 1.0), size: titleExpandedCredibilityIconSize))
}
var titleFrame: CGRect
var subtitleFrame: CGRect
let usernameFrame: CGRect
let usernameSpacing: CGFloat = 4.0
let expandedTitleScale: CGFloat = 0.8
transition.updateFrame(node: self.avatarListNode.listContainerNode.bottomShadowNode, frame: CGRect(origin: CGPoint(x: 0.0, y: expandedAvatarHeight - 70.0), size: CGSize(width: width, height: 70.0)))
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 {
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)
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)
let totalSubtitleWidth = subtitleSize.width + usernameSpacing + usernameSize.width
if usernameSize.width == 0.0 {
subtitleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((width - subtitleSize.width) / 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 singleTitleLockOffset: CGFloat = (peer?.id == self.context.account.peerId || subtitleSize.height.isZero) ? 8.0 : 0.0
let titleLockOffset: CGFloat = 7.0 + singleTitleLockOffset
let titleMaxLockOffset: CGFloat = 7.0
var titleCollapseOffset = titleFrame.midY - statusBarHeight - titleLockOffset
if case .regular = metrics.widthClass, !isSettings {
titleCollapseOffset -= 7.0
}
let titleOffset = -min(titleCollapseOffset, contentOffset)
let titleCollapseFraction = max(0.0, min(1.0, contentOffset / titleCollapseOffset))
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 {
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
}
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 {
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.nextPanelSubtitleNode.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
let controlsClippingFrame: CGRect
if self.isAvatarExpanded {
let expandedAvatarCenter = CGPoint(x: expandedAvatarListSize.width / 2.0, y: expandedAvatarListSize.height / 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())
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)
controlsClippingFrame = apparentAvatarFrame
}
let avatarClipOffset: CGFloat = !self.isAvatarExpanded && deviceMetrics.hasDynamicIsland && self.avatarClippingNode.clipsToBounds && !isLandscape ? 48.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)
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.height / 2.0), size: avatarListContainerSize)
} else {
avatarListContainerFrame = CGRect(origin: CGPoint(x: -expandedAvatarListSize.width / 2.0, y: -expandedAvatarListSize.height / 2.0), size: expandedAvatarListSize)
}
avatarListContainerScale = 1.0 + max(0.0, -contentOffset / avatarListContainerFrame.height)
} else {
avatarListContainerFrame = CGRect(origin: CGPoint(x: -apparentAvatarFrame.width / 2.0, y: -apparentAvatarFrame.height / 2.0), size: apparentAvatarFrame.size)
avatarListContainerScale = avatarScale
}
transition.updateFrame(node: self.avatarListNode.listContainerNode, frame: avatarListContainerFrame)
let innerScale = avatarListContainerFrame.height / expandedAvatarListSize.height
let innerDeltaX = (avatarListContainerFrame.width - expandedAvatarListSize.width) / 2.0
let innerDeltaY = (avatarListContainerFrame.height - expandedAvatarListSize.height) / 2.0
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()))
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 && 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
}
var panelWithAvatarHeight: CGFloat = 35.0 + avatarSize
if threadData != nil {
panelWithAvatarHeight += 10.0
}
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 {
expandablePart += 20.0
} else {
if peer?.id == self.context.account.peerId {
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: self.isAvatarExpanded ? 0.0 : titleHorizontalOffset * titleScale, 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), size: CGSize()))
transition.updateFrame(node: self.nextPanelSubtitleNode, frame: CGRect(origin: CGPoint(x: 0.0, y: panelSubtitleOffset), 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)
} else {
let titleScale: CGFloat
let subtitleScale: CGFloat
var subtitleOffset: CGFloat = 0.0
if self.isAvatarExpanded {
titleScale = expandedTitleScale
subtitleScale = 1.0
} else {
titleScale = (1.0 - titleCollapseFraction) * 1.0 + titleCollapseFraction * titleMinScale
subtitleScale = (1.0 - titleCollapseFraction) * 1.0 + titleCollapseFraction * subtitleMinScale
subtitleOffset = titleCollapseFraction * -2.0
}
let rawTitleFrame = titleFrame.offsetBy(dx: self.isAvatarExpanded ? 0.0 : 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), size: CGSize()))
transition.updateFrame(node: self.nextPanelSubtitleNode, frame: CGRect(origin: CGPoint(x: 0.0, y: panelSubtitleOffset), 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)
}
}
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
}
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)
}
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)
}
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, transition: buttonTransition)
if wasAdded {
buttonNode.alpha = 0.0
}
transition.updateAlpha(node: buttonNode, alpha: 1.0)
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(node: buttonNode, alpha: 0.0) { [weak buttonNode] _ in
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)))
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 buttonsTransitionFraction: CGFloat = 1.0 - max(0.0, min(1.0, buttonsTransitionDistance / buttonsTransitionDistanceNorm))
transition.updateAlpha(node: self.buttonsContainerNode, alpha: buttonsTransitionFraction * backgroundTransitionFraction)
let bannerFrame = CGRect(origin: CGPoint(x: 0.0, y: -2000.0 + apparentBackgroundHeight), size: CGSize(width: width, height: 2000.0))
transition.updateFrameAdditive(view: self.backgroundBannerView, frame: bannerFrame)
let backgroundCoverSize = self.backgroundCover.update(
transition: Transition(transition),
component: AnyComponent(PeerInfoCoverComponent(
context: self.context,
peer: peer.flatMap(EnginePeer.init),
isDark: presentationData.theme.overallDarkAppearance,
avatarCenter: apparentAvatarFrame.center,
avatarScale: avatarScale,
avatarTransitionFraction: max(0.0, min(1.0, titleCollapseFraction + transitionFraction * 2.0)),
patternTransitionFraction: buttonsTransitionFraction * backgroundTransitionFraction
)),
environment: {},
containerSize: CGSize(width: width, height: apparentBackgroundHeight)
)
if let backgroundCoverView = self.backgroundCover.view {
if backgroundCoverView.superview == nil {
self.backgroundBannerView.addSubview(backgroundCoverView)
}
transition.updateFrameAdditive(view: backgroundCoverView, frame: CGRect(origin: CGPoint(x: 0.0, y: bannerFrame.height - backgroundCoverSize.height), size: backgroundCoverSize))
}
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 {
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
}
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, .emojiStatus:
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
}
}
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 result.isDescendant(of: self.navigationButtonContainer.view) {
return result
}
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, deviceMetrics) = self.validLayout, deviceMetrics.hasDynamicIsland 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: isAvatarExpanded ? 0.37 : 0.5)
transition.updateAnchorPoint(layer: self.avatarListNode.maskNode.layer, anchorPoint: maskAnchorPoint)
}
}