Skip to content

Commit d7e2ed1

Browse files
OLED profile card layout: action card, brighter labels, pane card
- Move header action buttons into first #1C1C1E card (48pt circles) - Black hero keeps avatar + name + status only - Brighter field labels (#AEAEB2) on info card rows - Wrap tabs + pane content in unified #1C1C1E card with section inset - Collapse id/dc/registered into dim footer line - Page background #000 between cards in OLED profile mode Co-authored-by: D3C0Y <decoder-dev@users.noreply.github.com>
1 parent 01a4b27 commit d7e2ed1

9 files changed

Lines changed: 350 additions & 114 deletions
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import Foundation
2+
import UIKit
3+
import Display
4+
import AsyncDisplayKit
5+
import AccountContext
6+
import TelegramPresentationData
7+
import TelegramCore
8+
import PresentationDataUtils
9+
import ContextUI
10+
11+
private let actionButtonDiameter: CGFloat = 48.0
12+
private let actionButtonSpacing: CGFloat = 20.0
13+
private let actionCardVerticalInset: CGFloat = 16.0
14+
15+
final class PeerInfoScreenActionButtonsItem: PeerInfoScreenItem {
16+
let id: AnyHashable = AnyHashable("oled_action_buttons")
17+
let buttonKeys: [PeerInfoHeaderButtonKey]
18+
let highlightedButton: PeerInfoHeaderButtonKey?
19+
let peer: EnginePeer?
20+
let peerNotificationSettings: TelegramPeerNotificationSettings?
21+
let threadNotificationSettings: TelegramPeerNotificationSettings?
22+
let globalNotificationSettings: EngineGlobalNotificationSettings?
23+
let performAction: (PeerInfoHeaderButtonKey, PeerInfoHeaderButtonNode?, ContextGesture?) -> Void
24+
25+
init(
26+
buttonKeys: [PeerInfoHeaderButtonKey],
27+
highlightedButton: PeerInfoHeaderButtonKey?,
28+
peer: EnginePeer?,
29+
peerNotificationSettings: TelegramPeerNotificationSettings?,
30+
threadNotificationSettings: TelegramPeerNotificationSettings?,
31+
globalNotificationSettings: EngineGlobalNotificationSettings?,
32+
performAction: @escaping (PeerInfoHeaderButtonKey, PeerInfoHeaderButtonNode?, ContextGesture?) -> Void
33+
) {
34+
self.buttonKeys = buttonKeys
35+
self.highlightedButton = highlightedButton
36+
self.peer = peer
37+
self.peerNotificationSettings = peerNotificationSettings
38+
self.threadNotificationSettings = threadNotificationSettings
39+
self.globalNotificationSettings = globalNotificationSettings
40+
self.performAction = performAction
41+
}
42+
43+
func node() -> PeerInfoScreenItemNode {
44+
return PeerInfoScreenActionButtonsItemNode()
45+
}
46+
}
47+
48+
private final class PeerInfoScreenActionButtonsItemNode: PeerInfoScreenItemNode {
49+
private let maskNode: ASImageNode
50+
private var buttonNodes: [PeerInfoHeaderButtonKey: PeerInfoHeaderButtonNode] = [:]
51+
private var item: PeerInfoScreenActionButtonsItem?
52+
private var presentationData: PresentationData?
53+
54+
override init() {
55+
self.maskNode = ASImageNode()
56+
self.maskNode.isUserInteractionEnabled = false
57+
super.init()
58+
self.addSubnode(self.maskNode)
59+
}
60+
61+
override func update(context: AccountContext, width: CGFloat, safeInsets: UIEdgeInsets, presentationData: PresentationData, item: PeerInfoScreenItem, topItem: PeerInfoScreenItem?, bottomItem: PeerInfoScreenItem?, hasCorners: Bool, transition: ContainedViewLayoutTransition) -> CGFloat {
62+
guard let item = item as? PeerInfoScreenActionButtonsItem else {
63+
return 0.0
64+
}
65+
self.item = item
66+
self.presentationData = presentationData
67+
68+
let height = actionCardVerticalInset * 2.0 + actionButtonDiameter
69+
let totalButtonsWidth = CGFloat(item.buttonKeys.count) * actionButtonDiameter + CGFloat(max(0, item.buttonKeys.count - 1)) * actionButtonSpacing
70+
var leftOrigin = floor((width - totalButtonsWidth) / 2.0)
71+
let buttonY = actionCardVerticalInset
72+
73+
for buttonKey in item.buttonKeys {
74+
let buttonNode: PeerInfoHeaderButtonNode
75+
var wasAdded = false
76+
if let current = self.buttonNodes[buttonKey] {
77+
buttonNode = current
78+
} else {
79+
wasAdded = true
80+
buttonNode = PeerInfoHeaderButtonNode(key: buttonKey, action: { [weak self] buttonNode, gesture in
81+
self?.item?.performAction(buttonNode.key, buttonNode, gesture)
82+
})
83+
self.buttonNodes[buttonKey] = buttonNode
84+
self.addSubnode(buttonNode)
85+
}
86+
87+
let buttonFrame = CGRect(origin: CGPoint(x: leftOrigin, y: buttonY), size: CGSize(width: actionButtonDiameter, height: actionButtonDiameter))
88+
transition.updateFrame(node: buttonNode, frame: buttonFrame)
89+
transition.updateFrame(view: buttonNode.backgroundContainerView, frame: buttonFrame)
90+
91+
let content = peerInfoHeaderButtonContent(
92+
key: buttonKey,
93+
presentationData: presentationData,
94+
peer: item.peer,
95+
peerNotificationSettings: item.peerNotificationSettings,
96+
threadNotificationSettings: item.threadNotificationSettings,
97+
globalNotificationSettings: item.globalNotificationSettings
98+
)
99+
100+
var isActive = true
101+
if let highlightedButton = item.highlightedButton {
102+
isActive = buttonKey == highlightedButton
103+
}
104+
105+
buttonNode.update(
106+
size: buttonFrame.size,
107+
text: content.text,
108+
icon: content.icon,
109+
isActive: isActive,
110+
presentationData: presentationData,
111+
backgroundColor: peerInfoOledActionButtonBackgroundColor,
112+
foregroundColor: .white,
113+
fraction: 1.0,
114+
circularStyle: true,
115+
transition: wasAdded ? .immediate : transition
116+
)
117+
118+
if wasAdded {
119+
buttonNode.alpha = 0.0
120+
transition.updateAlpha(node: buttonNode, alpha: 1.0)
121+
}
122+
123+
leftOrigin += actionButtonDiameter + actionButtonSpacing
124+
}
125+
126+
for key in self.buttonNodes.keys {
127+
if !item.buttonKeys.contains(key) {
128+
if let buttonNode = self.buttonNodes.removeValue(forKey: key) {
129+
transition.updateAlpha(node: buttonNode, alpha: 0.0) { [weak buttonNode] _ in
130+
buttonNode?.backgroundContainerView.removeFromSuperview()
131+
buttonNode?.removeFromSupernode()
132+
}
133+
}
134+
}
135+
}
136+
137+
let hasRoundedCorners = hasCorners && (topItem == nil || bottomItem == nil)
138+
let hasTopCorners = hasRoundedCorners && topItem == nil
139+
let hasBottomCorners = hasRoundedCorners && bottomItem == nil
140+
self.maskNode.image = hasRoundedCorners ? PresentationResourcesItemList.cornersImage(presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: true) : nil
141+
transition.updateFrame(node: self.maskNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: width, height: height)))
142+
143+
return height
144+
}
145+
}

‎submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenCommentItem.swift‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,16 @@ final class PeerInfoScreenCommentItem: PeerInfoScreenItem {
2323
let text: String
2424
let attributedPrefix: NSAttributedString?
2525
let useAccentLinkColor: Bool
26+
let isDimFooter: Bool
2627
let linkAction: ((LinkAction) -> Void)?
2728

28-
init(id: AnyHashable, icon: Icon? = nil, text: String, attributedPrefix: NSAttributedString? = nil, useAccentLinkColor: Bool = true, linkAction: ((LinkAction) -> Void)? = nil) {
29+
init(id: AnyHashable, icon: Icon? = nil, text: String, attributedPrefix: NSAttributedString? = nil, useAccentLinkColor: Bool = true, isDimFooter: Bool = false, linkAction: ((LinkAction) -> Void)? = nil) {
2930
self.id = id
3031
self.icon = icon
3132
self.text = text
3233
self.attributedPrefix = attributedPrefix
3334
self.useAccentLinkColor = useAccentLinkColor
35+
self.isDimFooter = isDimFooter
3436
self.linkAction = linkAction
3537
}
3638

@@ -89,7 +91,7 @@ private final class PeerInfoScreenCommentItemNode: PeerInfoScreenItemNode {
8991
self.presentationData = presentationData
9092

9193
let sideInset: CGFloat = 16.0 + safeInsets.left
92-
let verticalInset: CGFloat = 7.0
94+
let verticalInset: CGFloat = item.isDimFooter ? 10.0 : 7.0
9395

9496
self.textNode.maximumNumberOfLines = 0
9597
self.textNode.arguments = TextNodeWithEntities.Arguments(
@@ -100,8 +102,8 @@ private final class PeerInfoScreenCommentItemNode: PeerInfoScreenItemNode {
100102
attemptSynchronous: false
101103
)
102104

103-
let textFont = Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize)
104-
let textColor = presentationData.theme.list.freeTextColor
105+
let textFont = Font.regular(item.isDimFooter ? 12.0 : presentationData.listsFontSize.itemListBaseHeaderFontSize)
106+
let textColor = item.isDimFooter ? peerInfoOledCardFooterColor : presentationData.theme.list.freeTextColor
105107

106108
var text = item.text
107109
text = text.replacingOccurrences(of: " >]", with: "\u{00A0}>]")

‎submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenLabeledValueItem.swift‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -503,7 +503,7 @@ private final class PeerInfoScreenLabeledValueItemNode: PeerInfoScreenItemNode {
503503
self.expandNode.attributedText = NSAttributedString(string: presentationData.strings.PeerInfo_BioExpand, font: Font.regular(17.0), textColor: presentationData.theme.list.itemAccentColor)
504504
let expandSize = self.expandNode.updateLayout(CGSize(width: width, height: 100.0))
505505

506-
self.labelNode.attributedText = NSAttributedString(string: item.label, font: Font.regular(14.0), textColor: presentationData.theme.list.itemPrimaryTextColor)
506+
self.labelNode.attributedText = NSAttributedString(string: item.label, font: Font.regular(14.0), textColor: peerInfoUsesOledCardLayout(presentationData: presentationData, isSettings: false, isMyProfile: false) && !item.label.isEmpty ? peerInfoOledCardLabelColor : presentationData.theme.list.itemPrimaryTextColor)
507507

508508
self.rightLabelNode.attributedText = NSAttributedString(string: item.rightLabel ?? "", font: Font.regular(14.0), textColor: presentationData.theme.list.itemSecondaryTextColor)
509509

‎submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift‎

Lines changed: 20 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -548,6 +548,8 @@ final class PeerInfoHeaderNode: ASDisplayNode {
548548
let buttonKeys: [PeerInfoHeaderButtonKey] = (self.isSettings || self.isMyProfile) ? [] : peerInfoHeaderButtons(peer: peer, cachedData: cachedData, isOpenedFromChat: self.isOpenedFromChat, isExpanded: true, videoCallsEnabled: width > 320.0 && self.videoCallsEnabled, isSecretChat: isSecretChat, isContact: isContact, threadInfo: threadData?.info)
549549

550550
let useOledProfileStyle = presentationData.theme.overallDarkAppearance && !self.isSettings && !self.isMyProfile
551+
let useOledCardLayout = useOledProfileStyle
552+
let headerButtonKeys = useOledCardLayout ? [] : buttonKeys
551553

552554
let backgroundCoverSubject: PeerInfoCoverComponent.Subject?
553555
var backgroundCoverAnimateIn = false
@@ -561,7 +563,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
561563
}
562564
self.didSetupBackgroundCover = true
563565
}
564-
if !buttonKeys.isEmpty {
566+
if !headerButtonKeys.isEmpty {
565567
backgroundDefaultHeight = 327.0
566568
if metrics.isTablet {
567569
backgroundDefaultHeight += 60.0
@@ -2295,7 +2297,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
22952297
let buttonSpacing: CGFloat = 8.0
22962298
let buttonSideInset = max(16.0, containerInset)
22972299

2298-
let useCircularHeaderButtons = useOledProfileStyle && !buttonKeys.isEmpty
2300+
let useCircularHeaderButtons = useOledProfileStyle && !headerButtonKeys.isEmpty
22992301
let circularButtonDiameter: CGFloat = 52.0
23002302
let circularButtonSpacing: CGFloat = 20.0
23012303

@@ -2355,7 +2357,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
23552357
}
23562358
}
23572359

2358-
let buttonWidth = useCircularHeaderButtons ? circularButtonDiameter : (width - buttonSideInset * 2.0 + buttonSpacing) / CGFloat(buttonKeys.count) - buttonSpacing
2360+
let buttonWidth = useCircularHeaderButtons ? circularButtonDiameter : (width - buttonSideInset * 2.0 + buttonSpacing) / CGFloat(max(1, headerButtonKeys.count)) - buttonSpacing
23592361
let buttonSize = CGSize(width: buttonWidth, height: useCircularHeaderButtons ? circularButtonDiameter : 58.0)
23602362
var buttonRightOrigin = CGPoint(x: width - buttonSideInset, y: backgroundHeight - bottomInset - 16.0 - buttonSize.height)
23612363
if !actionButtonKeys.isEmpty {
@@ -2377,11 +2379,11 @@ final class PeerInfoHeaderNode: ASDisplayNode {
23772379

23782380
var circularButtonLeftOrigin: CGFloat = 0.0
23792381
if useCircularHeaderButtons {
2380-
let totalButtonsWidth = CGFloat(buttonKeys.count) * circularButtonDiameter + CGFloat(max(0, buttonKeys.count - 1)) * circularButtonSpacing
2382+
let totalButtonsWidth = CGFloat(headerButtonKeys.count) * circularButtonDiameter + CGFloat(max(0, headerButtonKeys.count - 1)) * circularButtonSpacing
23812383
circularButtonLeftOrigin = floor((width - totalButtonsWidth) / 2.0)
23822384
}
23832385

2384-
let orderedButtonKeys: [PeerInfoHeaderButtonKey] = useCircularHeaderButtons ? buttonKeys : buttonKeys.reversed()
2386+
let orderedButtonKeys: [PeerInfoHeaderButtonKey] = useCircularHeaderButtons ? headerButtonKeys : headerButtonKeys.reversed()
23852387
for buttonKey in orderedButtonKeys {
23862388
let buttonNode: PeerInfoHeaderButtonNode
23872389
var wasAdded = false
@@ -2413,55 +2415,16 @@ final class PeerInfoHeaderNode: ASDisplayNode {
24132415
}
24142416
buttonTransition.updateFrame(view: buttonNode.backgroundContainerView, frame: buttonFrame.offsetBy(dx: 0.0, dy: -buttonFrame.minY))
24152417

2416-
let buttonText: String
2417-
let buttonIcon: PeerInfoHeaderButtonIcon
2418-
switch buttonKey {
2419-
case .message:
2420-
buttonText = presentationData.strings.PeerInfo_ButtonMessage
2421-
buttonIcon = .message
2422-
case .discussion:
2423-
buttonText = presentationData.strings.PeerInfo_ButtonDiscuss
2424-
buttonIcon = .message
2425-
case .call:
2426-
buttonText = presentationData.strings.PeerInfo_ButtonCall
2427-
buttonIcon = .call
2428-
case .videoCall:
2429-
buttonText = presentationData.strings.PeerInfo_ButtonVideoCall
2430-
buttonIcon = .videoCall
2431-
case .voiceChat:
2432-
if case let .channel(channel) = peer, case .broadcast = channel.info {
2433-
buttonText = presentationData.strings.PeerInfo_ButtonLiveStream
2434-
} else {
2435-
buttonText = presentationData.strings.PeerInfo_ButtonVoiceChat
2436-
}
2437-
buttonIcon = .voiceChat
2438-
case .mute:
2439-
let chatIsMuted = peerInfoIsChatMuted(peer: peer, peerNotificationSettings: peerNotificationSettings, threadNotificationSettings: threadNotificationSettings, globalNotificationSettings: globalNotificationSettings)
2440-
if chatIsMuted {
2441-
buttonText = presentationData.strings.PeerInfo_ButtonUnmute
2442-
buttonIcon = .unmute
2443-
} else {
2444-
buttonText = presentationData.strings.PeerInfo_ButtonMute
2445-
buttonIcon = .mute
2446-
}
2447-
case .more:
2448-
buttonText = presentationData.strings.PeerInfo_ButtonMore
2449-
buttonIcon = .more
2450-
case .addMember:
2451-
buttonText = presentationData.strings.PeerInfo_ButtonAddMember
2452-
buttonIcon = .addMember
2453-
case .search:
2454-
buttonText = presentationData.strings.PeerInfo_ButtonSearch
2455-
buttonIcon = .search
2456-
case .leave:
2457-
buttonText = presentationData.strings.PeerInfo_ButtonLeave
2458-
buttonIcon = .leave
2459-
case .stop:
2460-
buttonText = presentationData.strings.PeerInfo_ButtonStop
2461-
buttonIcon = .stop
2462-
case .addContact:
2463-
fatalError()
2464-
}
2418+
let buttonContent = peerInfoHeaderButtonContent(
2419+
key: buttonKey,
2420+
presentationData: presentationData,
2421+
peer: peer,
2422+
peerNotificationSettings: peerNotificationSettings,
2423+
threadNotificationSettings: threadNotificationSettings,
2424+
globalNotificationSettings: globalNotificationSettings
2425+
)
2426+
let buttonText = buttonContent.text
2427+
let buttonIcon = buttonContent.icon
24652428

24662429
var isActive = true
24672430
if let highlightedButton = state.highlightedButton {
@@ -2496,7 +2459,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
24962459
}
24972460

24982461
for key in self.buttonNodes.keys {
2499-
if !buttonKeys.contains(key) {
2462+
if !headerButtonKeys.contains(key) {
25002463
if let buttonNode = self.buttonNodes[key] {
25012464
self.buttonNodes.removeValue(forKey: key)
25022465
transition.updateAlpha(layer: buttonNode.backgroundContainerView.layer, alpha: 0.0)
@@ -2553,7 +2516,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
25532516
avatarSize: apparentAvatarFrame.size,
25542517
avatarScale: avatarScale,
25552518
defaultHeight: backgroundDefaultHeight,
2556-
gradientCenter: CGPoint(x: 0.5, y: buttonKeys.isEmpty ? 0.5 : 0.45),
2519+
gradientCenter: CGPoint(x: 0.5, y: headerButtonKeys.isEmpty ? 0.5 : 0.45),
25572520
avatarTransitionFraction: max(0.0, min(1.0, titleCollapseFraction + transitionFraction * 2.0)),
25582521
patternTransitionFraction: buttonsTransitionFraction * backgroundTransitionFraction
25592522
)),
@@ -2598,7 +2561,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
25982561
topLeftButtonsSize: CGSize(width: (self.isSettings ? 57.0 : 47.0), height: 46.0),
25992562
topRightButtonsSize: CGSize(width: 76.0 + (self.isMyProfile ? 38.0 : 0.0), height: 46.0),
26002563
titleWidth: max(140.0, titleFrame.width) + 42.0,
2601-
bottomHeight: !buttonKeys.isEmpty ? 81.0 : 30.0,
2564+
bottomHeight: !headerButtonKeys.isEmpty ? 81.0 : 30.0,
26022565
action: { [weak self] gift in
26032566
guard let self, case let .unique(gift) = gift.gift else {
26042567
return

‎submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoInteraction.swift‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import TelegramCore
66
import SwiftSignalKit
77
import AccountContext
88
import StatisticsUI
9+
import ContextUI
910

1011
final class PeerInfoInteraction {
1112
let openChat: (EnginePeer.Id?) -> Void
@@ -87,6 +88,8 @@ final class PeerInfoInteraction {
8788
let editingOpenBusinessChatBots: () -> Void
8889
let getController: () -> ViewController?
8990

91+
var performHeaderButtonAction: ((PeerInfoHeaderButtonKey, PeerInfoHeaderButtonNode?, ContextGesture?) -> Void)?
92+
9093
init(
9194
openUsername: @escaping (String, Bool, Promise<Bool>?) -> Void,
9295
openPhone: @escaping (String, ASDisplayNode, ContextGesture?, Promise<Bool>?) -> Void,

0 commit comments

Comments
 (0)