chore: migrate to new version + fixed several critical bugs

- Migrated project to latest Telegram iOS base (v12.3.2+)
- Fixed circular dependency between GhostModeManager and MiscSettingsManager
- Fixed multiple Bazel build configuration errors (select() default conditions)
- Fixed duplicate type definitions in PeerInfoScreen
- Fixed swiftmodule directory resolution in build scripts
- Added Ghostgram Settings tab in main Settings menu with all 5 features
- Cleared sensitive credentials from config.json (template-only now)
- Excluded bazel-cache from version control
This commit is contained in:
ichmagmaus 812
2026-02-23 23:04:32 +01:00
parent 703e291bcb
commit db53826061
1017 changed files with 62337 additions and 40559 deletions
@@ -9,6 +9,7 @@ import AppBundle
import StoryPeerListComponent
import TelegramCore
import MoreHeaderButton
import GlassBackgroundComponent
public final class HeaderNetworkStatusComponent: Component {
public enum Content: Equatable {
@@ -79,7 +80,6 @@ public final class ChatListHeaderComponent: Component {
public let chatListTitle: NetworkStatusTitle?
public let leftButton: AnyComponentWithIdentity<NavigationButtonComponentEnvironment>?
public let rightButtons: [AnyComponentWithIdentity<NavigationButtonComponentEnvironment>]
public let backTitle: String?
public let backPressed: (() -> Void)?
public init(
@@ -89,7 +89,6 @@ public final class ChatListHeaderComponent: Component {
chatListTitle: NetworkStatusTitle?,
leftButton: AnyComponentWithIdentity<NavigationButtonComponentEnvironment>?,
rightButtons: [AnyComponentWithIdentity<NavigationButtonComponentEnvironment>],
backTitle: String?,
backPressed: (() -> Void)?
) {
self.title = title
@@ -98,7 +97,6 @@ public final class ChatListHeaderComponent: Component {
self.chatListTitle = chatListTitle
self.leftButton = leftButton
self.rightButtons = rightButtons
self.backTitle = backTitle
self.backPressed = backPressed
}
@@ -121,7 +119,7 @@ public final class ChatListHeaderComponent: Component {
if lhs.rightButtons != rhs.rightButtons {
return false
}
if lhs.backTitle != rhs.backTitle {
if (lhs.backPressed == nil) != (rhs.backPressed == nil) {
return false
}
return true
@@ -226,8 +224,6 @@ public final class ChatListHeaderComponent: Component {
private let onPressed: () -> Void
let arrowView: UIImageView
let titleOffsetContainer: UIView
let titleView: ImmediateTextView
private var currentColor: UIColor?
@@ -235,16 +231,11 @@ public final class ChatListHeaderComponent: Component {
self.onPressed = onPressed
self.arrowView = UIImageView()
self.titleOffsetContainer = UIView()
self.titleView = ImmediateTextView()
super.init(frame: CGRect())
self.addSubview(self.arrowView)
self.addSubview(self.titleOffsetContainer)
self.titleOffsetContainer.addSubview(self.titleView)
self.highligthedChanged = { [weak self] highlighted in
guard let self else {
return
@@ -266,37 +257,37 @@ public final class ChatListHeaderComponent: Component {
self.onPressed()
}
func update(title: String, theme: PresentationTheme, availableSize: CGSize, transition: ComponentTransition) -> CGSize {
let titleText = NSAttributedString(string: title, font: Font.regular(17.0), textColor: theme.rootController.navigationBar.accentTextColor)
let titleTextUpdated = self.titleView.attributedText != titleText
self.titleView.attributedText = titleText
let titleSize = self.titleView.updateLayout(CGSize(width: 100.0, height: 44.0))
self.accessibilityLabel = title
func update(theme: PresentationTheme, strings: PresentationStrings, availableSize: CGSize, transition: ComponentTransition) -> CGSize {
self.accessibilityLabel = strings.Common_Back
self.accessibilityTraits = [.button]
if self.currentColor != theme.rootController.navigationBar.accentTextColor {
self.currentColor = theme.rootController.navigationBar.accentTextColor
self.arrowView.image = NavigationBarTheme.generateBackArrowImage(color: theme.rootController.navigationBar.accentTextColor)
if self.currentColor != theme.chat.inputPanel.panelControlColor {
self.currentColor = theme.chat.inputPanel.panelControlColor
let imageSize = CGSize(width: 44.0, height: 44.0)
let topRightPoint = CGPoint(x: 24.6, y: 14.0)
let centerPoint = CGPoint(x: 17.0, y: imageSize.height * 0.5)
self.arrowView.image = generateImage(imageSize, rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setStrokeColor(UIColor.white.cgColor)
context.setLineWidth(2.0)
context.setLineCap(.round)
context.setLineJoin(.round)
context.move(to: topRightPoint)
context.addLine(to: centerPoint)
context.addLine(to: CGPoint(x: topRightPoint.x, y: size.height - topRightPoint.y))
context.strokePath()
})?.withRenderingMode(.alwaysTemplate)
self.arrowView.tintColor = theme.chat.inputPanel.panelControlColor
}
let iconSpacing: CGFloat = 8.0
let iconOffset: CGFloat = -7.0
let size = CGSize(width: 44.0, height: availableSize.height)
let arrowSize = self.arrowView.image?.size ?? CGSize(width: 13.0, height: 22.0)
let arrowFrame = CGRect(origin: CGPoint(x: iconOffset - 1.0, y: floor((availableSize.height - arrowSize.height) / 2.0)), size: arrowSize)
let arrowFrame = arrowSize.centered(in: CGRect(origin: CGPoint(), size: size))
transition.setPosition(view: self.arrowView, position: arrowFrame.center)
transition.setBounds(view: self.arrowView, bounds: CGRect(origin: CGPoint(), size: arrowFrame.size))
let titleFrame = CGRect(origin: CGPoint(x: iconOffset - 3.0 + arrowSize.width + iconSpacing, y: floor((availableSize.height - titleSize.height) / 2.0)), size: titleSize)
if titleTextUpdated {
self.titleView.frame = titleFrame
} else {
transition.setFrame(view: self.titleView, frame: titleFrame)
}
return CGSize(width: iconOffset + arrowSize.width + iconSpacing + titleSize.width, height: availableSize.height)
return size
}
}
@@ -305,9 +296,9 @@ public final class ChatListHeaderComponent: Component {
let openStatusSetup: (UIView) -> Void
let toggleIsLocked: () -> Void
let leftButtonOffsetContainer: UIView
let leftButtonsContainer: UIView
var leftButtonViews: [AnyHashable: ComponentView<NavigationButtonComponentEnvironment>] = [:]
let rightButtonOffsetContainer: UIView
let rightButtonsContainer: UIView
var rightButtonViews: [AnyHashable: ComponentView<NavigationButtonComponentEnvironment>] = [:]
var backButtonView: BackButtonView?
@@ -324,6 +315,9 @@ public final class ChatListHeaderComponent: Component {
private(set) var centerContentOffsetX: CGFloat = 0.0
private(set) var centerContentOrigin: CGFloat = 0.0
private(set) var leftButtonsWidth: CGFloat = 0.0
private(set) var rightButtonsWidth: CGFloat = 0.0
init(
backPressed: @escaping () -> Void,
@@ -334,8 +328,9 @@ public final class ChatListHeaderComponent: Component {
self.openStatusSetup = openStatusSetup
self.toggleIsLocked = toggleIsLocked
self.leftButtonOffsetContainer = UIView()
self.rightButtonOffsetContainer = UIView()
self.leftButtonsContainer = UIView()
self.rightButtonsContainer = UIView()
self.titleOffsetContainer = UIView()
self.titleScaleContainer = UIView()
@@ -345,8 +340,6 @@ public final class ChatListHeaderComponent: Component {
self.addSubview(self.titleOffsetContainer)
self.titleOffsetContainer.addSubview(self.titleScaleContainer)
self.addSubview(self.leftButtonOffsetContainer)
self.addSubview(self.rightButtonOffsetContainer)
self.titleScaleContainer.addSubview(self.titleTextView)
}
@@ -393,12 +386,12 @@ public final class ChatListHeaderComponent: Component {
transition.setSublayerTransform(view: self.titleOffsetContainer, transform: transform)
}
func updateNavigationTransitionAsPrevious(nextView: ContentView, fraction: CGFloat, transition: ComponentTransition, completion: @escaping () -> Void) {
transition.setBounds(view: self.leftButtonOffsetContainer, bounds: CGRect(origin: CGPoint(x: fraction * self.bounds.width * 0.5, y: 0.0), size: self.leftButtonOffsetContainer.bounds.size), completion: { _ in
completion()
})
transition.setAlpha(view: self.leftButtonOffsetContainer, alpha: pow(1.0 - fraction, 2.0))
transition.setAlpha(view: self.rightButtonOffsetContainer, alpha: pow(1.0 - fraction, 2.0))
func updateNavigationTransitionAsPrevious(nextView: ContentView, width: CGFloat, fraction: CGFloat, transition: ComponentTransition, completion: @escaping () -> Void) {
let alphaTransition: ComponentTransition = transition.animation.isImmediate ? .immediate : .easeInOut(duration: 0.3)
alphaTransition.setAlpha(view: self.leftButtonsContainer, alpha: pow(1.0 - fraction, 2.0))
alphaTransition.setBlur(layer: self.leftButtonsContainer.layer, radius: fraction * 10.0)
alphaTransition.setAlpha(view: self.rightButtonsContainer, alpha: pow(1.0 - fraction, 2.0))
alphaTransition.setBlur(layer: self.rightButtonsContainer.layer, radius: fraction * 10.0)
if let backButtonView = self.backButtonView {
transition.setBounds(view: backButtonView, bounds: CGRect(origin: CGPoint(x: fraction * self.bounds.width * 0.5, y: 0.0), size: backButtonView.bounds.size), completion: { _ in
@@ -406,80 +399,53 @@ public final class ChatListHeaderComponent: Component {
})
}
if let chatListTitleView = self.chatListTitleView, let nextBackButtonView = nextView.backButtonView {
let titleFrame = chatListTitleView.titleNode.view.convert(chatListTitleView.titleNode.bounds, to: self.titleOffsetContainer)
let backButtonTitleFrame = nextBackButtonView.convert(nextBackButtonView.titleView.frame, to: nextView)
let totalOffset = titleFrame.minX - backButtonTitleFrame.minX
transition.setBounds(view: self.titleOffsetContainer, bounds: CGRect(origin: CGPoint(x: totalOffset * fraction, y: 0.0), size: self.titleOffsetContainer.bounds.size))
transition.setAlpha(view: self.titleOffsetContainer, alpha: pow(1.0 - fraction, 2.0))
}
let totalOffset = -width * fraction
transition.setBounds(view: self.titleOffsetContainer, bounds: CGRect(origin: CGPoint(x: totalOffset * fraction, y: 0.0), size: self.titleOffsetContainer.bounds.size))
transition.setAlpha(view: self.titleOffsetContainer, alpha: pow(1.0 - fraction, 2.0))
}
func updateNavigationTransitionAsNext(previousView: ContentView, storyPeerListView: StoryPeerListComponent.View?, fraction: CGFloat, transition: ComponentTransition, completion: @escaping () -> Void) {
let alphaTransition: ComponentTransition = transition.animation.isImmediate ? .immediate : .easeInOut(duration: 0.3)
transition.setBounds(view: self.titleOffsetContainer, bounds: CGRect(origin: CGPoint(x: -(1.0 - fraction) * self.bounds.width, y: 0.0), size: self.titleOffsetContainer.bounds.size), completion: { _ in
completion()
})
transition.setAlpha(view: self.rightButtonOffsetContainer, alpha: pow(fraction, 2.0))
transition.setBounds(view: self.rightButtonOffsetContainer, bounds: CGRect(origin: CGPoint(x: -(1.0 - fraction) * self.bounds.width, y: 0.0), size: self.rightButtonOffsetContainer.bounds.size))
if let backButtonView = self.backButtonView {
transition.setScale(view: backButtonView.arrowView, scale: pow(max(0.001, fraction), 2.0))
transition.setAlpha(view: backButtonView.arrowView, alpha: pow(fraction, 2.0))
if let storyPeerListView {
let previousTitleFrame = storyPeerListView.titleFrame()
let backButtonTitleFrame = backButtonView.convert(backButtonView.titleView.frame, to: self)
let totalOffset = previousTitleFrame.minX - backButtonTitleFrame.minX
transition.setBounds(view: backButtonView.titleOffsetContainer, bounds: CGRect(origin: CGPoint(x: -totalOffset * (1.0 - fraction), y: 0.0), size: backButtonView.titleOffsetContainer.bounds.size))
transition.setAlpha(view: backButtonView.titleOffsetContainer, alpha: pow(fraction, 2.0))
} else if let previousChatListTitleView = previousView.chatListTitleView {
let previousTitleFrame = previousChatListTitleView.titleNode.view.convert(previousChatListTitleView.titleNode.bounds, to: previousView.titleOffsetContainer)
let backButtonTitleFrame = backButtonView.convert(backButtonView.titleView.frame, to: self)
let totalOffset = previousTitleFrame.minX - backButtonTitleFrame.minX
transition.setBounds(view: backButtonView.titleOffsetContainer, bounds: CGRect(origin: CGPoint(x: -totalOffset * (1.0 - fraction), y: 0.0), size: backButtonView.titleOffsetContainer.bounds.size))
transition.setAlpha(view: backButtonView.titleOffsetContainer, alpha: pow(fraction, 2.0))
}
}
alphaTransition.setAlpha(view: self.rightButtonsContainer, alpha: pow(fraction, 2.0))
alphaTransition.setBlur(layer: self.leftButtonsContainer.layer, radius: (1.0 - fraction) * 10.0)
alphaTransition.setBlur(layer: self.rightButtonsContainer.layer, radius: (1.0 - fraction) * 10.0)
}
func updateNavigationTransitionAsPreviousInplace(nextView: ContentView, fraction: CGFloat, transition: ComponentTransition, completion: @escaping () -> Void) {
transition.setBounds(view: self.leftButtonOffsetContainer, bounds: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: self.leftButtonOffsetContainer.bounds.size), completion: { _ in
})
transition.setAlpha(view: self.leftButtonOffsetContainer, alpha: pow(1.0 - fraction, 2.0))
transition.setAlpha(view: self.rightButtonOffsetContainer, alpha: pow(1.0 - fraction, 2.0), completion: { _ in
let alphaTransition: ComponentTransition = transition.animation.isImmediate ? .immediate : .easeInOut(duration: 0.3)
alphaTransition.setAlpha(view: self.leftButtonsContainer, alpha: pow(1.0 - fraction, 2.0))
alphaTransition.setBlur(layer: self.leftButtonsContainer.layer, radius: fraction * 10.0)
alphaTransition.setAlpha(view: self.rightButtonsContainer, alpha: pow(1.0 - fraction, 2.0), completion: { _ in
completion()
})
if let backButtonView = self.backButtonView {
transition.setBounds(view: backButtonView, bounds: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: backButtonView.bounds.size), completion: { _ in
})
}
alphaTransition.setBlur(layer: self.rightButtonsContainer.layer, radius: fraction * 10.0)
transition.setBounds(view: self.titleOffsetContainer, bounds: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: self.titleOffsetContainer.bounds.size))
transition.setAlpha(view: self.titleOffsetContainer, alpha: pow(1.0 - fraction, 2.0))
}
func updateNavigationTransitionAsNextInplace(previousView: ContentView, fraction: CGFloat, transition: ComponentTransition, completion: @escaping () -> Void) {
let alphaTransition: ComponentTransition = transition.animation.isImmediate ? .immediate : .easeInOut(duration: 0.3)
transition.setBounds(view: self.titleOffsetContainer, bounds: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: self.titleOffsetContainer.bounds.size), completion: { _ in
completion()
})
transition.setAlpha(view: self.rightButtonOffsetContainer, alpha: pow(fraction, 2.0))
transition.setBounds(view: self.rightButtonOffsetContainer, bounds: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: self.rightButtonOffsetContainer.bounds.size))
if let backButtonView = self.backButtonView {
transition.setScale(view: backButtonView.arrowView, scale: pow(max(0.001, fraction), 2.0))
transition.setAlpha(view: backButtonView.arrowView, alpha: pow(fraction, 2.0))
transition.setBounds(view: backButtonView.titleOffsetContainer, bounds: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: backButtonView.titleOffsetContainer.bounds.size))
transition.setAlpha(view: backButtonView.titleOffsetContainer, alpha: pow(fraction, 2.0))
}
alphaTransition.setBlur(layer: self.leftButtonsContainer.layer, radius: (1.0 - fraction) * 10.0)
alphaTransition.setAlpha(view: self.leftButtonsContainer, alpha: pow(fraction, 2.0))
alphaTransition.setBlur(layer: self.rightButtonsContainer.layer, radius: (1.0 - fraction) * 10.0)
alphaTransition.setAlpha(view: self.rightButtonsContainer, alpha: pow(fraction, 2.0))
}
func update(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, content: Content, backTitle: String?, sideInset: CGFloat, sideContentWidth: CGFloat, sideContentFraction: CGFloat, size: CGSize, transition: ComponentTransition) {
func update(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, content: Content, displayBackButton: Bool, sideInset: CGFloat, sideContentWidth: CGFloat, sideContentFraction: CGFloat, size: CGSize, transition: ComponentTransition) {
let alphaTransition: ComponentTransition = transition.animation.isImmediate ? .immediate : .easeInOut(duration: 0.3)
transition.setPosition(view: self.titleOffsetContainer, position: CGPoint(x: size.width * 0.5, y: size.height * 0.5))
transition.setBounds(view: self.titleOffsetContainer, bounds: CGRect(origin: self.titleOffsetContainer.bounds.origin, size: size))
@@ -490,11 +456,10 @@ public final class ChatListHeaderComponent: Component {
let titleTextUpdated = self.titleTextView.attributedText != titleText
self.titleTextView.attributedText = titleText
let buttonSpacing: CGFloat = 8.0
let buttonSpacing: CGFloat = 0.0
var nextLeftButtonX: CGFloat = 0.0
var leftOffset = sideInset
if let backTitle = backTitle {
if displayBackButton {
var backButtonTransition = transition
let backButtonView: BackButtonView
if let current = self.backButtonView {
@@ -508,11 +473,14 @@ public final class ChatListHeaderComponent: Component {
self.backPressed()
})
self.backButtonView = backButtonView
self.addSubview(backButtonView)
self.leftButtonsContainer.addSubview(backButtonView)
}
let backButtonSize = backButtonView.update(title: backTitle, theme: theme, availableSize: CGSize(width: 100.0, height: size.height), transition: backButtonTransition)
backButtonTransition.setFrame(view: backButtonView, frame: CGRect(origin: CGPoint(x: leftOffset, y: floor((size.height - backButtonSize.height) / 2.0)), size: backButtonSize))
leftOffset += backButtonSize.width + buttonSpacing
let backButtonSize = backButtonView.update(theme: theme, strings: strings, availableSize: CGSize(width: 100.0, height: size.height), transition: backButtonTransition)
backButtonTransition.setFrame(view: backButtonView, frame: CGRect(origin: CGPoint(x: nextLeftButtonX, y: floor((size.height - backButtonSize.height) / 2.0)), size: backButtonSize))
if nextLeftButtonX != 0.0 {
nextLeftButtonX += buttonSpacing
}
nextLeftButtonX += backButtonSize.width
} else if let backButtonView = self.backButtonView {
self.backButtonView = nil
backButtonView.removeFromSuperview()
@@ -521,6 +489,10 @@ public final class ChatListHeaderComponent: Component {
var validLeftButtons = Set<AnyHashable>()
if let leftButton = content.leftButton {
validLeftButtons.insert(leftButton.id)
if nextLeftButtonX != 0.0 {
nextLeftButtonX += buttonSpacing
}
var buttonTransition = transition
var animateButtonIn = false
@@ -541,23 +513,25 @@ public final class ChatListHeaderComponent: Component {
},
containerSize: CGSize(width: 100.0, height: size.height)
)
let buttonFrame = CGRect(origin: CGPoint(x: leftOffset, y: floor((size.height - buttonSize.height) / 2.0)), size: buttonSize)
let buttonFrame = CGRect(origin: CGPoint(x: nextLeftButtonX, y: floor((size.height - buttonSize.height) / 2.0)), size: buttonSize)
if let buttonComponentView = buttonView.view {
if buttonComponentView.superview == nil {
self.leftButtonOffsetContainer.addSubview(buttonComponentView)
self.leftButtonsContainer.addSubview(buttonComponentView)
}
buttonTransition.setFrame(view: buttonComponentView, frame: buttonFrame)
if animateButtonIn {
transition.animateAlpha(view: buttonComponentView, from: 0.0, to: 1.0)
alphaTransition.animateBlur(layer: buttonComponentView.layer, fromRadius: 10.0, toRadius: 0.0)
alphaTransition.animateAlpha(view: buttonComponentView, from: 0.0, to: 1.0)
}
}
leftOffset = buttonFrame.maxX + buttonSpacing
nextLeftButtonX += buttonSize.width
}
var removeLeftButtons: [AnyHashable] = []
for (id, buttonView) in self.leftButtonViews {
if !validLeftButtons.contains(id) {
if let buttonComponentView = buttonView.view {
transition.setAlpha(view: buttonComponentView, alpha: 0.0, completion: { [weak buttonComponentView] _ in
alphaTransition.setBlur(layer: buttonComponentView.layer, radius: 10.0)
alphaTransition.setAlpha(view: buttonComponentView, alpha: 0.0, completion: { [weak buttonComponentView] _ in
buttonComponentView?.removeFromSuperview()
})
}
@@ -568,10 +542,14 @@ public final class ChatListHeaderComponent: Component {
self.leftButtonViews.removeValue(forKey: id)
}
var rightOffset = size.width - sideInset
var nextRightButtonX: CGFloat = 0.0
var validRightButtons = Set<AnyHashable>()
for rightButton in content.rightButtons {
for rightButton in content.rightButtons.reversed() {
validRightButtons.insert(rightButton.id)
if nextRightButtonX != 0.0 {
nextRightButtonX += buttonSpacing
}
var buttonTransition = transition
var animateButtonIn = false
@@ -592,23 +570,25 @@ public final class ChatListHeaderComponent: Component {
},
containerSize: CGSize(width: 100.0, height: size.height)
)
let buttonFrame = CGRect(origin: CGPoint(x: rightOffset - buttonSize.width, y: floor((size.height - buttonSize.height) / 2.0)), size: buttonSize)
let buttonFrame = CGRect(origin: CGPoint(x: nextRightButtonX, y: floor((size.height - buttonSize.height) / 2.0)), size: buttonSize)
if let buttonComponentView = buttonView.view {
if buttonComponentView.superview == nil {
self.rightButtonOffsetContainer.addSubview(buttonComponentView)
self.rightButtonsContainer.addSubview(buttonComponentView)
}
buttonTransition.setFrame(view: buttonComponentView, frame: buttonFrame)
if animateButtonIn {
transition.animateAlpha(view: buttonComponentView, from: 0.0, to: 1.0)
alphaTransition.animateBlur(layer: buttonComponentView.layer, fromRadius: 10.0, toRadius: 0.0)
alphaTransition.animateAlpha(view: buttonComponentView, from: 0.0, to: 1.0)
}
}
rightOffset = buttonFrame.minX - buttonSpacing
nextRightButtonX += buttonSize.width
}
var removeRightButtons: [AnyHashable] = []
for (id, buttonView) in self.rightButtonViews {
if !validRightButtons.contains(id) {
if let buttonComponentView = buttonView.view {
transition.setAlpha(view: buttonComponentView, alpha: 0.0, completion: { [weak buttonComponentView] _ in
alphaTransition.setBlur(layer: buttonComponentView.layer, radius: 10.0)
alphaTransition.setAlpha(view: buttonComponentView, alpha: 0.0, completion: { [weak buttonComponentView] _ in
buttonComponentView?.removeFromSuperview()
})
}
@@ -618,8 +598,11 @@ public final class ChatListHeaderComponent: Component {
for id in removeRightButtons {
self.rightButtonViews.removeValue(forKey: id)
}
let commonInset: CGFloat = max(leftOffset, size.width - rightOffset)
self.leftButtonsWidth = nextLeftButtonX
self.rightButtonsWidth = nextRightButtonX
let commonInset: CGFloat = sideInset + max(nextLeftButtonX, nextRightButtonX) + 8.0
let remainingWidth = size.width - commonInset * 2.0
let titleTextSize = self.titleTextView.updateLayout(CGSize(width: remainingWidth, height: size.height))
@@ -662,10 +645,10 @@ public final class ChatListHeaderComponent: Component {
}
var centerContentLeftInset: CGFloat = 0.0
centerContentLeftInset = leftOffset - 4.0
centerContentLeftInset = nextLeftButtonX + 4.0
var centerContentRightInset: CGFloat = 0.0
centerContentRightInset = size.width - rightOffset - 8.0
centerContentRightInset = nextRightButtonX + 20.0
var centerContentWidth: CGFloat = 0.0
var centerContentOffsetX: CGFloat = 0.0
@@ -687,13 +670,11 @@ public final class ChatListHeaderComponent: Component {
chatListTitleView.theme = theme
chatListTitleView.strings = strings
chatListTitleView.setTitle(chatListTitle, animated: false)
let titleContentRect = chatListTitleView.updateLayout(size: chatListTitleContentSize, clearBounds: CGRect(origin: CGPoint(), size: chatListTitleContentSize), transition: transition.containedViewLayoutTransition)
let titleContentRect = chatListTitleView.updateLayoutInternal(size: chatListTitleContentSize, transition: transition.containedViewLayoutTransition)
centerContentWidth = floor((chatListTitleContentSize.width * 0.5 - titleContentRect.minX) * 2.0)
//sideWidth + centerWidth + centerOffset = size.width
//let centerOffset = -(size.width - (sideContentWidth + centerContentWidth)) * 0.5 + size.width * 0.5
let centerOffset = sideContentWidth * 0.5
centerContentOffsetX = -max(0.0, centerOffset + titleContentRect.maxX - 2.0 - rightOffset)
centerContentOffsetX = -max(0.0, centerOffset + titleContentRect.maxX - 2.0 - (size.width - sideInset - nextRightButtonX))
chatListTitleView.openStatusSetup = { [weak self] sourceView in
guard let self else {
@@ -734,13 +715,18 @@ public final class ChatListHeaderComponent: Component {
}
}
public final class View: UIView, NavigationBarHeaderView {
public final class View: UIView {
private var component: ChatListHeaderComponent?
private weak var state: EmptyComponentState?
private var primaryContentView: ContentView?
private var secondaryContentView: ContentView?
private var storyOffsetFraction: CGFloat = 0.0
private let leftButtonsContainer: UIView
private let rightButtonsContainer: UIView
private var leftButtonsBackgroundContainer: GlassBackgroundView?
private var rightButtonsBackgroundContainer: GlassBackgroundView?
private let storyPeerListExternalState = StoryPeerListComponent.ExternalState()
private var storyPeerList: ComponentView<Empty>?
@@ -753,6 +739,10 @@ public final class ChatListHeaderComponent: Component {
}
override init(frame: CGRect) {
self.leftButtonsContainer = UIView()
self.rightButtonsContainer = UIView()
self.rightButtonsContainer.layer.anchorPoint = CGPoint(x: 1.0, y: 0.0)
super.init(frame: frame)
self.storyOffsetFraction = 1.0
@@ -766,10 +756,6 @@ public final class ChatListHeaderComponent: Component {
return self.effectiveContentView?.backButtonView?.arrowView
}
public var backButtonTitleView: UIView? {
return self.effectiveContentView?.backButtonView?.titleView
}
public var rightButtonView: UIView? {
return self.effectiveContentView?.rightButtonViews.first?.value.view
}
@@ -784,29 +770,6 @@ public final class ChatListHeaderComponent: Component {
return self.effectiveContentView?.titleContentView?.view
}
public func makeTransitionBackArrowView(accentColor: UIColor) -> UIView? {
if let backArrowView = self.backArrowView {
let view = UIImageView()
view.image = NavigationBar.backArrowImage(color: accentColor)
view.frame = backArrowView.convert(backArrowView.bounds, to: self)
return view
} else {
return nil
}
}
public func makeTransitionBackButtonView(accentColor: UIColor) -> UIView? {
if let backButtonTitleView = self.backButtonTitleView as? ImmediateTextView {
let view = ImmediateTextView()
view.attributedText = NSAttributedString(string: backButtonTitleView.attributedText?.string ?? "", font: Font.regular(17.0), textColor: accentColor)
let _ = view.updateLayout(CGSize(width: 100.0, height: 100.0))
view.frame = backButtonTitleView.convert(backButtonTitleView.bounds, to: self)
return view
} else {
return nil
}
}
public func storyPeerListView() -> StoryPeerListComponent.View? {
return self.storyPeerList?.view as? StoryPeerListComponent.View
}
@@ -848,8 +811,8 @@ public final class ChatListHeaderComponent: Component {
let previousComponent = self.component
self.component = component
var primaryContentTransition = transition
if var primaryContent = component.primaryContent {
var primaryContentTransition = transition
let primaryContentView: ContentView
if let current = self.primaryContentView {
primaryContentView = current
@@ -877,6 +840,8 @@ public final class ChatListHeaderComponent: Component {
)
self.primaryContentView = primaryContentView
self.addSubview(primaryContentView)
self.leftButtonsContainer.addSubview(primaryContentView.leftButtonsContainer)
self.rightButtonsContainer.addSubview(primaryContentView.rightButtonsContainer)
}
let sideContentWidth: CGFloat = 0.0
@@ -889,18 +854,19 @@ public final class ChatListHeaderComponent: Component {
chatListTitle: nil,
leftButton: primaryContent.leftButton,
rightButtons: primaryContent.rightButtons,
backTitle: primaryContent.backTitle,
backPressed: primaryContent.backPressed
)
}
primaryContentView.update(context: component.context, theme: component.theme, strings: component.strings, content: primaryContent, backTitle: primaryContent.backTitle, sideInset: component.sideInset, sideContentWidth: sideContentWidth, sideContentFraction: (1.0 - component.storiesFraction), size: availableSize, transition: primaryContentTransition)
primaryContentView.update(context: component.context, theme: component.theme, strings: component.strings, content: primaryContent, displayBackButton: primaryContent.backPressed != nil, sideInset: component.sideInset, sideContentWidth: sideContentWidth, sideContentFraction: (1.0 - component.storiesFraction), size: availableSize, transition: primaryContentTransition)
primaryContentTransition.setFrame(view: primaryContentView, frame: CGRect(origin: CGPoint(), size: availableSize))
primaryContentView.updateContentOffsetFraction(contentOffsetFraction: 1.0 - self.storyOffsetFraction, transition: primaryContentTransition)
} else if let primaryContentView = self.primaryContentView {
self.primaryContentView = nil
primaryContentView.removeFromSuperview()
primaryContentView.leftButtonsContainer.removeFromSuperview()
primaryContentView.rightButtonsContainer.removeFromSuperview()
}
var storyListTransition = transition
@@ -991,13 +957,16 @@ public final class ChatListHeaderComponent: Component {
)
}
var secondaryContentTransition = transition
var secondaryContentIsAnimatingIn = false
var removedSecondaryContentView: ContentView?
if let secondaryContent = component.secondaryContent {
var secondaryContentTransition = transition
let secondaryContentView: ContentView
if let current = self.secondaryContentView {
secondaryContentView = current
} else {
secondaryContentTransition = .immediate
secondaryContentIsAnimatingIn = true
secondaryContentView = ContentView(
backPressed: { [weak self] in
guard let self, let component = self.component else {
@@ -1019,9 +988,15 @@ public final class ChatListHeaderComponent: Component {
}
)
self.secondaryContentView = secondaryContentView
self.addSubview(secondaryContentView)
if let primaryContentView = self.primaryContentView {
self.insertSubview(secondaryContentView, aboveSubview: primaryContentView)
} else {
self.addSubview(secondaryContentView)
}
self.leftButtonsContainer.addSubview(secondaryContentView.leftButtonsContainer)
self.rightButtonsContainer.addSubview(secondaryContentView.rightButtonsContainer)
}
secondaryContentView.update(context: component.context, theme: component.theme, strings: component.strings, content: secondaryContent, backTitle: component.primaryContent?.navigationBackTitle ?? component.primaryContent?.title, sideInset: component.sideInset, sideContentWidth: 0.0, sideContentFraction: 0.0, size: availableSize, transition: secondaryContentTransition)
secondaryContentView.update(context: component.context, theme: component.theme, strings: component.strings, content: secondaryContent, displayBackButton: true, sideInset: component.sideInset, sideContentWidth: 0.0, sideContentFraction: 0.0, size: availableSize, transition: secondaryContentTransition)
secondaryContentTransition.setFrame(view: secondaryContentView, frame: CGRect(origin: CGPoint(), size: availableSize))
secondaryContentView.updateContentOffsetFraction(contentOffsetFraction: 1.0 - self.storyOffsetFraction, transition: secondaryContentTransition)
@@ -1032,7 +1007,7 @@ public final class ChatListHeaderComponent: Component {
primaryContentView.updateNavigationTransitionAsPreviousInplace(nextView: secondaryContentView, fraction: 0.0, transition: .immediate, completion: {})
secondaryContentView.updateNavigationTransitionAsNextInplace(previousView: primaryContentView, fraction: 0.0, transition: .immediate, completion: {})
} else {
primaryContentView.updateNavigationTransitionAsPrevious(nextView: secondaryContentView, fraction: 0.0, transition: .immediate, completion: {})
primaryContentView.updateNavigationTransitionAsPrevious(nextView: secondaryContentView, width: availableSize.width, fraction: 0.0, transition: .immediate, completion: {})
secondaryContentView.updateNavigationTransitionAsNext(previousView: primaryContentView, storyPeerListView: self.storyPeerListView(), fraction: 0.0, transition: .immediate, completion: {})
}
}
@@ -1041,26 +1016,33 @@ public final class ChatListHeaderComponent: Component {
primaryContentView.updateNavigationTransitionAsPreviousInplace(nextView: secondaryContentView, fraction: component.secondaryTransition, transition: transition, completion: {})
secondaryContentView.updateNavigationTransitionAsNextInplace(previousView: primaryContentView, fraction: component.secondaryTransition, transition: transition, completion: {})
} else {
primaryContentView.updateNavigationTransitionAsPrevious(nextView: secondaryContentView, fraction: component.secondaryTransition, transition: transition, completion: {})
primaryContentView.updateNavigationTransitionAsPrevious(nextView: secondaryContentView, width: availableSize.width, fraction: component.secondaryTransition, transition: transition, completion: {})
secondaryContentView.updateNavigationTransitionAsNext(previousView: primaryContentView, storyPeerListView: self.storyPeerListView(), fraction: component.secondaryTransition, transition: transition, completion: {})
}
}
} else if let secondaryContentView = self.secondaryContentView {
self.secondaryContentView = nil
removedSecondaryContentView = secondaryContentView
if let primaryContentView = self.primaryContentView {
if self.storyOffsetFraction < 0.8 {
primaryContentView.updateNavigationTransitionAsPreviousInplace(nextView: secondaryContentView, fraction: 0.0, transition: transition, completion: {})
secondaryContentView.updateNavigationTransitionAsNextInplace(previousView: primaryContentView, fraction: 0.0, transition: transition, completion: { [weak secondaryContentView] in
secondaryContentView?.leftButtonsContainer.removeFromSuperview()
secondaryContentView?.rightButtonsContainer.removeFromSuperview()
secondaryContentView?.removeFromSuperview()
})
} else {
primaryContentView.updateNavigationTransitionAsPrevious(nextView: secondaryContentView, fraction: 0.0, transition: transition, completion: {})
primaryContentView.updateNavigationTransitionAsPrevious(nextView: secondaryContentView, width: availableSize.width, fraction: 0.0, transition: transition, completion: {})
secondaryContentView.updateNavigationTransitionAsNext(previousView: primaryContentView, storyPeerListView: self.storyPeerListView(), fraction: 0.0, transition: transition, completion: { [weak secondaryContentView] in
secondaryContentView?.leftButtonsContainer.removeFromSuperview()
secondaryContentView?.rightButtonsContainer.removeFromSuperview()
secondaryContentView?.removeFromSuperview()
})
}
} else {
secondaryContentView.leftButtonsContainer.removeFromSuperview()
secondaryContentView.rightButtonsContainer.removeFromSuperview()
secondaryContentView.removeFromSuperview()
}
}
@@ -1070,18 +1052,10 @@ public final class ChatListHeaderComponent: Component {
self.addSubview(storyPeerListComponentView)
}
//let storyPeerListMinOffset: CGFloat = -7.0
let storyPeerListMaxOffset: CGFloat = availableSize.height + 2.0
//let storyPeerListPosition: CGFloat = storyPeerListMinOffset * (1.0 - component.storiesFraction) + storyPeerListMaxOffset * component.storiesFraction
var storiesX: CGFloat = 0.0
if let nextBackButtonView = self.secondaryContentView?.backButtonView {
let backButtonTitleFrame = nextBackButtonView.convert(nextBackButtonView.titleView.frame, to: self)
let storyListTitleFrame = storyPeerListComponentView.titleFrame()
storiesX += (backButtonTitleFrame.minX - storyListTitleFrame.minX) * component.secondaryTransition
}
storiesX -= availableSize.width * component.secondaryTransition
storyListTransition.setFrame(view: storyPeerListComponentView, frame: CGRect(origin: CGPoint(x: storiesX, y: storyPeerListMaxOffset), size: CGSize(width: availableSize.width, height: 79.0)))
@@ -1090,6 +1064,90 @@ public final class ChatListHeaderComponent: Component {
let storyListAlpha: CGFloat = (1.0 - component.secondaryTransition) * storyListNormalAlpha
storyListTransition.setAlpha(view: storyPeerListComponentView, alpha: storyListAlpha)
}
var leftButtonsEffectiveWidth: CGFloat = 0.0
var rightButtonsEffectiveWidth: CGFloat = 0.0
if let primaryContentView = self.primaryContentView, let secondaryContentView = self.secondaryContentView {
leftButtonsEffectiveWidth = primaryContentView.leftButtonsWidth * (1.0 - component.secondaryTransition) + secondaryContentView.leftButtonsWidth * component.secondaryTransition
rightButtonsEffectiveWidth = primaryContentView.rightButtonsWidth * (1.0 - component.secondaryTransition) + secondaryContentView.rightButtonsWidth * component.secondaryTransition
primaryContentTransition.setFrame(view: primaryContentView.leftButtonsContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: max(44.0, primaryContentView.leftButtonsWidth), height: 44.0)))
secondaryContentTransition.setFrame(view: secondaryContentView.leftButtonsContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: max(44.0, secondaryContentView.leftButtonsWidth), height: 44.0)))
primaryContentTransition.setFrame(view: primaryContentView.rightButtonsContainer, frame: CGRect(origin: CGPoint(x: rightButtonsEffectiveWidth - primaryContentView.rightButtonsWidth, y: 0.0), size: CGSize(width: max(44.0, primaryContentView.rightButtonsWidth), height: 44.0)))
if secondaryContentIsAnimatingIn {
secondaryContentView.rightButtonsContainer.frame = CGRect(origin: CGPoint(x: self.rightButtonsContainer.bounds.width - secondaryContentView.rightButtonsWidth, y: 0.0), size: CGSize(width: max(44.0, secondaryContentView.rightButtonsWidth), height: 44.0))
}
transition.setFrame(view: secondaryContentView.rightButtonsContainer, frame: CGRect(origin: CGPoint(x: rightButtonsEffectiveWidth - secondaryContentView.rightButtonsWidth, y: 0.0), size: CGSize(width: max(44.0, secondaryContentView.rightButtonsWidth), height: 44.0)))
} else if let primaryContentView = self.primaryContentView {
leftButtonsEffectiveWidth = primaryContentView.leftButtonsWidth
rightButtonsEffectiveWidth = primaryContentView.rightButtonsWidth
primaryContentTransition.setFrame(view: primaryContentView.leftButtonsContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: max(44.0, primaryContentView.leftButtonsWidth), height: 44.0)))
primaryContentTransition.setFrame(view: primaryContentView.rightButtonsContainer, frame: CGRect(origin: CGPoint(x: rightButtonsEffectiveWidth - primaryContentView.rightButtonsWidth, y: 0.0), size: CGSize(width: max(44.0, primaryContentView.rightButtonsWidth), height: 44.0)))
if let removedSecondaryContentView {
transition.setFrame(view: removedSecondaryContentView.rightButtonsContainer, frame: CGRect(origin: CGPoint(x: rightButtonsEffectiveWidth - removedSecondaryContentView.rightButtonsWidth, y: 0.0), size: CGSize(width: max(44.0, removedSecondaryContentView.rightButtonsWidth), height: 44.0)))
}
}
if leftButtonsEffectiveWidth != 0.0 {
let leftButtonsBackgroundContainer: GlassBackgroundView
var leftButtonsBackgroundContainerTransition = transition
if let current = self.leftButtonsBackgroundContainer {
leftButtonsBackgroundContainer = current
} else {
leftButtonsBackgroundContainerTransition = leftButtonsBackgroundContainerTransition.withAnimation(.none)
leftButtonsBackgroundContainer = GlassBackgroundView()
self.leftButtonsBackgroundContainer = leftButtonsBackgroundContainer
self.addSubview(leftButtonsBackgroundContainer)
leftButtonsBackgroundContainer.contentView.addSubview(self.leftButtonsContainer)
}
let leftButtonsContainerFrame = CGRect(origin: CGPoint(x: component.sideInset, y: 0.0), size: CGSize(width: max(44.0, leftButtonsEffectiveWidth), height: 44.0))
leftButtonsBackgroundContainerTransition.setFrame(view: leftButtonsBackgroundContainer, frame: leftButtonsContainerFrame)
leftButtonsBackgroundContainer.update(size: leftButtonsContainerFrame.size, cornerRadius: leftButtonsContainerFrame.height * 0.5, isDark: component.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: component.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: leftButtonsBackgroundContainerTransition)
leftButtonsBackgroundContainerTransition.setFrame(view: self.leftButtonsContainer, frame: CGRect(origin: CGPoint(), size: leftButtonsContainerFrame.size))
} else {
if let leftButtonsBackgroundContainer = self.leftButtonsBackgroundContainer {
self.leftButtonsBackgroundContainer = nil
transition.setAlpha(view: leftButtonsBackgroundContainer, alpha: 0.0, completion: { [weak leftButtonsBackgroundContainer] _ in
leftButtonsBackgroundContainer?.removeFromSuperview()
})
}
}
if rightButtonsEffectiveWidth != 0.0 {
let rightButtonsBackgroundContainer: GlassBackgroundView
var rightButtonsBackgroundContainerTransition = transition
let rightButtonsContainerFrame = CGRect(origin: CGPoint(x: availableSize.width - component.sideInset - max(44.0, rightButtonsEffectiveWidth), y: 0.0), size: CGSize(width: max(44.0, rightButtonsEffectiveWidth), height: 44.0))
if let current = self.rightButtonsBackgroundContainer {
rightButtonsBackgroundContainer = current
} else {
rightButtonsBackgroundContainerTransition = rightButtonsBackgroundContainerTransition.withAnimation(.none)
rightButtonsBackgroundContainer = GlassBackgroundView()
self.rightButtonsBackgroundContainer = rightButtonsBackgroundContainer
self.addSubview(rightButtonsBackgroundContainer)
rightButtonsBackgroundContainer.contentView.addSubview(self.rightButtonsContainer)
rightButtonsBackgroundContainer.update(size: rightButtonsContainerFrame.size, cornerRadius: rightButtonsContainerFrame.height * 0.5, isDark: component.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: component.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, isVisible: false, transition: .immediate)
}
rightButtonsBackgroundContainerTransition.setFrame(view: rightButtonsBackgroundContainer, frame: rightButtonsContainerFrame)
rightButtonsBackgroundContainer.update(size: rightButtonsContainerFrame.size, cornerRadius: rightButtonsContainerFrame.height * 0.5, isDark: component.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: component.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: transition)
rightButtonsBackgroundContainerTransition.setFrame(view: self.rightButtonsContainer, frame: CGRect(origin: CGPoint(), size: rightButtonsContainerFrame.size))
} else {
if let rightButtonsBackgroundContainer = self.rightButtonsBackgroundContainer {
self.rightButtonsBackgroundContainer = nil
rightButtonsBackgroundContainer.update(size: rightButtonsBackgroundContainer.bounds.size, cornerRadius: rightButtonsBackgroundContainer.bounds.height * 0.5, isDark: component.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: component.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, isVisible: false, transition: transition)
transition.attachAnimation(view: rightButtonsBackgroundContainer, id: "remove", completion: { [weak rightButtonsBackgroundContainer] _ in
rightButtonsBackgroundContainer?.removeFromSuperview()
})
}
}
return availableSize
}
@@ -1119,251 +1177,3 @@ public final class ChatListHeaderComponent: Component {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}
public final class NavigationButtonComponentEnvironment: Equatable {
public let theme: PresentationTheme
public init(theme: PresentationTheme) {
self.theme = theme
}
public static func ==(lhs: NavigationButtonComponentEnvironment, rhs: NavigationButtonComponentEnvironment) -> Bool {
if lhs.theme != rhs.theme {
return false
}
return true
}
}
public final class NavigationButtonComponent: Component {
public typealias EnvironmentType = NavigationButtonComponentEnvironment
public enum Content: Equatable {
case text(title: String, isBold: Bool)
case more
case icon(imageName: String)
case proxy(status: ChatTitleProxyStatus)
}
public let content: Content
public let pressed: (UIView) -> Void
public let contextAction: ((UIView, ContextGesture?) -> Void)?
public init(
content: Content,
pressed: @escaping (UIView) -> Void,
contextAction: ((UIView, ContextGesture?) -> Void)? = nil
) {
self.content = content
self.pressed = pressed
self.contextAction = contextAction
}
public static func ==(lhs: NavigationButtonComponent, rhs: NavigationButtonComponent) -> Bool {
if lhs.content != rhs.content {
return false
}
return true
}
public final class View: HighlightTrackingButton {
private var textView: ImmediateTextView?
private var iconView: UIImageView?
private var iconImageName: String?
private var proxyNode: ChatTitleProxyNode?
private var moreButton: MoreHeaderButton?
private var component: NavigationButtonComponent?
private var theme: PresentationTheme?
override init(frame: CGRect) {
super.init(frame: frame)
self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
self.highligthedChanged = { [weak self] highlighted in
guard let self else {
return
}
if highlighted {
self.textView?.alpha = 0.6
self.proxyNode?.alpha = 0.6
self.iconView?.alpha = 0.6
} else {
self.textView?.alpha = 1.0
self.textView?.layer.animateAlpha(from: 0.6, to: 1.0, duration: 0.2)
self.proxyNode?.alpha = 1.0
self.proxyNode?.layer.animateAlpha(from: 0.6, to: 1.0, duration: 0.2)
self.iconView?.alpha = 1.0
self.iconView?.layer.animateAlpha(from: 0.6, to: 1.0, duration: 0.2)
}
}
}
required public init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc private func pressed() {
self.component?.pressed(self)
}
func update(component: NavigationButtonComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<NavigationButtonComponentEnvironment>, transition: ComponentTransition) -> CGSize {
self.component = component
let theme = environment[NavigationButtonComponentEnvironment.self].value.theme
var themeUpdated = false
if self.theme !== theme {
self.theme = theme
themeUpdated = true
}
let iconOffset: CGFloat = 4.0
var textString: NSAttributedString?
var imageName: String?
var proxyStatus: ChatTitleProxyStatus?
var isMore: Bool = false
switch component.content {
case let .text(title, isBold):
textString = NSAttributedString(string: title, font: isBold ? Font.bold(17.0) : Font.regular(17.0), textColor: theme.rootController.navigationBar.accentTextColor)
case .more:
isMore = true
case let .icon(imageNameValue):
imageName = imageNameValue
case let .proxy(status):
proxyStatus = status
}
var size = CGSize(width: 0.0, height: availableSize.height)
if let textString = textString {
let textView: ImmediateTextView
if let current = self.textView {
textView = current
} else {
textView = ImmediateTextView()
textView.isUserInteractionEnabled = false
self.textView = textView
self.addSubview(textView)
}
textView.attributedText = textString
let textSize = textView.updateLayout(availableSize)
size.width = textSize.width
textView.frame = CGRect(origin: CGPoint(x: 0.0, y: floor((availableSize.height - textSize.height) / 2.0)), size: textSize)
} else if let textView = self.textView {
self.textView = nil
textView.removeFromSuperview()
}
if let imageName = imageName {
let iconView: UIImageView
if let current = self.iconView {
iconView = current
} else {
iconView = UIImageView()
iconView.isUserInteractionEnabled = false
self.iconView = iconView
self.addSubview(iconView)
}
if self.iconImageName != imageName || themeUpdated {
self.iconImageName = imageName
iconView.image = generateTintedImage(image: UIImage(bundleImageName: imageName), color: theme.rootController.navigationBar.accentTextColor)
}
if let iconSize = iconView.image?.size {
size.width = iconSize.width
iconView.frame = CGRect(origin: CGPoint(x: iconOffset, y: floor((availableSize.height - iconSize.height) / 2.0)), size: iconSize)
}
} else if let iconView = self.iconView {
self.iconView = nil
iconView.removeFromSuperview()
self.iconImageName = nil
}
if let proxyStatus = proxyStatus {
let proxyNode: ChatTitleProxyNode
if let current = self.proxyNode {
proxyNode = current
} else {
proxyNode = ChatTitleProxyNode(theme: theme)
proxyNode.isUserInteractionEnabled = false
self.proxyNode = proxyNode
self.addSubnode(proxyNode)
}
let proxySize = CGSize(width: 30.0, height: 30.0)
size.width = proxySize.width
proxyNode.theme = theme
proxyNode.status = proxyStatus
proxyNode.frame = CGRect(origin: CGPoint(x: iconOffset, y: floor((availableSize.height - proxySize.height) / 2.0)), size: proxySize)
} else if let proxyNode = self.proxyNode {
self.proxyNode = nil
proxyNode.removeFromSupernode()
}
if isMore {
let moreButton: MoreHeaderButton
if let current = self.moreButton, !themeUpdated {
moreButton = current
} else {
if let moreButton = self.moreButton {
moreButton.removeFromSupernode()
self.moreButton = nil
}
moreButton = MoreHeaderButton(color: theme.rootController.navigationBar.buttonColor)
moreButton.isUserInteractionEnabled = true
moreButton.setContent(.more(MoreHeaderButton.optionsCircleImage(color: theme.rootController.navigationBar.buttonColor)))
moreButton.onPressed = { [weak self] in
guard let self, let component = self.component else {
return
}
self.moreButton?.play()
component.pressed(self)
}
moreButton.contextAction = { [weak self] sourceNode, gesture in
guard let self, let component = self.component else {
return
}
self.moreButton?.play()
component.contextAction?(self, gesture)
}
self.moreButton = moreButton
self.addSubnode(moreButton)
}
let buttonSize = CGSize(width: 26.0, height: 44.0)
size.width = buttonSize.width
moreButton.setContent(.more(MoreHeaderButton.optionsCircleImage(color: theme.rootController.navigationBar.buttonColor)))
moreButton.frame = CGRect(origin: CGPoint(x: iconOffset, y: floor((availableSize.height - buttonSize.height) / 2.0)), size: buttonSize)
} else if let moreButton = self.moreButton {
self.moreButton = nil
moreButton.removeFromSupernode()
}
return size
}
}
public func makeView() -> View {
return View(frame: CGRect())
}
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<NavigationButtonComponentEnvironment>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}
@@ -9,6 +9,16 @@ import SearchUI
import AccountContext
import TelegramCore
import StoryPeerListComponent
import EdgeEffect
import GlassBackgroundComponent
private func searchScrollHeightValue() -> CGFloat {
return 54.0
}
private func storiesHeightValue() -> CGFloat {
return 96.0
}
public final class ChatListNavigationBar: Component {
public final class AnimationHint {
@@ -20,20 +30,37 @@ public final class ChatListNavigationBar: Component {
self.crossfadeStoryPeers = crossfadeStoryPeers
}
}
public struct Search: Equatable {
public var isEnabled: Bool
public init(isEnabled: Bool) {
self.isEnabled = isEnabled
}
}
public struct ActiveSearch: Equatable {
public var isExternal: Bool
public init(isExternal: Bool) {
self.isExternal = isExternal
}
}
public let context: AccountContext
public let theme: PresentationTheme
public let strings: PresentationStrings
public let statusBarHeight: CGFloat
public let sideInset: CGFloat
public let isSearchActive: Bool
public let isSearchEnabled: Bool
public let search: Search?
public let activeSearch: ActiveSearch?
public let primaryContent: ChatListHeaderComponent.Content?
public let secondaryContent: ChatListHeaderComponent.Content?
public let secondaryTransition: CGFloat
public let storySubscriptions: EngineStorySubscriptions?
public let storiesIncludeHidden: Bool
public let uploadProgress: [EnginePeer.Id: Float]
public let headerPanels: AnyComponent<Empty>?
public let tabsNode: ASDisplayNode?
public let tabsNodeIsSearch: Bool
public let accessoryPanelContainer: ASDisplayNode?
@@ -48,14 +75,15 @@ public final class ChatListNavigationBar: Component {
strings: PresentationStrings,
statusBarHeight: CGFloat,
sideInset: CGFloat,
isSearchActive: Bool,
isSearchEnabled: Bool,
search: Search?,
activeSearch: ActiveSearch?,
primaryContent: ChatListHeaderComponent.Content?,
secondaryContent: ChatListHeaderComponent.Content?,
secondaryTransition: CGFloat,
storySubscriptions: EngineStorySubscriptions?,
storiesIncludeHidden: Bool,
uploadProgress: [EnginePeer.Id: Float],
headerPanels: AnyComponent<Empty>?,
tabsNode: ASDisplayNode?,
tabsNodeIsSearch: Bool,
accessoryPanelContainer: ASDisplayNode?,
@@ -69,14 +97,15 @@ public final class ChatListNavigationBar: Component {
self.strings = strings
self.statusBarHeight = statusBarHeight
self.sideInset = sideInset
self.isSearchActive = isSearchActive
self.isSearchEnabled = isSearchEnabled
self.search = search
self.activeSearch = activeSearch
self.primaryContent = primaryContent
self.secondaryContent = secondaryContent
self.secondaryTransition = secondaryTransition
self.storySubscriptions = storySubscriptions
self.storiesIncludeHidden = storiesIncludeHidden
self.uploadProgress = uploadProgress
self.headerPanels = headerPanels
self.tabsNode = tabsNode
self.tabsNodeIsSearch = tabsNodeIsSearch
self.accessoryPanelContainer = accessoryPanelContainer
@@ -102,10 +131,10 @@ public final class ChatListNavigationBar: Component {
if lhs.sideInset != rhs.sideInset {
return false
}
if lhs.isSearchActive != rhs.isSearchActive {
if lhs.search != rhs.search {
return false
}
if lhs.isSearchEnabled != rhs.isSearchEnabled {
if lhs.activeSearch != rhs.activeSearch {
return false
}
if lhs.primaryContent != rhs.primaryContent {
@@ -126,6 +155,9 @@ public final class ChatListNavigationBar: Component {
if lhs.uploadProgress != rhs.uploadProgress {
return false
}
if lhs.headerPanels != rhs.headerPanels {
return false
}
if lhs.tabsNode !== rhs.tabsNode {
return false
}
@@ -149,15 +181,13 @@ public final class ChatListNavigationBar: Component {
}
}
public static let searchScrollHeight: CGFloat = 52.0
public static let storiesScrollHeight: CGFloat = {
return 83.0
}()
public static let searchScrollHeight: CGFloat = searchScrollHeightValue()
public static let storiesScrollHeight: CGFloat = storiesHeightValue()
public final class View: UIView {
private let backgroundView: BlurredBackgroundView
private let separatorLayer: SimpleLayer
private let edgeEffectView: EdgeEffectView
private let headerBackgroundContainer: GlassBackgroundContainerView
public let headerContent = ComponentView<Empty>()
public private(set) var searchContentNode: NavigationBarSearchContentNode?
@@ -178,23 +208,36 @@ public final class ChatListNavigationBar: Component {
public private(set) var storiesUnlocked: Bool = false
private let bottomContentsContainer: UIView
private var tabsNode: ASDisplayNode?
private var tabsNodeIsSearch: Bool = false
private weak var disappearingTabsView: UIView?
private var disappearingTabsViewSearch: Bool = false
private var headerPanelsView: ComponentView<Empty>?
private var disappearingHeaderPanels: ComponentView<Empty>?
public var headerPanels: UIView? {
return self.headerPanelsView?.view
}
private var currentHeaderComponent: ChatListHeaderComponent?
private var currentHeight: CGFloat = 0.0
override public init(frame: CGRect) {
self.backgroundView = BlurredBackgroundView(color: .clear, enableBlur: true)
self.backgroundView.layer.anchorPoint = CGPoint(x: 0.0, y: 1.0)
self.separatorLayer = SimpleLayer()
self.separatorLayer.anchorPoint = CGPoint()
self.edgeEffectView = EdgeEffectView()
self.headerBackgroundContainer = GlassBackgroundContainerView()
self.headerBackgroundContainer.layer.anchorPoint = CGPoint()
self.bottomContentsContainer = UIView()
self.bottomContentsContainer.layer.anchorPoint = CGPoint()
super.init(frame: frame)
self.addSubview(self.backgroundView)
self.layer.addSublayer(self.separatorLayer)
self.addSubview(self.edgeEffectView)
self.addSubview(self.bottomContentsContainer)
}
required init?(coder: NSCoder) {
@@ -202,7 +245,7 @@ public final class ChatListNavigationBar: Component {
}
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if !self.backgroundView.frame.contains(point) {
if point.y >= self.currentHeight {
return nil
}
@@ -248,7 +291,12 @@ public final class ChatListNavigationBar: Component {
let searchOffsetDistance: CGFloat = ChatListNavigationBar.searchScrollHeight
let minContentOffset: CGFloat = ChatListNavigationBar.searchScrollHeight
let minContentOffset: CGFloat
if component.search != nil {
minContentOffset = ChatListNavigationBar.searchScrollHeight
} else {
minContentOffset = 0.0
}
let clippedScrollOffset = min(minContentOffset, offset)
if self.clippedScrollOffset == clippedScrollOffset && !self.hasDeferredScrollOffset && !forceUpdate && !allowAvatarsExpansionUpdated {
@@ -259,71 +307,105 @@ public final class ChatListNavigationBar: Component {
let visibleSize = CGSize(width: currentLayout.size.width, height: max(0.0, currentLayout.size.height - clippedScrollOffset))
let previousHeight = self.separatorLayer.position.y
let previousHeight = self.currentHeight
self.currentHeight = visibleSize.height
self.backgroundView.update(size: CGSize(width: visibleSize.width, height: 1000.0), transition: transition.containedViewLayoutTransition)
transition.setBounds(view: self.backgroundView, bounds: CGRect(origin: CGPoint(), size: CGSize(width: visibleSize.width, height: 1000.0)))
transition.animatePosition(view: self.backgroundView, from: CGPoint(x: 0.0, y: -visibleSize.height + self.backgroundView.layer.position.y), to: CGPoint(), additive: true)
self.backgroundView.layer.position = CGPoint(x: 0.0, y: visibleSize.height)
transition.setFrameWithAdditivePosition(layer: self.separatorLayer, frame: CGRect(origin: CGPoint(x: 0.0, y: visibleSize.height), size: CGSize(width: visibleSize.width, height: UIScreenPixel)))
let searchContentNode: NavigationBarSearchContentNode
if let current = self.searchContentNode {
searchContentNode = current
if themeUpdated {
var embeddedSearchBarExpansionHeight: CGFloat = 0.0
var searchFrameValue: CGRect?
if let search = component.search {
let searchContentNode: NavigationBarSearchContentNode
if let current = self.searchContentNode {
searchContentNode = current
if themeUpdated {
let placeholder: String
let compactPlaceholder: String
placeholder = component.strings.Common_Search
compactPlaceholder = component.strings.Common_Search
searchContentNode.updateThemeAndPlaceholder(theme: component.theme, placeholder: placeholder, compactPlaceholder: compactPlaceholder)
}
} else {
let placeholder: String
let compactPlaceholder: String
placeholder = component.strings.Common_Search
compactPlaceholder = component.strings.Common_Search
searchContentNode.updateThemeAndPlaceholder(theme: component.theme, placeholder: placeholder, compactPlaceholder: compactPlaceholder)
}
} else {
let placeholder: String
let compactPlaceholder: String
placeholder = component.strings.Common_Search
compactPlaceholder = component.strings.Common_Search
searchContentNode = NavigationBarSearchContentNode(
theme: component.theme,
placeholder: placeholder,
compactPlaceholder: compactPlaceholder,
activate: { [weak self] in
guard let self, let component = self.component, let searchContentNode = self.searchContentNode else {
return
searchContentNode = NavigationBarSearchContentNode(
theme: component.theme,
placeholder: placeholder,
compactPlaceholder: compactPlaceholder,
activate: { [weak self] in
guard let self, let component = self.component, let searchContentNode = self.searchContentNode else {
return
}
component.activateSearch(searchContentNode)
}
component.activateSearch(searchContentNode)
)
searchContentNode.view.layer.anchorPoint = CGPoint()
self.searchContentNode = searchContentNode
self.addSubview(searchContentNode.view)
}
let searchSize = CGSize(width: currentLayout.size.width, height: navigationBarSearchContentHeight)
var searchFrame = CGRect(origin: CGPoint(x: 0.0, y: visibleSize.height - searchSize.height - self.bottomContentsContainer.bounds.height - 2.0), size: searchSize)
if let activeSearch = component.activeSearch, !activeSearch.isExternal {
searchFrame.origin.y = component.statusBarHeight + 8.0
}
if component.tabsNode != nil {
searchFrame.origin.y -= 40.0
}
if let activeSearch = component.activeSearch {
if !activeSearch.isExternal {
searchFrame.origin.y -= component.accessoryPanelContainerHeight
}
)
searchContentNode.view.layer.anchorPoint = CGPoint()
self.searchContentNode = searchContentNode
self.addSubview(searchContentNode.view)
} else {
searchFrame.origin.y -= component.accessoryPanelContainerHeight
}
let clippedSearchOffset = max(0.0, min(clippedScrollOffset, searchOffsetDistance))
let searchOffsetFraction = clippedSearchOffset / searchOffsetDistance
searchContentNode.expansionProgress = 1.0 - searchOffsetFraction
embeddedSearchBarExpansionHeight = 60.0 - floorToScreenPixels((1.0 - searchOffsetFraction) * searchSize.height)
if searchOffsetFraction > 0.0 {
searchFrame.origin.y -= (60.0 - 44.0) * 0.5 * searchOffsetFraction
}
searchFrameValue = searchFrame
transition.setFrameWithAdditivePosition(view: searchContentNode.view, frame: searchFrame)
let _ = searchContentNode.updateLayout(size: searchSize, leftInset: component.sideInset, rightInset: component.sideInset, transition: transition.containedViewLayoutTransition)
var searchAlpha: CGFloat = search.isEnabled ? 1.0 : 0.5
if let activeSearch = component.activeSearch, activeSearch.isExternal {
searchAlpha = 0.0
}
transition.setAlpha(view: searchContentNode.view, alpha: searchAlpha)
searchContentNode.isUserInteractionEnabled = search.isEnabled
} else {
if let searchContentNode = self.searchContentNode {
self.searchContentNode = nil
searchContentNode.view.removeFromSuperview()
}
}
let searchSize = CGSize(width: currentLayout.size.width - 6.0 * 2.0, height: navigationBarSearchContentHeight)
var searchFrame = CGRect(origin: CGPoint(x: 6.0, y: visibleSize.height - searchSize.height), size: searchSize)
if component.tabsNode != nil {
searchFrame.origin.y -= 40.0
}
if !component.isSearchActive {
searchFrame.origin.y -= component.accessoryPanelContainerHeight
let edgeEffectHeight: CGFloat = currentLayout.size.height + 20.0
var edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: currentLayout.size.width, height: edgeEffectHeight))
if component.search != nil {
if component.activeSearch != nil {
edgeEffectFrame.origin.y -= 16.0
} else {
edgeEffectFrame.origin.y -= embeddedSearchBarExpansionHeight
}
} else if component.activeSearch != nil {
edgeEffectFrame.origin.y -= 16.0
}
transition.setFrame(view: self.edgeEffectView, frame: edgeEffectFrame)
self.edgeEffectView.update(content: component.theme.list.plainBackgroundColor, blur: true, alpha: 0.55, rect: edgeEffectFrame, edge: .top, edgeSize: 40.0, transition: transition)
let clippedSearchOffset = max(0.0, min(clippedScrollOffset, searchOffsetDistance))
let searchOffsetFraction = clippedSearchOffset / searchOffsetDistance
searchContentNode.expansionProgress = 1.0 - searchOffsetFraction
transition.setFrameWithAdditivePosition(view: searchContentNode.view, frame: searchFrame)
searchContentNode.updateLayout(size: searchSize, leftInset: component.sideInset, rightInset: component.sideInset, transition: transition.containedViewLayoutTransition)
transition.setAlpha(view: searchContentNode.view, alpha: component.isSearchEnabled ? 1.0 : 0.5)
searchContentNode.isUserInteractionEnabled = component.isSearchEnabled
let headerTransition = transition
@@ -405,28 +487,31 @@ public final class ChatListNavigationBar: Component {
containerSize: CGSize(width: currentLayout.size.width, height: 44.0)
)
let headerContentY: CGFloat
if component.isSearchActive {
if component.activeSearch != nil {
headerContentY = -headerContentSize.height
} else {
if component.statusBarHeight < 1.0 {
headerContentY = 0.0
} else {
headerContentY = component.statusBarHeight + 5.0
headerContentY = component.statusBarHeight + 10.0
}
}
let headerContentFrame = CGRect(origin: CGPoint(x: 0.0, y: headerContentY), size: headerContentSize)
if let headerContentView = self.headerContent.view {
if headerContentView.superview == nil {
headerContentView.layer.anchorPoint = CGPoint()
self.addSubview(headerContentView)
self.addSubview(self.headerBackgroundContainer)
self.headerBackgroundContainer.contentView.addSubview(headerContentView)
}
transition.setFrameWithAdditivePosition(view: headerContentView, frame: headerContentFrame)
transition.setFrameWithAdditivePosition(view: self.headerBackgroundContainer, frame: headerContentFrame)
self.headerBackgroundContainer.update(size: headerContentFrame.size, isDark: component.theme.overallDarkAppearance, transition: transition)
transition.setFrameWithAdditivePosition(view: headerContentView, frame: CGRect(origin: CGPoint(), size: headerContentFrame.size))
if component.isSearchActive != (headerContentView.alpha == 0.0) {
headerContentView.alpha = component.isSearchActive ? 0.0 : 1.0
if (component.activeSearch != nil) != (headerContentView.alpha == 0.0) {
headerContentView.alpha = component.activeSearch != nil ? 0.0 : 1.0
if !transition.animation.isImmediate {
if component.isSearchActive {
if component.activeSearch != nil {
headerContentView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.14)
} else {
headerContentView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
@@ -435,6 +520,18 @@ public final class ChatListNavigationBar: Component {
}
}
let bottomContentsContainerPosition: CGPoint
if let activeSearch = component.activeSearch {
if let searchFrameValue, !activeSearch.isExternal {
bottomContentsContainerPosition = CGPoint(x: 0.0, y: searchFrameValue.maxY - 8.0)
} else {
bottomContentsContainerPosition = CGPoint(x: 0.0, y: -self.bottomContentsContainer.bounds.height)
}
} else {
bottomContentsContainerPosition = CGPoint(x: 0.0, y: visibleSize.height - self.bottomContentsContainer.bounds.height)
}
transition.setPosition(view: self.bottomContentsContainer, position: bottomContentsContainerPosition)
if component.tabsNode !== self.tabsNode {
if let tabsNode = self.tabsNode {
tabsNode.layer.anchorPoint = CGPoint()
@@ -455,7 +552,8 @@ public final class ChatListNavigationBar: Component {
}
var tabsFrame = CGRect(origin: CGPoint(x: 0.0, y: visibleSize.height), size: CGSize(width: visibleSize.width, height: 46.0))
if !component.isSearchActive {
if component.activeSearch != nil {
} else {
tabsFrame.origin.y -= component.accessoryPanelContainerHeight
}
if component.tabsNode != nil {
@@ -463,7 +561,8 @@ public final class ChatListNavigationBar: Component {
}
var accessoryPanelContainerFrame = CGRect(origin: CGPoint(x: 0.0, y: visibleSize.height), size: CGSize(width: visibleSize.width, height: component.accessoryPanelContainerHeight))
if !component.isSearchActive {
if component.activeSearch != nil {
} else {
accessoryPanelContainerFrame.origin.y -= component.accessoryPanelContainerHeight
}
@@ -528,14 +627,15 @@ public final class ChatListNavigationBar: Component {
strings: component.strings,
statusBarHeight: component.statusBarHeight,
sideInset: component.sideInset,
isSearchActive: component.isSearchActive,
isSearchEnabled: component.isSearchEnabled,
search: component.search,
activeSearch: component.activeSearch,
primaryContent: component.primaryContent,
secondaryContent: component.secondaryContent,
secondaryTransition: component.secondaryTransition,
storySubscriptions: component.storySubscriptions,
storiesIncludeHidden: component.storiesIncludeHidden,
uploadProgress: storyUploadProgress,
headerPanels: component.headerPanels,
tabsNode: component.tabsNode,
tabsNodeIsSearch: component.tabsNodeIsSearch,
accessoryPanelContainer: component.accessoryPanelContainer,
@@ -575,8 +675,6 @@ public final class ChatListNavigationBar: Component {
}
func update(component: ChatListNavigationBar, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
let themeUpdated = self.component?.theme !== component.theme
var uploadProgressUpdated = false
var storySubscriptionsUpdated = false
if let previousComponent = self.component {
@@ -591,32 +689,85 @@ public final class ChatListNavigationBar: Component {
self.component = component
self.state = state
if themeUpdated {
self.backgroundView.updateColor(color: component.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate)
self.separatorLayer.backgroundColor = component.theme.rootController.navigationBar.separatorColor.cgColor
}
var contentHeight = component.statusBarHeight
if component.statusBarHeight >= 1.0 {
contentHeight += 3.0
}
contentHeight += 44.0
if component.isSearchActive {
if component.statusBarHeight < 1.0 {
contentHeight += 8.0
if let activeSearch = component.activeSearch {
if !activeSearch.isExternal {
contentHeight += navigationBarSearchContentHeight
}
} else {
contentHeight += navigationBarSearchContentHeight
contentHeight += 44.0
contentHeight += 9.0
if component.search != nil {
contentHeight += navigationBarSearchContentHeight + 2.0
}
}
if component.tabsNode != nil {
contentHeight += 40.0
var headersContentHeight: CGFloat = 0.0
if let disappearingHeaderPanelsView = self.disappearingHeaderPanels?.view {
let headerPanelsFrame = CGRect(origin: CGPoint(x: 0.0, y: headersContentHeight), size: disappearingHeaderPanelsView.bounds.size)
transition.setFrame(view: disappearingHeaderPanelsView, frame: headerPanelsFrame)
}
if let headerPanels = component.headerPanels {
let headerPanelsView: ComponentView<Empty>
var headerPanelsTransition = transition
if let current = self.headerPanelsView {
headerPanelsView = current
} else {
headerPanelsTransition = headerPanelsTransition.withAnimation(.none)
headerPanelsView = ComponentView()
self.headerPanelsView = headerPanelsView
}
let headerPanelsSize = headerPanelsView.update(
transition: headerPanelsTransition,
component: headerPanels,
environment: {},
containerSize: CGSize(width: availableSize.width - component.sideInset * 2.0, height: 10000.0)
)
let headerPanelsFrame = CGRect(origin: CGPoint(x: component.sideInset, y: headersContentHeight), size: headerPanelsSize)
if let headerPanelsComponentView = headerPanelsView.view {
if headerPanelsComponentView.superview == nil {
self.bottomContentsContainer.addSubview(headerPanelsComponentView)
transition.animateAlpha(view: headerPanelsComponentView, from: 0.0, to: 1.0)
}
headerPanelsTransition.setFrame(view: headerPanelsComponentView, frame: headerPanelsFrame)
}
headersContentHeight += headerPanelsSize.height
} else if let headerPanelsView = self.headerPanelsView {
self.headerPanelsView = nil
self.disappearingHeaderPanels = headerPanelsView
if let headerPanelsComponentView = headerPanelsView.view {
transition.setAlpha(view: headerPanelsComponentView, alpha: 0.0, completion: { [weak self, weak headerPanelsComponentView] _ in
guard let self, let headerPanelsComponentView else {
return
}
headerPanelsComponentView.removeFromSuperview()
if self.disappearingHeaderPanels?.view === headerPanelsComponentView {
self.disappearingHeaderPanels = nil
}
})
}
}
headersContentHeight += 3.0
transition.setBounds(view: self.bottomContentsContainer, bounds: CGRect(origin: CGPoint(), size: CGSize(width: availableSize.width, height: headersContentHeight)))
if let activeSearch = component.activeSearch, !activeSearch.isExternal {
transition.setAlpha(view: self.bottomContentsContainer, alpha: 0.0)
} else {
transition.setAlpha(view: self.bottomContentsContainer, alpha: 1.0)
}
if component.accessoryPanelContainer != nil && !component.isSearchActive {
contentHeight += component.accessoryPanelContainerHeight
if component.activeSearch == nil {
contentHeight += headersContentHeight
if component.tabsNode != nil {
contentHeight += 40.0
}
}
let size = CGSize(width: availableSize.width, height: contentHeight)
@@ -0,0 +1,254 @@
import Foundation
import UIKit
import ComponentFlow
import ChatListTitleView
import TelegramPresentationData
import Display
import MoreHeaderButton
public final class NavigationButtonComponentEnvironment: Equatable {
public let theme: PresentationTheme
public init(theme: PresentationTheme) {
self.theme = theme
}
public static func ==(lhs: NavigationButtonComponentEnvironment, rhs: NavigationButtonComponentEnvironment) -> Bool {
if lhs.theme != rhs.theme {
return false
}
return true
}
}
public final class NavigationButtonComponent: Component {
public typealias EnvironmentType = NavigationButtonComponentEnvironment
public enum Content: Equatable {
case text(title: String, isBold: Bool)
case more
case icon(imageName: String)
case proxy(status: ChatTitleProxyStatus)
}
public let content: Content
public let pressed: (UIView) -> Void
public let contextAction: ((UIView, ContextGesture?) -> Void)?
public init(
content: Content,
pressed: @escaping (UIView) -> Void,
contextAction: ((UIView, ContextGesture?) -> Void)? = nil
) {
self.content = content
self.pressed = pressed
self.contextAction = contextAction
}
public static func ==(lhs: NavigationButtonComponent, rhs: NavigationButtonComponent) -> Bool {
if lhs.content != rhs.content {
return false
}
return true
}
public final class View: HighlightTrackingButton {
private var textView: ImmediateTextView?
private var iconView: UIImageView?
private var iconImageName: String?
private var proxyNode: ChatTitleProxyNode?
private var moreButton: MoreHeaderButton?
private var component: NavigationButtonComponent?
private var theme: PresentationTheme?
override init(frame: CGRect) {
super.init(frame: frame)
self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
self.highligthedChanged = { [weak self] highlighted in
guard let self else {
return
}
if highlighted {
self.textView?.alpha = 0.6
self.proxyNode?.alpha = 0.6
self.iconView?.alpha = 0.6
} else {
self.textView?.alpha = 1.0
self.textView?.layer.animateAlpha(from: 0.6, to: 1.0, duration: 0.2)
self.proxyNode?.alpha = 1.0
self.proxyNode?.layer.animateAlpha(from: 0.6, to: 1.0, duration: 0.2)
self.iconView?.alpha = 1.0
self.iconView?.layer.animateAlpha(from: 0.6, to: 1.0, duration: 0.2)
}
}
}
required public init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc private func pressed() {
self.component?.pressed(self)
}
func update(component: NavigationButtonComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<NavigationButtonComponentEnvironment>, transition: ComponentTransition) -> CGSize {
self.component = component
let theme = environment[NavigationButtonComponentEnvironment.self].value.theme
var themeUpdated = false
if self.theme !== theme {
self.theme = theme
themeUpdated = true
}
var textString: NSAttributedString?
var imageName: String?
var proxyStatus: ChatTitleProxyStatus?
var isMore: Bool = false
switch component.content {
case let .text(title, isBold):
textString = NSAttributedString(string: title, font: isBold ? Font.bold(17.0) : Font.medium(17.0), textColor: theme.chat.inputPanel.panelControlColor)
case .more:
isMore = true
case let .icon(imageNameValue):
imageName = imageNameValue
case let .proxy(status):
proxyStatus = status
}
var size = CGSize(width: 0.0, height: availableSize.height)
if let textString = textString {
let textView: ImmediateTextView
if let current = self.textView {
textView = current
} else {
textView = ImmediateTextView()
textView.isUserInteractionEnabled = false
self.textView = textView
self.addSubview(textView)
}
textView.attributedText = textString
let textSize = textView.updateLayout(availableSize)
let textInset: CGFloat = 12.0
size.width = max(44.0, textSize.width + textInset * 2.0)
textView.frame = CGRect(origin: CGPoint(x: floor((size.width - textSize.width) / 2.0), y: floor((availableSize.height - textSize.height) / 2.0)), size: textSize)
} else if let textView = self.textView {
self.textView = nil
textView.removeFromSuperview()
}
if let imageName = imageName {
let iconView: UIImageView
if let current = self.iconView {
iconView = current
} else {
iconView = UIImageView()
iconView.isUserInteractionEnabled = false
self.iconView = iconView
self.addSubview(iconView)
}
if self.iconImageName != imageName || themeUpdated {
self.iconImageName = imageName
iconView.image = generateTintedImage(image: UIImage(bundleImageName: imageName), color: theme.chat.inputPanel.panelControlColor)
}
if let iconSize = iconView.image?.size {
size.width = 44.0
iconView.frame = CGRect(origin: CGPoint(x: floor((size.width - iconSize.width) / 2.0), y: floor((availableSize.height - iconSize.height) / 2.0)), size: iconSize)
}
} else if let iconView = self.iconView {
self.iconView = nil
iconView.removeFromSuperview()
self.iconImageName = nil
}
if let proxyStatus = proxyStatus {
let proxyNode: ChatTitleProxyNode
if let current = self.proxyNode {
proxyNode = current
} else {
proxyNode = ChatTitleProxyNode(theme: theme)
proxyNode.isUserInteractionEnabled = false
self.proxyNode = proxyNode
self.addSubnode(proxyNode)
}
let proxySize = CGSize(width: 30.0, height: 30.0)
size.width = 44.0
proxyNode.theme = theme
proxyNode.status = proxyStatus
proxyNode.frame = CGRect(origin: CGPoint(x: floor((size.width - proxySize.width) / 2.0), y: floor((availableSize.height - proxySize.height) / 2.0)), size: proxySize)
} else if let proxyNode = self.proxyNode {
self.proxyNode = nil
proxyNode.removeFromSupernode()
}
if isMore {
let moreButton: MoreHeaderButton
if let current = self.moreButton, !themeUpdated {
moreButton = current
} else {
if let moreButton = self.moreButton {
moreButton.removeFromSupernode()
self.moreButton = nil
}
moreButton = MoreHeaderButton(color: theme.chat.inputPanel.panelControlColor)
moreButton.isUserInteractionEnabled = true
moreButton.setContent(.more(MoreHeaderButton.optionsCircleImage(color: theme.chat.inputPanel.panelControlColor)))
moreButton.onPressed = { [weak self] in
guard let self, let component = self.component else {
return
}
self.moreButton?.play()
component.pressed(self)
}
moreButton.contextAction = { [weak self] sourceNode, gesture in
guard let self, let component = self.component else {
return
}
self.moreButton?.play()
component.contextAction?(self, gesture)
}
self.moreButton = moreButton
self.addSubnode(moreButton)
}
let buttonSize = CGSize(width: 44.0, height: 44.0)
size.width = 44.0
moreButton.setContent(.more(MoreHeaderButton.optionsCircleImage(color: theme.rootController.navigationBar.buttonColor)))
moreButton.frame = CGRect(origin: CGPoint(x: floor((size.width - buttonSize.width) / 2.0), y: floor((size.height - buttonSize.height) / 2.0)), size: buttonSize)
} else if let moreButton = self.moreButton {
self.moreButton = nil
moreButton.removeFromSupernode()
}
return size
}
}
public func makeView() -> View {
return View(frame: CGRect())
}
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<NavigationButtonComponentEnvironment>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}