diff --git a/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenContactInfoItem.swift b/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenContactInfoItem.swift new file mode 100644 index 0000000000..01980ccd1f --- /dev/null +++ b/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenContactInfoItem.swift @@ -0,0 +1,401 @@ +import AsyncDisplayKit +import Display +import TelegramPresentationData +import AccountContext +import TextFormat +import UIKit +import AppBundle +import TelegramStringFormatting +import ContextUI + +final class PeerInfoScreenContactInfoItem: PeerInfoScreenItem { + let id: AnyHashable + let username: String + let phoneNumber: String + let additionalText: String? + let usernameAction: ((ASDisplayNode) -> Void)? + let usernameLongTapAction: ((ASDisplayNode) -> Void)? + let phoneAction: ((ASDisplayNode) -> Void)? + let phoneLongTapAction: ((ASDisplayNode) -> Void)? + let linkItemAction: ((TextLinkItemActionType, TextLinkItem, ASDisplayNode, CGRect?) -> Void)? + let contextAction: ((ASDisplayNode, ContextGesture?, CGPoint?) -> Void)? + let requestLayout: () -> Void + + init( + id: AnyHashable, + username: String, + phoneNumber: String, + additionalText: String? = nil, + usernameAction: ((ASDisplayNode) -> Void)?, + usernameLongTapAction: ((ASDisplayNode) -> Void)?, + phoneAction: ((ASDisplayNode) -> Void)?, + phoneLongTapAction: ((ASDisplayNode) -> Void)?, + linkItemAction: ((TextLinkItemActionType, TextLinkItem, ASDisplayNode, CGRect?) -> Void)? = nil, + contextAction: ((ASDisplayNode, ContextGesture?, CGPoint?) -> Void)? = nil, + requestLayout: @escaping () -> Void + ) { + self.id = id + self.username = username + self.phoneNumber = phoneNumber + self.additionalText = additionalText + self.usernameAction = usernameAction + self.usernameLongTapAction = usernameLongTapAction + self.phoneAction = phoneAction + self.phoneLongTapAction = phoneLongTapAction + self.linkItemAction = linkItemAction + self.contextAction = contextAction + self.requestLayout = requestLayout + } + + func node() -> PeerInfoScreenItemNode { + return PeerInfoScreenContactInfoItemNode() + } +} + +private final class PeerInfoScreenContactInfoItemNode: PeerInfoScreenItemNode { + private let containerNode: ContextControllerSourceNode + private let contextSourceNode: ContextExtractedContentContainingNode + + private let extractedBackgroundImageNode: ASImageNode + + private var extractedRect: CGRect? + private var nonExtractedRect: CGRect? + + private let selectionNode: PeerInfoScreenSelectableBackgroundNode + private let maskNode: ASImageNode + private let usernameNode: ImmediateTextNode + private let phoneNumberNode: ImmediateTextNode + private let additionalTextNode: ImmediateTextNode + private let measureTextNode: ImmediateTextNode + private let bottomSeparatorNode: ASDisplayNode + + private var linkHighlightingNode: LinkHighlightingNode? + + private let activateArea: AccessibilityAreaNode + + private var item: PeerInfoScreenContactInfoItem? + private var theme: PresentationTheme? + + override init() { + var bringToFrontForHighlightImpl: (() -> Void)? + + self.contextSourceNode = ContextExtractedContentContainingNode() + self.containerNode = ContextControllerSourceNode() + + self.extractedBackgroundImageNode = ASImageNode() + self.extractedBackgroundImageNode.displaysAsynchronously = false + self.extractedBackgroundImageNode.alpha = 0.0 + + self.selectionNode = PeerInfoScreenSelectableBackgroundNode(bringToFrontForHighlight: { bringToFrontForHighlightImpl?() }) + self.selectionNode.isUserInteractionEnabled = false + + self.maskNode = ASImageNode() + self.maskNode.isUserInteractionEnabled = false + + self.usernameNode = ImmediateTextNode() + self.usernameNode.displaysAsynchronously = false + self.usernameNode.isUserInteractionEnabled = false + + self.phoneNumberNode = ImmediateTextNode() + self.phoneNumberNode.displaysAsynchronously = false + self.phoneNumberNode.isUserInteractionEnabled = false + + self.additionalTextNode = ImmediateTextNode() + self.additionalTextNode.displaysAsynchronously = false + self.additionalTextNode.isUserInteractionEnabled = false + + self.measureTextNode = ImmediateTextNode() + self.measureTextNode.displaysAsynchronously = false + self.measureTextNode.isUserInteractionEnabled = false + + self.bottomSeparatorNode = ASDisplayNode() + self.bottomSeparatorNode.isLayerBacked = true + + self.activateArea = AccessibilityAreaNode() + + super.init() + + bringToFrontForHighlightImpl = { [weak self] in + self?.bringToFrontForHighlight?() + } + + self.addSubnode(self.bottomSeparatorNode) + self.addSubnode(self.selectionNode) + + self.containerNode.addSubnode(self.contextSourceNode) + self.containerNode.targetNodeForActivationProgress = self.contextSourceNode.contentNode + self.addSubnode(self.containerNode) + + self.addSubnode(self.maskNode) + + self.contextSourceNode.contentNode.addSubnode(self.extractedBackgroundImageNode) + + self.contextSourceNode.contentNode.addSubnode(self.usernameNode) + self.contextSourceNode.contentNode.addSubnode(self.phoneNumberNode) + self.contextSourceNode.contentNode.addSubnode(self.additionalTextNode) + + + self.addSubnode(self.activateArea) + + self.containerNode.activated = { [weak self] gesture, _ in + guard let strongSelf = self, let item = strongSelf.item, let contextAction = item.contextAction else { + gesture.cancel() + return + } + contextAction(strongSelf.contextSourceNode, gesture, nil) + } + + self.contextSourceNode.willUpdateIsExtractedToContextPreview = { [weak self] isExtracted, transition in + guard let strongSelf = self, let theme = strongSelf.theme else { + return + } + + if isExtracted { + strongSelf.extractedBackgroundImageNode.image = generateStretchableFilledCircleImage(diameter: 28.0, color: theme.list.plainBackgroundColor) + } + + if let extractedRect = strongSelf.extractedRect, let nonExtractedRect = strongSelf.nonExtractedRect { + let rect = isExtracted ? extractedRect : nonExtractedRect + transition.updateFrame(node: strongSelf.extractedBackgroundImageNode, frame: rect) + } + + transition.updateAlpha(node: strongSelf.extractedBackgroundImageNode, alpha: isExtracted ? 1.0 : 0.0, completion: { _ in + if !isExtracted { + self?.extractedBackgroundImageNode.image = nil + } + }) + } + } + + override func didLoad() { + super.didLoad() + + let recognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapLongTapOrDoubleTapGesture(_:))) + recognizer.tapActionAtPoint = { [weak self] point in + guard let strongSelf = self else { + return .keepWithSingleTap + } + if let _ = strongSelf.linkItemAtPoint(point) { + return .waitForSingleTap + } + return .waitForSingleTap + } + recognizer.highlight = { [weak self] point in + guard let strongSelf = self else { + return + } + strongSelf.updateTouchesAtPoint(point) + } + self.view.addGestureRecognizer(recognizer) + } + + @objc private func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) { + switch recognizer.state { + case .ended: + if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation { + switch gesture { + case .tap, .longTap: + if let item = self.item { + if let linkItem = self.linkItemAtPoint(location) { + item.linkItemAction?(gesture == .tap ? .tap : .longTap, linkItem, self.linkHighlightingNode ?? self, self.linkHighlightingNode?.rects.first) + } else if case .longTap = gesture { + if self.usernameNode.frame.insetBy(dx: -10.0, dy: -10.0).contains(location) { + item.usernameLongTapAction?(self.usernameNode) + } else if self.phoneNumberNode.frame.insetBy(dx: -10.0, dy: -10.0).contains(location) { + item.phoneLongTapAction?(self.phoneNumberNode) + } + } else if case .tap = gesture { + if self.usernameNode.frame.insetBy(dx: -10.0, dy: -10.0).contains(location) { + item.usernameAction?(self.contextSourceNode) + } else if self.phoneNumberNode.frame.insetBy(dx: -10.0, dy: -10.0).contains(location) { + item.phoneAction?(self.contextSourceNode) + } + } + } + default: + break + } + } + default: + break + } + } + + private func linkItemAtPoint(_ point: CGPoint) -> TextLinkItem? { + let additionalTextNodeFrame = self.additionalTextNode.frame + if let (_, attributes) = self.additionalTextNode.attributesAtPoint(CGPoint(x: point.x - additionalTextNodeFrame.minX, y: point.y - additionalTextNodeFrame.minY)) { + if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String { + return .url(url: url, concealed: false) + } else if let peerName = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerTextMention)] as? String { + return .mention(peerName) + } else if let hashtag = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Hashtag)] as? TelegramHashtag { + return .hashtag(hashtag.peerName, hashtag.hashtag) + } else { + return nil + } + } + return nil + } + + override func update(width: CGFloat, safeInsets: UIEdgeInsets, presentationData: PresentationData, item: PeerInfoScreenItem, topItem: PeerInfoScreenItem?, bottomItem: PeerInfoScreenItem?, hasCorners: Bool, transition: ContainedViewLayoutTransition) -> CGFloat { + guard let item = item as? PeerInfoScreenContactInfoItem else { + return 10.0 + } + + self.item = item + self.theme = presentationData.theme + +// if let action = item.action { +// self.selectionNode.pressed = { [weak self] in +// if let strongSelf = self { +// action(strongSelf.contextSourceNode) +// } +// } +// } else { +// self.selectionNode.pressed = nil +// } + + let sideInset: CGFloat = 16.0 + safeInsets.left + + self.bottomSeparatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor + + self.usernameNode.attributedText = NSAttributedString(string: item.username, font: Font.regular(15.0), textColor: presentationData.theme.list.itemPrimaryTextColor) + + self.phoneNumberNode.maximumNumberOfLines = 1 + self.phoneNumberNode.cutout = nil + self.phoneNumberNode.attributedText = NSAttributedString(string: item.phoneNumber, font: Font.regular(15.0), textColor: presentationData.theme.list.itemAccentColor) + + let fontSize: CGFloat = 15.0 + + let baseFont = Font.regular(fontSize) + let linkFont = baseFont + let boldFont = Font.medium(fontSize) + let italicFont = Font.italic(fontSize) + let boldItalicFont = Font.semiboldItalic(fontSize) + let titleFixedFont = Font.monospace(fontSize) + + if let additionalText = item.additionalText { + let entities = generateTextEntities(additionalText, enabledTypes: [.mention]) + let attributedAdditionalText = stringWithAppliedEntities(additionalText, entities: entities, baseColor: presentationData.theme.list.itemPrimaryTextColor, linkColor: presentationData.theme.list.itemAccentColor, baseFont: baseFont, linkFont: linkFont, boldFont: boldFont, italicFont: italicFont, boldItalicFont: boldItalicFont, fixedFont: titleFixedFont, blockQuoteFont: baseFont, underlineLinks: false, message: nil) + + self.additionalTextNode.maximumNumberOfLines = 10 + self.additionalTextNode.attributedText = attributedAdditionalText + } else { + self.additionalTextNode.attributedText = nil + } + + + let usernameSize = self.usernameNode.updateLayout(CGSize(width: width - sideInset * 2.0, height: .greatestFiniteMagnitude)) + let phoneSize = self.phoneNumberNode.updateLayout(CGSize(width: width - sideInset * 2.0, height: .greatestFiniteMagnitude)) + + let additionalTextSize = self.additionalTextNode.updateLayout(CGSize(width: width - sideInset * 2.0, height: .greatestFiniteMagnitude)) + + let topOffset = 12.0 + var height = topOffset * 2.0 + let usernameFrame = CGRect(origin: CGPoint(x: sideInset, y: topOffset), size: usernameSize) + let phoneFrame = CGRect(origin: CGPoint(x: usernameSize.width > 0.0 ? width - sideInset - phoneSize.width : sideInset, y: topOffset), size: phoneSize) + height += max(usernameSize.height, phoneSize.height) + + let additionalTextFrame = CGRect(origin: CGPoint(x: sideInset, y: topOffset), size: additionalTextSize) + transition.updateFrame(node: self.usernameNode, frame: usernameFrame) + + transition.updateFrame(node: self.phoneNumberNode, frame: phoneFrame) + + transition.updateFrame(node: self.additionalTextNode, frame: additionalTextFrame) + + if additionalTextSize.height > 0.0 { + height += additionalTextSize.height + 3.0 + } + + let highlightNodeOffset: CGFloat = topItem == nil ? 0.0 : UIScreenPixel + self.selectionNode.update(size: CGSize(width: width, height: height + highlightNodeOffset), theme: presentationData.theme, transition: transition) + transition.updateFrame(node: self.selectionNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -highlightNodeOffset), size: CGSize(width: width, height: height + highlightNodeOffset))) + + transition.updateFrame(node: self.bottomSeparatorNode, frame: CGRect(origin: CGPoint(x: sideInset, y: height - UIScreenPixel), size: CGSize(width: width - sideInset, height: UIScreenPixel))) + transition.updateAlpha(node: self.bottomSeparatorNode, alpha: bottomItem == nil ? 0.0 : 1.0) + + let hasCorners = hasCorners && (topItem == nil || bottomItem == nil) + let hasTopCorners = hasCorners && topItem == nil + let hasBottomCorners = hasCorners && bottomItem == nil + + self.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + self.maskNode.frame = CGRect(origin: CGPoint(x: safeInsets.left, y: 0.0), size: CGSize(width: width - safeInsets.left - safeInsets.right, height: height)) + self.bottomSeparatorNode.isHidden = hasBottomCorners + + self.activateArea.frame = CGRect(origin: CGPoint(), size: CGSize(width: width, height: height)) + self.activateArea.accessibilityLabel = item.username + self.activateArea.accessibilityValue = item.phoneNumber + + let contentSize = CGSize(width: width, height: height) + self.containerNode.frame = CGRect(origin: CGPoint(), size: contentSize) + self.contextSourceNode.frame = CGRect(origin: CGPoint(), size: contentSize) + self.contextSourceNode.contentNode.frame = CGRect(origin: CGPoint(), size: contentSize) + self.containerNode.isGestureEnabled = item.contextAction != nil + + let nonExtractedRect = CGRect(origin: CGPoint(), size: CGSize(width: contentSize.width, height: contentSize.height)) + let extractedRect = nonExtractedRect + self.extractedRect = extractedRect + self.nonExtractedRect = nonExtractedRect + + if self.contextSourceNode.isExtractedToContextPreview { + self.extractedBackgroundImageNode.frame = extractedRect + } else { + self.extractedBackgroundImageNode.frame = nonExtractedRect + } + self.contextSourceNode.contentRect = extractedRect + + return height + } + + private func updateTouchesAtPoint(_ point: CGPoint?) { + guard let _ = self.item, let theme = self.theme else { + return + } + var rects: [CGRect]? + var textNode: ASDisplayNode? + if let point = point { + if self.usernameNode.frame.insetBy(dx: -10.0, dy: -10.0).contains(point) { + textNode = self.usernameNode + rects = [self.usernameNode.bounds] + } else if self.phoneNumberNode.frame.insetBy(dx: -10.0, dy: -10.0).contains(point) { + textNode = self.phoneNumberNode + rects = [self.phoneNumberNode.bounds] + } else if self.additionalTextNode.frame.contains(point) { + let mappedPoint = CGPoint(x: point.x - self.additionalTextNode.frame.minX, y: point.y - self.additionalTextNode.frame.minY) + if mappedPoint.y > 0.0, let (index, attributes) = self.additionalTextNode.attributesAtPoint(mappedPoint) { + let possibleNames: [String] = [ + TelegramTextAttributes.URL, + TelegramTextAttributes.PeerMention, + TelegramTextAttributes.PeerTextMention, + TelegramTextAttributes.BotCommand, + TelegramTextAttributes.Hashtag + ] + for name in possibleNames { + if let _ = attributes[NSAttributedString.Key(rawValue: name)] { + rects = self.additionalTextNode.attributeRects(name: name, at: index) + textNode = self.additionalTextNode + break + } + } + } + } + } + if let rects = rects, let textNode = textNode { + let linkHighlightingNode: LinkHighlightingNode + if let current = self.linkHighlightingNode { + linkHighlightingNode = current + } else { + linkHighlightingNode = LinkHighlightingNode(color: theme.list.itemAccentColor.withAlphaComponent(0.5)) + self.linkHighlightingNode = linkHighlightingNode + self.contextSourceNode.contentNode.insertSubnode(linkHighlightingNode, belowSubnode: textNode) + } + linkHighlightingNode.frame = textNode.frame + linkHighlightingNode.updateRects(rects) + } else if let linkHighlightingNode = self.linkHighlightingNode { + self.linkHighlightingNode = nil + linkHighlightingNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, removeOnCompletion: false, completion: { [weak linkHighlightingNode] _ in + linkHighlightingNode?.removeFromSupernode() + }) + } + } +} diff --git a/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenLabeledValueItem.swift b/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenLabeledValueItem.swift index 9aa643886c..a8a1d3fcf0 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenLabeledValueItem.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenLabeledValueItem.swift @@ -472,9 +472,19 @@ private final class PeerInfoScreenLabeledValueItemNode: PeerInfoScreenItemNode { self.expandButonNode.isHidden = true } - let labelFrame = CGRect(origin: CGPoint(x: sideInset, y: 11.0), size: labelSize) - let textFrame = CGRect(origin: CGPoint(x: sideInset, y: labelFrame.maxY + 3.0), size: textSize) - let additionalTextFrame = CGRect(origin: CGPoint(x: sideInset, y: textFrame.maxY + 3.0), size: additionalTextSize) + var topOffset = 11.0 + var height = topOffset * 2.0 + let labelFrame = CGRect(origin: CGPoint(x: sideInset, y: topOffset), size: labelSize) + if labelSize.height > 0.0 { + topOffset += labelSize.height + 3.0 + height += labelSize.height + 3.0 + } + let textFrame = CGRect(origin: CGPoint(x: sideInset, y: topOffset), size: textSize) + if textSize.height > 0.0 { + topOffset += textSize.height + 3.0 + height += textSize.height + } + let additionalTextFrame = CGRect(origin: CGPoint(x: sideInset, y: topOffset), size: additionalTextSize) let expandFrame = CGRect(origin: CGPoint(x: width - safeInsets.right - expandSize.width - 14.0 - UIScreenPixel, y: textFrame.maxY - expandSize.height), size: expandSize) self.expandNode.frame = expandFrame @@ -496,8 +506,6 @@ private final class PeerInfoScreenLabeledValueItemNode: PeerInfoScreenItemNode { transition.updateFrame(node: self.additionalTextNode, frame: additionalTextFrame) - var height = labelSize.height + 3.0 + textSize.height + 22.0 - let iconButtonFrame = CGRect(x: width - safeInsets.right - height, y: 0.0, width: height, height: height) transition.updateFrame(node: self.iconButtonNode, frame: iconButtonFrame) if let iconSize = self.iconNode.image?.size { diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift index 3aba2570be..e4b8ee529e 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift @@ -1142,10 +1142,18 @@ func peerInfoHeaderButtonIsHiddenWhileExpanded(buttonKey: PeerInfoHeaderButtonKe return hiddenWhileExpanded } +func peerInfoHeaderActionButtons(peer: Peer?, isSecretChat: Bool, isContact: Bool) -> [PeerInfoHeaderButtonKey] { + var result: [PeerInfoHeaderButtonKey] = [] + if !isContact && !isSecretChat, let user = peer as? TelegramUser, user.botInfo == nil { + result = [.message, .addContact] + } + return result +} + func peerInfoHeaderButtons(peer: Peer?, cachedData: CachedPeerData?, isOpenedFromChat: Bool, isExpanded: Bool, videoCallsEnabled: Bool, isSecretChat: Bool, isContact: Bool, threadInfo: EngineMessageHistoryThread.Info?) -> [PeerInfoHeaderButtonKey] { var result: [PeerInfoHeaderButtonKey] = [] if let user = peer as? TelegramUser { - if !isOpenedFromChat { + if !isOpenedFromChat && isContact { result.append(.message) } var callsAvailable = false diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift index 80c6aabc17..c0af3f13b3 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift @@ -44,6 +44,7 @@ enum PeerInfoHeaderButtonKey: Hashable { case search case leave case stop + case addContact } enum PeerInfoHeaderButtonIcon { @@ -135,7 +136,7 @@ final class PeerInfoHeaderButtonNode: HighlightableButtonNode { self.action(self, nil) } - func update(size: CGSize, text: String, icon: PeerInfoHeaderButtonIcon, isActive: Bool, isExpanded: Bool, presentationData: PresentationData, transition: ContainedViewLayoutTransition) { + func update(size: CGSize, text: String, icon: PeerInfoHeaderButtonIcon, isActive: Bool, presentationData: PresentationData, transition: ContainedViewLayoutTransition) { let previousIcon = self.icon let themeUpdated = self.theme != presentationData.theme let iconUpdated = self.icon != icon @@ -288,6 +289,88 @@ final class PeerInfoHeaderButtonNode: HighlightableButtonNode { } } +final class PeerInfoHeaderActionButtonNode: HighlightableButtonNode { + let key: PeerInfoHeaderButtonKey + private let action: (PeerInfoHeaderActionButtonNode, ContextGesture?) -> Void + let referenceNode: ContextReferenceContentNode + let containerNode: ContextControllerSourceNode + private let backgroundNode: ASDisplayNode + private let textNode: ImmediateTextNode + + private var theme: PresentationTheme? + + init(key: PeerInfoHeaderButtonKey, action: @escaping (PeerInfoHeaderActionButtonNode, ContextGesture?) -> Void) { + self.key = key + self.action = action + + self.referenceNode = ContextReferenceContentNode() + self.containerNode = ContextControllerSourceNode() + self.containerNode.animateScale = false + + self.backgroundNode = ASDisplayNode() + self.backgroundNode.cornerRadius = 11.0 + + self.textNode = ImmediateTextNode() + self.textNode.displaysAsynchronously = false + self.textNode.isUserInteractionEnabled = false + + super.init() + + self.accessibilityTraits = .button + + self.containerNode.addSubnode(self.referenceNode) + self.referenceNode.addSubnode(self.backgroundNode) + self.addSubnode(self.containerNode) + self.addSubnode(self.textNode) + + self.highligthedChanged = { [weak self] highlighted in + if let strongSelf = self { + if highlighted { + strongSelf.layer.removeAnimation(forKey: "opacity") + strongSelf.alpha = 0.4 + } else { + strongSelf.alpha = 1.0 + strongSelf.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + } + } + } + + self.containerNode.activated = { [weak self] gesture, _ in + if let strongSelf = self { + strongSelf.action(strongSelf, gesture) + } + } + + self.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) + } + + @objc private func buttonPressed() { + self.action(self, nil) + } + + func update(size: CGSize, text: String, presentationData: PresentationData, transition: ContainedViewLayoutTransition) { + let themeUpdated = self.theme != presentationData.theme + if themeUpdated { + self.theme = presentationData.theme + + self.containerNode.isGestureEnabled = false + + self.backgroundNode.backgroundColor = presentationData.theme.list.itemAccentColor + transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: size)) + } + + self.textNode.attributedText = NSAttributedString(string: text, font: Font.semibold(16.0), textColor: presentationData.theme.list.itemCheckColors.foregroundColor) + self.accessibilityLabel = text + let titleSize = self.textNode.updateLayout(CGSize(width: 120.0, height: .greatestFiniteMagnitude)) + + transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(), size: size)) + transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: size)) + transition.updateFrameAdditiveToCenter(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: floorToScreenPixels((size.height - titleSize.height) / 2.0)), size: titleSize)) + + self.referenceNode.frame = self.containerNode.bounds + } +} + final class PeerInfoHeaderNavigationTransition { let sourceNavigationBar: NavigationBar let sourceTitleView: ChatTitleView @@ -2285,6 +2368,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { let usernameNodeContainer: ASDisplayNode let usernameNodeRawContainer: ASDisplayNode let usernameNode: MultiScaleTextNode + var actionButtonNodes: [PeerInfoHeaderButtonKey: PeerInfoHeaderActionButtonNode] = [:] var buttonNodes: [PeerInfoHeaderButtonKey: PeerInfoHeaderButtonNode] = [:] let backgroundNode: NavigationBackgroundNode let expandedBackgroundNode: NavigationBackgroundNode @@ -2834,6 +2918,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { 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 @@ -3497,14 +3582,69 @@ final class PeerInfoHeaderNode: ASDisplayNode { let buttonSpacing: CGFloat = 8.0 let buttonSideInset = max(16.0, containerInset) - var buttonRightOrigin = CGPoint(x: width - buttonSideInset, y: maxY + 25.0 - navigationHeight - UIScreenPixel) + + var actionButtonRightOrigin = CGPoint(x: width - buttonSideInset, y: maxY + 24.0 - navigationHeight - UIScreenPixel) + let actionButtonWidth = (width - buttonSideInset * 2.0 + buttonSpacing) / CGFloat(actionButtonKeys.count) - buttonSpacing + let actionButtonSize = CGSize(width: actionButtonWidth, height: 40.0) + + 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() + } + } + } + } + + var buttonRightOrigin = CGPoint(x: width - buttonSideInset, y: maxY + 24.0 - navigationHeight - UIScreenPixel) + if !actionButtonKeys.isEmpty { + buttonRightOrigin.y += actionButtonSize.height + 24.0 + } let buttonWidth = (width - buttonSideInset * 2.0 + buttonSpacing) / CGFloat(buttonKeys.count) - buttonSpacing - - let apparentButtonSize = CGSize(width: buttonWidth, height: 58.0) - let buttonsAlpha: CGFloat = 1.0 - let buttonsVerticalOffset: CGFloat = 0.0 - - let buttonsAlphaTransition = transition + let buttonSize = CGSize(width: buttonWidth, height: 58.0) for buttonKey in buttonKeys.reversed() { let buttonNode: PeerInfoHeaderButtonNode @@ -3520,14 +3660,13 @@ final class PeerInfoHeaderNode: ASDisplayNode { self.buttonsContainerNode.addSubnode(buttonNode) } - let buttonFrame = CGRect(origin: CGPoint(x: buttonRightOrigin.x - apparentButtonSize.width, y: buttonRightOrigin.y), size: apparentButtonSize) + let buttonFrame = CGRect(origin: CGPoint(x: buttonRightOrigin.x - buttonSize.width, y: buttonRightOrigin.y), size: buttonSize) let buttonTransition: ContainedViewLayoutTransition = wasAdded ? .immediate : transition - let apparentButtonFrame = buttonFrame.offsetBy(dx: 0.0, dy: buttonsVerticalOffset) if additive { - buttonTransition.updateFrameAdditiveToCenter(node: buttonNode, frame: apparentButtonFrame) + buttonTransition.updateFrameAdditiveToCenter(node: buttonNode, frame: buttonFrame) } else { - buttonTransition.updateFrame(node: buttonNode, frame: apparentButtonFrame) + buttonTransition.updateFrame(node: buttonNode, frame: buttonFrame) } let buttonText: String let buttonIcon: PeerInfoHeaderButtonIcon @@ -3575,6 +3714,8 @@ final class PeerInfoHeaderNode: ASDisplayNode { case .stop: buttonText = presentationData.strings.PeerInfo_ButtonStop buttonIcon = .stop + case .addContact: + fatalError() } var isActive = true @@ -3582,12 +3723,12 @@ final class PeerInfoHeaderNode: ASDisplayNode { isActive = buttonKey == highlightedButton } - buttonNode.update(size: buttonFrame.size, text: buttonText, icon: buttonIcon, isActive: isActive, isExpanded: false, presentationData: presentationData, transition: buttonTransition) + buttonNode.update(size: buttonFrame.size, text: buttonText, icon: buttonIcon, isActive: isActive, presentationData: presentationData, transition: buttonTransition) if wasAdded { buttonNode.alpha = 0.0 } - buttonsAlphaTransition.updateAlpha(node: buttonNode, alpha: buttonsAlpha) + transition.updateAlpha(node: buttonNode, alpha: 1.0) if case .mute = buttonKey, buttonNode.containerNode.alpha.isZero, additive { if case let .animated(duration, curve) = transition { @@ -3598,7 +3739,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { } else { transition.updateAlpha(node: buttonNode.containerNode, alpha: 1.0) } - buttonRightOrigin.x -= apparentButtonSize.width + buttonSpacing + buttonRightOrigin.x -= buttonSize.width + buttonSpacing } for key in self.buttonNodes.keys { @@ -3622,7 +3763,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { let backgroundFrame: CGRect let separatorFrame: CGRect - let resolvedHeight: CGFloat + var resolvedHeight: CGFloat if state.isEditing { resolvedHeight = editingContentHeight @@ -3635,7 +3776,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { } transition.updateFrame(node: self.regularContentNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: width, height: resolvedHeight))) - transition.updateFrame(node: self.buttonsContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationHeight + UIScreenPixel), size: CGSize(width: width, height: resolvedHeight - navigationHeight + 180.0))) + transition.updateFrame(node: self.buttonsContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationHeight + UIScreenPixel), size: CGSize(width: width, height: resolvedHeight - navigationHeight + 500.0))) if additive { transition.updateFrameAdditive(node: self.backgroundNode, frame: backgroundFrame) @@ -3651,6 +3792,14 @@ final class PeerInfoHeaderNode: ASDisplayNode { transition.updateFrame(node: self.separatorNode, frame: separatorFrame) } + if !state.isEditing && !isSettings { + resolvedHeight += 71.0 + + if !actionButtonKeys.isEmpty { + resolvedHeight += 64.0 + } + } + if isFirstTime { self.updateAvatarMask(transition: .immediate) } @@ -3662,6 +3811,10 @@ final class PeerInfoHeaderNode: ASDisplayNode { 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 diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index 2b8b7a994b..320454854e 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -976,7 +976,7 @@ private func settingsEditingItems(data: PeerInfoScreenData?, state: PeerInfoStat return result } -private func infoItems(data: PeerInfoScreenData?, context: AccountContext, presentationData: PresentationData, interaction: PeerInfoInteraction, nearbyPeerDistance: Int32?, reactionSourceMessageId: MessageId?, callMessages: [Message], chatLocation: ChatLocation) -> [(AnyHashable, [PeerInfoScreenItem])] { +private func infoItems(data: PeerInfoScreenData?, context: AccountContext, presentationData: PresentationData, interaction: PeerInfoInteraction, nearbyPeerDistance: Int32?, reactionSourceMessageId: MessageId?, callMessages: [Message], chatLocation: ChatLocation, isOpenedFromChat: Bool) -> [(AnyHashable, [PeerInfoScreenItem])] { guard let data = data else { return [] } @@ -1005,39 +1005,63 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese items[.calls]!.append(PeerInfoScreenCallListItem(id: 20, messages: callMessages)) } - if let phone = user.phone { - let formattedPhone = formatPhoneNumber(context: context, number: phone) - let label: String - if formattedPhone.hasPrefix("+888 ") { - label = presentationData.strings.UserInfo_AnonymousNumberLabel - } else { - label = presentationData.strings.ContactInfo_PhoneLabelMobile - } - items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 2, label: label, text: formattedPhone, textColor: .accent, action: { node in - interaction.openPhone(phone, node, nil) - }, longTapAction: nil, contextAction: { node, gesture, _ in - interaction.openPhone(phone, node, gesture) - }, requestLayout: { - interaction.requestLayout(false) - })) - } + var username: String? + var additionalUsernames: String? + var phoneNumber: String? if let mainUsername = user.addressName { - var additionalUsernames: String? + username = mainUsername let usernames = user.usernames.filter { $0.isActive && $0.username != mainUsername } if !usernames.isEmpty { additionalUsernames = presentationData.strings.Profile_AdditionalUsernames(String(usernames.map { "@\($0.username)" }.joined(separator: ", "))).string } - + } + if let phone = user.phone { + phoneNumber = formatPhoneNumber(context: context, number: phone) + if let phone = phoneNumber, !phone.isEmpty && !phone.hasPrefix("+") { + phoneNumber = "+\(phone)" + } + } + + if user.botInfo == nil { + if username != nil || phoneNumber != nil { + items[.peerInfo]!.append(PeerInfoScreenContactInfoItem( + id: 1, + username: username.flatMap { "@\($0)" } ?? "", + phoneNumber: phoneNumber ?? "", + additionalText: additionalUsernames, + usernameAction: { _ in + interaction.openUsername(username ?? "") + }, + usernameLongTapAction: { sourceNode in + interaction.openPeerInfoContextMenu(.link(customLink: nil), sourceNode, nil) + }, + phoneAction: { node in + interaction.openPhone(phoneNumber ?? "", node, nil) + }, + phoneLongTapAction: { _ in }, + linkItemAction: { type, item, _, _ in + if case .tap = type { + if case let .mention(username) = item { + interaction.openUsername(String(username[username.index(username.startIndex, offsetBy: 1)...])) + } + } + }, + requestLayout: { + interaction.requestLayout(false) + } + )) + } + } else if let username { items[.peerInfo]!.append( PeerInfoScreenLabeledValueItem( id: 1, - label: presentationData.strings.Profile_Username, - text: "@\(mainUsername)", + label: "", + text: "@\(username)", additionalText: additionalUsernames, textColor: .accent, icon: .qrCode, action: { _ in - interaction.openUsername(mainUsername) + interaction.openUsername(username) }, longTapAction: { sourceNode in interaction.openPeerInfoContextMenu(.link(customLink: nil), sourceNode, nil) }, linkItemAction: { type, item, _, _ in @@ -1054,17 +1078,18 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese ) ) } + if let cachedData = data.cachedData as? CachedUserData { if user.isFake { - items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: user.botInfo == nil ? presentationData.strings.Profile_About : presentationData.strings.Profile_BotInfo, text: user.botInfo != nil ? presentationData.strings.UserInfo_FakeBotWarning : presentationData.strings.UserInfo_FakeUserWarning, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: user.botInfo != nil ? enabledPrivateBioEntities : []), action: nil, requestLayout: { + items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: "", text: user.botInfo != nil ? presentationData.strings.UserInfo_FakeBotWarning : presentationData.strings.UserInfo_FakeUserWarning, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: user.botInfo != nil ? enabledPrivateBioEntities : []), action: nil, requestLayout: { interaction.requestLayout(false) })) } else if user.isScam { - items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: user.botInfo == nil ? presentationData.strings.Profile_About : presentationData.strings.Profile_BotInfo, text: user.botInfo != nil ? presentationData.strings.UserInfo_ScamBotWarning : presentationData.strings.UserInfo_ScamUserWarning, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: user.botInfo != nil ? enabledPrivateBioEntities : []), action: nil, requestLayout: { + items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: "", text: user.botInfo != nil ? presentationData.strings.UserInfo_ScamBotWarning : presentationData.strings.UserInfo_ScamUserWarning, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: user.botInfo != nil ? enabledPrivateBioEntities : []), action: nil, requestLayout: { interaction.requestLayout(false) })) } else if let about = cachedData.about, !about.isEmpty { - items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: user.botInfo == nil ? presentationData.strings.Profile_About : presentationData.strings.Profile_BotInfo, text: about, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: user.isPremium ? enabledPublicBioEntities : enabledPrivateBioEntities), action: nil, longTapAction: bioContextAction, linkItemAction: bioLinkAction, requestLayout: { + items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: "", text: about, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: user.isPremium ? enabledPublicBioEntities : enabledPrivateBioEntities), action: nil, longTapAction: bioContextAction, linkItemAction: bioLinkAction, requestLayout: { interaction.requestLayout(false) })) } @@ -1086,14 +1111,6 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese interaction.openReport(.user) })) } else { - if !data.isContact { - if user.botInfo == nil { - items[.peerInfo]!.append(PeerInfoScreenActionItem(id: 3, text: presentationData.strings.PeerInfo_AddToContacts, action: { - interaction.openAddContact() - })) - } - } - var isBlocked = false if let cachedData = data.cachedData as? CachedUserData, cachedData.isBlocked { isBlocked = true @@ -1106,7 +1123,7 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese } else { if user.flags.contains(.isSupport) || data.isContact { } else { - if user.botInfo == nil { + if user.botInfo == nil && isOpenedFromChat { items[.peerInfo]!.append(PeerInfoScreenActionItem(id: 4, text: presentationData.strings.Conversation_BlockUser, color: .destructive, action: { interaction.updateBlocked(true) })) @@ -1162,7 +1179,7 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese items[.peerInfo]!.append( PeerInfoScreenLabeledValueItem( id: ItemUsername, - label: presentationData.strings.Channel_LinkItem, + label: "", text: linkText, textColor: .accent, icon: .qrCode, @@ -1214,7 +1231,7 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese items[.peerInfo]!.append( PeerInfoScreenLabeledValueItem( id: ItemUsername, - label: presentationData.strings.Channel_LinkItem, + label: "", text: "https://t.me/\(mainUsername)", additionalText: additionalUsernames, textColor: .accent, @@ -1266,7 +1283,7 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese if case .group = channel.info { enabledEntities = enabledPrivateBioEntities } - items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: ItemAbout, label: presentationData.strings.Channel_Info_Description, text: aboutText, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: enabledEntities), action: nil, longTapAction: bioContextAction, linkItemAction: bioLinkAction, requestLayout: { + items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: ItemAbout, label: "", text: aboutText, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: enabledEntities), action: nil, longTapAction: bioContextAction, linkItemAction: bioLinkAction, requestLayout: { interaction.requestLayout(true) })) } @@ -1312,7 +1329,7 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese } if let aboutText = aboutText { - items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: presentationData.strings.Channel_Info_Description, text: aboutText, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: enabledPrivateBioEntities), action: nil, longTapAction: bioContextAction, linkItemAction: bioLinkAction, requestLayout: { + items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: "", text: aboutText, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: enabledPrivateBioEntities), action: nil, longTapAction: bioContextAction, linkItemAction: bioLinkAction, requestLayout: { interaction.requestLayout(true) })) } @@ -5426,6 +5443,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro case .stop: self.controller?.present(UndoOverlayController(presentationData: self.presentationData, content: .universal(animation: "anim_banned", scale: 0.066, colors: [:], title: self.presentationData.strings.PeerInfo_BotBlockedTitle, text: self.presentationData.strings.PeerInfo_BotBlockedText, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) self.updateBlocked(block: true) + case .addContact: + self.openAddContact() } } @@ -8886,10 +8905,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } let headerInset = sectionInset - var headerHeight = self.headerNode.update(width: layout.size.width, containerHeight: layout.size.height, containerInset: headerInset, statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: navigationHeight, isModalOverlay: layout.isModalOverlay, isMediaOnly: self.isMediaOnly, contentOffset: self.isMediaOnly ? 212.0 : self.scrollNode.view.contentOffset.y, paneContainerY: self.paneContainerNode.frame.minY, presentationData: self.presentationData, peer: self.data?.peer, cachedData: self.data?.cachedData, threadData: self.data?.threadData, peerNotificationSettings: self.data?.peerNotificationSettings, threadNotificationSettings: self.data?.threadNotificationSettings, globalNotificationSettings: self.data?.globalNotificationSettings, statusData: self.data?.status, panelStatusData: self.customStatusData, isSecretChat: self.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.data?.isContact ?? false, isSettings: self.isSettings, state: self.state, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, transition: transition, additive: additive) - if !self.isSettings && !self.state.isEditing { - headerHeight += 71.0 - } + let headerHeight = self.headerNode.update(width: layout.size.width, containerHeight: layout.size.height, containerInset: headerInset, statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: navigationHeight, isModalOverlay: layout.isModalOverlay, isMediaOnly: self.isMediaOnly, contentOffset: self.isMediaOnly ? 212.0 : self.scrollNode.view.contentOffset.y, paneContainerY: self.paneContainerNode.frame.minY, presentationData: self.presentationData, peer: self.data?.peer, cachedData: self.data?.cachedData, threadData: self.data?.threadData, peerNotificationSettings: self.data?.peerNotificationSettings, threadNotificationSettings: self.data?.threadNotificationSettings, globalNotificationSettings: self.data?.globalNotificationSettings, statusData: self.data?.status, panelStatusData: self.customStatusData, isSecretChat: self.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.data?.isContact ?? false, isSettings: self.isSettings, state: self.state, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, transition: transition, additive: additive) let headerFrame = CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: CGSize(width: layout.size.width, height: headerHeight)) if additive { transition.updateFrameAdditive(node: self.headerNode, frame: headerFrame) @@ -8906,11 +8922,11 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro insets.left += sectionInset insets.right += sectionInset - let items = self.isSettings ? settingsItems(data: self.data, context: self.context, presentationData: self.presentationData, interaction: self.interaction, isExpanded: self.headerNode.isAvatarExpanded) : infoItems(data: self.data, context: self.context, presentationData: self.presentationData, interaction: self.interaction, nearbyPeerDistance: self.nearbyPeerDistance, reactionSourceMessageId: self.reactionSourceMessageId, callMessages: self.callMessages, chatLocation: self.chatLocation) + let items = self.isSettings ? settingsItems(data: self.data, context: self.context, presentationData: self.presentationData, interaction: self.interaction, isExpanded: self.headerNode.isAvatarExpanded) : infoItems(data: self.data, context: self.context, presentationData: self.presentationData, interaction: self.interaction, nearbyPeerDistance: self.nearbyPeerDistance, reactionSourceMessageId: self.reactionSourceMessageId, callMessages: self.callMessages, chatLocation: self.chatLocation, isOpenedFromChat: self.isOpenedFromChat) contentHeight += headerHeight if !(self.isSettings && self.state.isEditing) { - contentHeight += sectionSpacing + contentHeight += sectionSpacing + 12.0 } for (sectionId, sectionItems) in items {