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
+2
View File
@@ -21,6 +21,8 @@ swift_library(
"//submodules/AvatarNode:AvatarNode",
"//submodules/AccountContext:AccountContext",
"//submodules/TelegramUI/Components/EmojiStatusComponent",
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
"//submodules/Components/ComponentDisplayAdapters",
],
visibility = [
"//visibility:public",
@@ -11,6 +11,7 @@ import AvatarNode
import AccountContext
import ComponentFlow
import EmojiStatusComponent
import ComponentDisplayAdapters
private func generateLoupeIcon(color: UIColor) -> UIImage? {
return generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Loupe"), color: color)
@@ -369,6 +370,7 @@ private class SearchBarTextField: UITextField, UIScrollViewDelegate {
}
var theme: SearchBarNodeTheme
let style: SearchBarStyle
fileprivate func layoutTokens(transition: ContainedViewLayoutTransition = .immediate) {
var hasSelected = false
@@ -508,6 +510,12 @@ private class SearchBarTextField: UITextField, UIScrollViewDelegate {
}
fileprivate var tokensWidth: CGFloat = 0.0
var tokensInsetWidth: CGFloat {
if self.tokensWidth == 0.0 {
return 0.0
}
return self.tokensWidth + 8.0
}
private let measurePrefixLabel: ImmediateTextNode
let prefixLabel: ImmediateTextNode
@@ -519,8 +527,9 @@ private class SearchBarTextField: UITextField, UIScrollViewDelegate {
}
}
init(theme: SearchBarNodeTheme) {
init(theme: SearchBarNodeTheme, style: SearchBarStyle) {
self.theme = theme
self.style = style
self.placeholderLabel = ImmediateTextNode()
self.placeholderLabel.isUserInteractionEnabled = false
@@ -547,7 +556,10 @@ private class SearchBarTextField: UITextField, UIScrollViewDelegate {
super.init(frame: CGRect())
self.addSubnode(self.placeholderLabel)
if case .glass = style {
} else {
self.addSubnode(self.placeholderLabel)
}
self.addSubnode(self.prefixLabel)
self.addSubnode(self.clippingNode)
self.clippingNode.addSubnode(self.tokenContainerNode)
@@ -682,11 +694,19 @@ private class SearchBarTextField: UITextField, UIScrollViewDelegate {
let textRect = self.textRect(forBounds: bounds)
let labelSize = self.placeholderLabel.updateLayout(textRect.size)
self.placeholderLabel.frame = CGRect(origin: CGPoint(x: textRect.minX + placeholderXOffset, y: textRect.minY + textOffset + placeholderYOffset), size: labelSize)
switch self.style {
case .glass, .inlineNavigation:
placeholderYOffset += 0.0
case .legacy, .modern:
break
}
self.placeholderLabel.frame = CGRect(origin: CGPoint(x: textRect.minX + placeholderXOffset, y: floorToScreenPixels(bounds.height - labelSize.height) * 0.5), size: labelSize)
let prefixSize = self.prefixLabel.updateLayout(CGSize(width: floor(bounds.size.width * 0.7), height: bounds.size.height))
let prefixBounds = bounds.insetBy(dx: 4.0, dy: 4.0)
self.prefixLabel.frame = CGRect(origin: CGPoint(x: prefixBounds.minX, y: prefixBounds.minY + textOffset + placeholderYOffset), size: prefixSize)
self.prefixLabel.frame = CGRect(origin: CGPoint(x: prefixBounds.minX, y: floorToScreenPixels(bounds.height - prefixSize.height) * 0.5), size: prefixSize)
}
override func deleteBackward() {
@@ -813,40 +833,52 @@ public final class SearchBarNodeTheme: Equatable {
public enum SearchBarStyle {
case modern
case legacy
case inlineNavigation
case glass
var font: UIFont {
switch self {
case .modern:
return Font.regular(17.0)
case .legacy:
return Font.regular(14.0)
case .modern, .inlineNavigation, .glass:
return Font.regular(17.0)
case .legacy:
return Font.regular(14.0)
}
}
var cornerDiameter: CGFloat {
switch self {
case .modern:
return 21.0
case .legacy:
return 14.0
case .modern, .inlineNavigation:
return 21.0
case .glass:
return 22.0
case .legacy:
return 14.0
}
}
var height: CGFloat {
switch self {
case .modern:
return 36.0
case .legacy:
return 28.0
case .inlineNavigation:
return 48.0
case .glass:
return 44.0
case .modern:
return 36.0
case .legacy:
return 28.0
}
}
var padding: CGFloat {
switch self {
case .modern:
return 10.0
case .legacy:
return 8.0
case .inlineNavigation:
return 0.0
case .glass:
return 20.0
case .modern:
return 10.0
case .legacy:
return 8.0
}
}
}
@@ -868,6 +900,9 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate {
public var tokensUpdated: (([SearchBarToken]) -> Void)?
private let inlineSearchPlaceholder: SearchBarPlaceholderNode
private var inlineSearchPlaceholderContentsView: SearchBarPlaceholderContentView?
private let backgroundNode: NavigationBackgroundNode
private let separatorNode: ASDisplayNode
private let textBackgroundNode: ASDisplayNode
@@ -877,6 +912,8 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate {
private let clearButton: HighlightableButtonNode
private let cancelButton: HighlightableButtonNode
private var takenSearchPlaceholderContentView: SearchBarPlaceholderContentView?
public var placeholderString: NSAttributedString? {
get {
return self.textField.placeholderString
@@ -941,6 +978,12 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate {
activityIndicator.removeFromSupernode()
}
self.iconNode.isHidden = self.activity
if let takenSearchPlaceholderContentView = self.takenSearchPlaceholderContentView {
takenSearchPlaceholderContentView.updateSearchIconVisibility(isVisible: !self.activity)
}
if let inlineSearchPlaceholderContentsView = self.inlineSearchPlaceholderContentsView {
inlineSearchPlaceholderContentsView.updateSearchIconVisibility(isVisible: !self.activity)
}
}
}
}
@@ -965,17 +1008,24 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate {
private var validLayout: (CGSize, CGFloat, CGFloat)?
private let fieldStyle: SearchBarStyle
public let fieldStyle: SearchBarStyle
private let forceSeparator: Bool
private var theme: SearchBarNodeTheme?
private var presentationTheme: PresentationTheme
private var strings: PresentationStrings?
private let cancelText: String?
public init(theme: SearchBarNodeTheme, strings: PresentationStrings, fieldStyle: SearchBarStyle = .legacy, icon: Icon = .loupe, forceSeparator: Bool = false, displayBackground: Bool = true, cancelText: String? = nil) {
private var isAnimatingOut: Bool = false
public init(theme: SearchBarNodeTheme, presentationTheme: PresentationTheme, strings: PresentationStrings, fieldStyle: SearchBarStyle = .legacy, icon: Icon = .loupe, forceSeparator: Bool = false, displayBackground: Bool = true, cancelText: String? = nil) {
self.presentationTheme = presentationTheme
self.fieldStyle = fieldStyle
self.forceSeparator = forceSeparator
self.cancelText = cancelText
self.icon = icon
self.inlineSearchPlaceholder = SearchBarPlaceholderNode(fieldStyle: .glass)
self.backgroundNode = NavigationBackgroundNode(color: theme.background)
self.backgroundNode.isUserInteractionEnabled = false
@@ -994,7 +1044,7 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate {
self.iconNode.displaysAsynchronously = false
self.iconNode.displayWithoutProcessing = true
self.textField = SearchBarTextField(theme: theme)
self.textField = SearchBarTextField(theme: theme, style: fieldStyle)
self.textField.accessibilityTraits = .searchField
self.textField.autocorrectionType = .no
self.textField.returnKeyType = .search
@@ -1011,14 +1061,27 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate {
super.init()
self.addSubnode(self.backgroundNode)
self.addSubnode(self.separatorNode)
self.addSubnode(self.textBackgroundNode)
switch self.fieldStyle {
case .glass:
break
case .inlineNavigation:
break
case .legacy, .modern:
self.addSubnode(self.backgroundNode)
self.addSubnode(self.separatorNode)
self.addSubnode(self.textBackgroundNode)
}
self.view.addSubview(self.textField)
self.addSubnode(self.iconNode)
switch self.fieldStyle {
case .glass, .inlineNavigation:
break
case .legacy, .modern:
self.addSubnode(self.iconNode)
self.addSubnode(self.cancelButton)
}
self.addSubnode(self.clearButton)
self.addSubnode(self.cancelButton)
self.textField.delegate = self
self.textField.addTarget(self, action: #selector(self.textFieldDidChange(_:)), for: .editingChanged)
@@ -1043,11 +1106,11 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate {
self.cancelButton.addTarget(self, action: #selector(self.cancelPressed), forControlEvents: .touchUpInside)
self.clearButton.addTarget(self, action: #selector(self.clearPressed), forControlEvents: .touchUpInside)
self.updateThemeAndStrings(theme: theme, strings: strings)
self.updateThemeAndStrings(theme: theme, presentationTheme: presentationTheme, strings: strings)
self.updateIsEmpty(animated: false)
}
public func updateThemeAndStrings(theme: SearchBarNodeTheme, strings: PresentationStrings) {
public func updateThemeAndStrings(theme: SearchBarNodeTheme, presentationTheme: PresentationTheme, strings: PresentationStrings) {
if self.theme != theme || self.strings !== strings {
self.clearButton.accessibilityLabel = strings.WebSearch_RecentSectionClear
self.cancelButton.accessibilityLabel = self.cancelText ?? strings.Common_Cancel
@@ -1080,6 +1143,7 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate {
}
self.theme = theme
self.presentationTheme = presentationTheme
self.strings = strings
if let (boundingSize, leftInset, rightInset) = self.validLayout {
self.updateLayout(boundingSize: boundingSize, leftInset: leftInset, rightInset: rightInset, transition: .immediate)
@@ -1093,19 +1157,40 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate {
self.backgroundNode.update(size: self.backgroundNode.bounds.size, transition: .immediate)
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: self.bounds.size.height), size: CGSize(width: self.bounds.size.width, height: UIScreenPixel)))
let verticalOffset: CGFloat = boundingSize.height - 82.0
let contentFrame = CGRect(origin: CGPoint(x: leftInset, y: 0.0), size: CGSize(width: boundingSize.width - leftInset - rightInset, height: boundingSize.height))
let textBackgroundHeight = self.fieldStyle.height
let textBackgroundHeight: CGFloat
if case .inlineNavigation = self.fieldStyle {
textBackgroundHeight = boundingSize.height
} else {
textBackgroundHeight = self.fieldStyle.height
}
let verticalOffset: CGFloat
switch self.fieldStyle {
case .inlineNavigation, .glass:
verticalOffset = -textBackgroundHeight
case .legacy, .modern:
verticalOffset = boundingSize.height - 82.0
}
let cancelButtonSize = self.cancelButton.measure(CGSize(width: 100.0, height: CGFloat.infinity))
transition.updateFrame(node: self.cancelButton, frame: CGRect(origin: CGPoint(x: contentFrame.maxX - 10.0 - cancelButtonSize.width, y: verticalOffset + textBackgroundHeight + floorToScreenPixels((textBackgroundHeight - cancelButtonSize.height) / 2.0)), size: cancelButtonSize))
let padding = self.fieldStyle.padding
let textBackgroundFrame = CGRect(origin: CGPoint(x: contentFrame.minX + padding, y: verticalOffset + textBackgroundHeight), size: CGSize(width: contentFrame.width - padding * 2.0 - (self.hasCancelButton ? cancelButtonSize.width + 11.0 : 0.0), height: textBackgroundHeight))
var textBackgroundFrame = CGRect(origin: CGPoint(x: contentFrame.minX + padding, y: verticalOffset + textBackgroundHeight), size: CGSize(width: contentFrame.width - padding - (self.hasCancelButton ? cancelButtonSize.width + 11.0 : 0.0), height: textBackgroundHeight))
if case .glass = self.fieldStyle {
textBackgroundFrame.size.width -= 8.0
} else {
textBackgroundFrame.size.width -= padding
}
transition.updateFrame(node: self.textBackgroundNode, frame: textBackgroundFrame)
let textFrame = CGRect(origin: CGPoint(x: textBackgroundFrame.minX + 24.0, y: textBackgroundFrame.minY), size: CGSize(width: max(1.0, textBackgroundFrame.size.width - 24.0 - 27.0), height: textBackgroundFrame.size.height))
var textFrame = CGRect(origin: CGPoint(x: 0.0, y: textBackgroundFrame.minY), size: CGSize(width: max(1.0, textBackgroundFrame.size.width - 24.0 - 27.0), height: textBackgroundFrame.size.height))
if case .inlineNavigation = self.fieldStyle {
textFrame.size.width = boundingSize.width - 27.0
textBackgroundFrame.size.width = boundingSize.width
} else {
textFrame.origin.x = textBackgroundFrame.minX + 24.0
}
if let iconImage = self.iconNode.image {
let iconSize = iconImage.size
@@ -1121,6 +1206,54 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate {
transition.updateFrame(node: self.clearButton, frame: CGRect(origin: CGPoint(x: textBackgroundFrame.maxX - 6.0 - clearSize.width, y: textBackgroundFrame.minY + floor((textBackgroundFrame.size.height - clearSize.height) / 2.0)), size: clearSize))
self.textField.frame = textFrame
let additionalPlaceholderInset = self.textField.tokensInsetWidth
let searchPlaceholderFrame = CGRect(origin: CGPoint(x: leftInset + 16.0, y: 0.0), size: CGSize(width: max(0.0, boundingSize.width - 16.0 * 2.0 - leftInset - rightInset), height: 44.0))
if case .glass = self.fieldStyle, self.takenSearchPlaceholderContentView == nil {
transition.updateFrame(node: self.inlineSearchPlaceholder, frame: searchPlaceholderFrame)
var isFirstTime = false
if let theme = self.theme {
let _ = self.inlineSearchPlaceholder.updateLayout(
placeholderString: self.placeholderString,
compactPlaceholderString: self.placeholderString,
constrainedSize: searchPlaceholderFrame.size,
expansionProgress: 1.0,
iconColor: theme.inputIcon,
foregroundColor: self.presentationTheme.chat.inputPanel.panelControlColor,
backgroundColor: self.presentationTheme.rootController.navigationBar.opaqueBackgroundColor,
controlColor: self.presentationTheme.chat.inputPanel.panelControlColor,
transition: transition
)
if self.inlineSearchPlaceholderContentsView == nil {
isFirstTime = true
let inlineSearchPlaceholderContentsView = self.inlineSearchPlaceholder.takeContents()
inlineSearchPlaceholderContentsView.onCancel = { [weak self] in
guard let self else {
return
}
self.cancel?()
}
self.inlineSearchPlaceholderContentsView = inlineSearchPlaceholderContentsView
self.view.insertSubview(inlineSearchPlaceholderContentsView, at: 0)
}
}
if let inlineSearchPlaceholderContentsView = self.inlineSearchPlaceholderContentsView {
inlineSearchPlaceholderContentsView.update(size: searchPlaceholderFrame.size, isActive: true, additionalPlaceholderInset: additionalPlaceholderInset, transition: transition)
transition.updateFrame(view: inlineSearchPlaceholderContentsView, frame: searchPlaceholderFrame)
if isFirstTime {
self.updateIsEmpty(animated: false)
inlineSearchPlaceholderContentsView.updateSearchIconVisibility(isVisible: !self.activity)
}
}
}
if !self.isAnimatingOut, let takenSearchPlaceholderContentView = self.takenSearchPlaceholderContentView {
transition.updateFrame(view: takenSearchPlaceholderContentView, frame: searchPlaceholderFrame)
takenSearchPlaceholderContentView.update(size: searchPlaceholderFrame.size, isActive: true, additionalPlaceholderInset: additionalPlaceholderInset, transition: transition)
}
}
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
@@ -1138,7 +1271,33 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate {
}
public func animateIn(from node: SearchBarPlaceholderNode, duration: Double, timingFunction: String) {
let initialTextBackgroundFrame = node.view.convert(node.backgroundNode.frame, to: self.view)
guard let (boundingSize, leftInset, rightInset) = self.validLayout else {
return
}
self.inlineSearchPlaceholder.isHidden = true
let takenSearchPlaceholderContentView = node.takeContents()
takenSearchPlaceholderContentView.onCancel = { [weak self] in
guard let self else {
return
}
self.cancel?()
}
self.takenSearchPlaceholderContentView = takenSearchPlaceholderContentView
self.view.insertSubview(takenSearchPlaceholderContentView, at: 0)
if let inlineSearchPlaceholderContentsView = self.inlineSearchPlaceholderContentsView {
inlineSearchPlaceholderContentsView.removeFromSuperview()
}
let sourceFrame = node.view.convert(node.bounds, to: self.view)
let targetFrame = CGRect(origin: CGPoint(x: leftInset + 16.0, y: 0.0), size: CGSize(width: max(0.0, boundingSize.width - 16.0 * 2.0 - leftInset - rightInset), height: 44.0))
let transition: ContainedViewLayoutTransition = .animated(duration: duration, curve: timingFunction == kCAMediaTimingFunctionSpring ? .spring : .easeInOut)
takenSearchPlaceholderContentView.frame = sourceFrame
transition.updateFrame(view: takenSearchPlaceholderContentView, frame: targetFrame)
takenSearchPlaceholderContentView.update(size: targetFrame.size, isActive: true, additionalPlaceholderInset: self.textField.tokensInsetWidth, transition: transition)
/*let initialTextBackgroundFrame = node.view.convert(node.backgroundView.frame, to: self.view)
let initialBackgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: self.bounds.size.width, height: max(0.0, initialTextBackgroundFrame.maxY + 8.0)))
if let fromBackgroundColor = node.backgroundColor, let toBackgroundColor = self.backgroundNode.backgroundColor {
@@ -1152,7 +1311,7 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate {
self.separatorNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration)
self.separatorNode.layer.animateFrame(from: initialSeparatorFrame, to: self.separatorNode.frame, duration: duration, timingFunction: timingFunction)
if let fromTextBackgroundColor = node.backgroundNode.backgroundColor, let toTextBackgroundColor = self.textBackgroundNode.backgroundColor {
if let fromTextBackgroundColor = node.backgroundView.backgroundColor, let toTextBackgroundColor = self.textBackgroundNode.backgroundColor {
self.textBackgroundNode.layer.animate(from: fromTextBackgroundColor.cgColor, to: toTextBackgroundColor.cgColor, keyPath: "backgroundColor", timingFunction: timingFunction, duration: duration * 1.0)
}
self.textBackgroundNode.layer.animateFrame(from: initialTextBackgroundFrame, to: self.textBackgroundNode.frame, duration: duration, timingFunction: timingFunction)
@@ -1177,7 +1336,7 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate {
let cancelButtonFrame = self.cancelButton.frame
self.cancelButton.layer.animatePosition(from: CGPoint(x: self.bounds.size.width + cancelButtonFrame.size.width / 2.0, y: initialTextBackgroundFrame.midY), to: self.cancelButton.layer.position, duration: duration, timingFunction: timingFunction)
node.isHidden = true
node.isHidden = true*/
}
public func deactivate(clear: Bool = true) {
@@ -1191,7 +1350,9 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate {
}
public func transitionOut(to node: SearchBarPlaceholderNode, transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) {
let targetTextBackgroundFrame = node.view.convert(node.backgroundNode.frame, to: self.view)
self.isAnimatingOut = true
/*let targetTextBackgroundFrame = node.view.convert(node.backgroundView.frame, to: self.view)
let duration: Double = transition.isAnimated ? 0.5 : 0.0
let timingFunction = kCAMediaTimingFunctionSpring
@@ -1307,8 +1468,8 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate {
let transitionBackgroundNode = ASDisplayNode()
transitionBackgroundNode.isLayerBacked = true
transitionBackgroundNode.displaysAsynchronously = false
transitionBackgroundNode.backgroundColor = node.backgroundNode.backgroundColor
transitionBackgroundNode.cornerRadius = node.backgroundNode.cornerRadius
transitionBackgroundNode.backgroundColor = node.backgroundView.backgroundColor
transitionBackgroundNode.cornerRadius = node.backgroundView.layer.cornerRadius
self.insertSubnode(transitionBackgroundNode, aboveSubnode: self.textBackgroundNode)
transitionBackgroundNode.layer.animateFrame(from: self.textBackgroundNode.frame, to: targetTextBackgroundFrame, duration: duration, timingFunction: timingFunction, removeOnCompletion: false)
@@ -1331,7 +1492,52 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate {
self.iconNode.layer.animateFrame(from: self.iconNode.frame, to: targetIconFrame, duration: duration, timingFunction: timingFunction, removeOnCompletion: false)
let cancelButtonFrame = self.cancelButton.frame
self.cancelButton.layer.animatePosition(from: self.cancelButton.layer.position, to: CGPoint(x: self.bounds.size.width + cancelButtonFrame.size.width / 2.0, y: targetTextBackgroundFrame.midY), duration: duration, timingFunction: timingFunction, removeOnCompletion: false)
self.cancelButton.layer.animatePosition(from: self.cancelButton.layer.position, to: CGPoint(x: self.bounds.size.width + cancelButtonFrame.size.width / 2.0, y: targetTextBackgroundFrame.midY), duration: duration, timingFunction: timingFunction, removeOnCompletion: false)*/
if let takenSearchPlaceholderContentView = self.takenSearchPlaceholderContentView {
let transition = ComponentTransition(transition)
let alphaTransition: ComponentTransition = transition.animation.isImmediate ? .immediate : .easeInOut(duration: 0.2)
let sourceFrame = node.view.convert(node.bounds, to: self.view)
takenSearchPlaceholderContentView.update(size: sourceFrame.size, isActive: false, additionalPlaceholderInset: 0.0, transition: transition.containedViewLayoutTransition)
takenSearchPlaceholderContentView.updatePlaceholderVisibility(isVisible: true)
takenSearchPlaceholderContentView.updateSearchIconVisibility(isVisible: true)
transition.setFrame(view: takenSearchPlaceholderContentView, frame: sourceFrame, completion: { [weak node] _ in
node?.putBackContents()
completion()
})
let textBackgroundHeight: CGFloat
if case .inlineNavigation = self.fieldStyle {
textBackgroundHeight = sourceFrame.height
} else {
textBackgroundHeight = self.fieldStyle.height
}
let padding = self.fieldStyle.padding
var textBackgroundFrame = CGRect(origin: CGPoint(x: sourceFrame.minX + padding, y: sourceFrame.minY), size: CGSize(width: sourceFrame.width - padding, height: textBackgroundHeight))
if case .glass = self.fieldStyle {
textBackgroundFrame.size.width -= 8.0
} else {
textBackgroundFrame.size.width -= padding
}
var textFrame = CGRect(origin: CGPoint(x: 0.0, y: textBackgroundFrame.minY), size: CGSize(width: max(1.0, textBackgroundFrame.size.width - 24.0 - 27.0), height: textBackgroundFrame.size.height))
if case .inlineNavigation = self.fieldStyle {
textFrame.size.width = sourceFrame.width - 27.0
textBackgroundFrame.size.width = sourceFrame.width
} else {
textFrame.origin.x = textBackgroundFrame.minX + 24.0
}
transition.setFrame(view: self.textField, frame: textFrame)
//alphaTransition.setAlpha(view: self.textField, alpha: 0.0)
self.textField.isHidden = true
let clearSize = self.clearButton.bounds.size
alphaTransition.setAlpha(view: self.clearButton.view, alpha: 0.0)
transition.setFrame(view: self.clearButton.view, frame: CGRect(origin: CGPoint(x: textBackgroundFrame.maxX - 6.0 - clearSize.width, y: textBackgroundFrame.minY + floor((textBackgroundFrame.size.height - clearSize.height) / 2.0)), size: clearSize))
}
}
public func textFieldDidBeginEditing(_ textField: UITextField) {
@@ -1403,6 +1609,10 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate {
let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.3, curve: .spring) : .immediate
let placeholderTransition = !isEmpty ? .immediate : transition
placeholderTransition.updateAlpha(node: self.textField.placeholderLabel, alpha: isEmpty ? 1.0 : 0.0)
if let takenSearchPlaceholderContentView = self.takenSearchPlaceholderContentView {
takenSearchPlaceholderContentView.updatePlaceholderVisibility(isVisible: isEmpty)
}
self.inlineSearchPlaceholderContentsView?.updatePlaceholderVisibility(isVisible: isEmpty)
let clearIsHidden = (textIsEmpty && tokensEmpty) && self.prefixString == nil
transition.updateAlpha(node: self.clearButton.imageNode, alpha: clearIsHidden ? 0.0 : 1.0)
@@ -5,6 +5,8 @@ import AsyncDisplayKit
import Display
import AppBundle
import ComponentFlow
import GlassBackgroundComponent
import ComponentDisplayAdapters
private let templateLoupeIcon = UIImage(bundleImageName: "Components/Search Bar/Loupe")
@@ -21,44 +23,69 @@ private class SearchBarPlaceholderNodeView: UIView {
}
}
public class SearchBarPlaceholderNode: ASDisplayNode {
public var activate: (() -> Void)?
public final class SearchBarPlaceholderContentView: UIView {
private struct Params {
var placeholderString: NSAttributedString?
var compactPlaceholderString: NSAttributedString?
var constrainedSize: CGSize
var expansionProgress: CGFloat
var iconColor: UIColor
var foregroundColor: UIColor
var backgroundColor: UIColor
var controlColor: UIColor
var isActive: Bool
var additionalPlaceholderInset: CGFloat
init(placeholderString: NSAttributedString?, compactPlaceholderString: NSAttributedString?, constrainedSize: CGSize, expansionProgress: CGFloat, iconColor: UIColor, foregroundColor: UIColor, backgroundColor: UIColor, controlColor: UIColor, isActive: Bool, additionalPlaceholderInset: CGFloat) {
self.placeholderString = placeholderString
self.compactPlaceholderString = compactPlaceholderString
self.constrainedSize = constrainedSize
self.expansionProgress = expansionProgress
self.iconColor = iconColor
self.foregroundColor = foregroundColor
self.backgroundColor = backgroundColor
self.controlColor = controlColor
self.isActive = isActive
self.additionalPlaceholderInset = additionalPlaceholderInset
}
}
private let fieldStyle: SearchBarStyle
public let backgroundNode: ASDisplayNode
let fieldStyle: SearchBarStyle
let plainBackgroundView: UIImageView
let glassBackgroundView: GlassBackgroundView?
private var fillBackgroundColor: UIColor
private var foregroundColor: UIColor
private var iconColor: UIColor
public let iconNode: ASImageNode
public let labelNode: TextNode
let iconNode: ASImageNode
let labelNode: TextNode
let plainIconNode: ASImageNode
let plainLabelNode: TextNode
var pointerInteraction: PointerInteraction?
private var close: (background: GlassBackgroundView, icon: UIImageView)?
public private(set) var placeholderString: NSAttributedString?
private(set) var placeholderString: NSAttributedString?
private(set) var accessoryComponentContainer: UIView?
private(set) var accessoryComponentView: ComponentHostView<Empty>?
private var params: Params?
convenience public override init() {
self.init(fieldStyle: .legacy)
}
public var onCancel: (() -> Void)?
public init(fieldStyle: SearchBarStyle = .legacy) {
init(fieldStyle: SearchBarStyle) {
self.fieldStyle = fieldStyle
self.backgroundNode = ASDisplayNode()
self.backgroundNode.isLayerBacked = false
self.backgroundNode.displaysAsynchronously = false
self.fillBackgroundColor = UIColor.white
self.foregroundColor = UIColor(rgb: 0xededed)
self.iconColor = UIColor(rgb: 0x000000, alpha: 0.0)
self.backgroundNode.backgroundColor = self.foregroundColor
self.backgroundNode.cornerRadius = self.fieldStyle.cornerDiameter / 2.0
self.plainBackgroundView = UIImageView()
switch fieldStyle {
case .legacy, .modern:
self.glassBackgroundView = nil
case .inlineNavigation, .glass:
self.glassBackgroundView = GlassBackgroundView()
}
self.iconNode = ASImageNode()
self.iconNode.isLayerBacked = true
self.iconNode.displaysAsynchronously = false
self.iconNode.displayWithoutProcessing = true
@@ -66,51 +93,431 @@ public class SearchBarPlaceholderNode: ASDisplayNode {
self.labelNode.isOpaque = false
self.labelNode.isUserInteractionEnabled = false
self.plainIconNode = ASImageNode()
self.plainIconNode.displaysAsynchronously = false
self.plainIconNode.displayWithoutProcessing = true
self.plainLabelNode = TextNode()
self.plainLabelNode.isOpaque = false
self.plainLabelNode.isUserInteractionEnabled = false
super.init(frame: CGRect())
self.plainBackgroundView.isUserInteractionEnabled = true
self.addSubview(self.plainBackgroundView)
self.plainBackgroundView.addSubview(self.plainIconNode.view)
self.plainBackgroundView.addSubview(self.plainLabelNode.view)
if let glassBackgroundView = self.glassBackgroundView {
self.addSubview(glassBackgroundView)
glassBackgroundView.contentView.addSubview(self.iconNode.view)
glassBackgroundView.contentView.addSubview(self.labelNode.view)
}
}
required public init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc private func onCloseTapGesture(_ recognizer: UITapGestureRecognizer) {
if case .ended = recognizer.state {
self.onCancel?()
}
}
func updateLayout(
placeholderString: NSAttributedString?,
compactPlaceholderString: NSAttributedString?,
constrainedSize: CGSize,
expansionProgress: CGFloat,
iconColor: UIColor,
foregroundColor: UIColor,
backgroundColor: UIColor,
controlColor: UIColor,
transition: ContainedViewLayoutTransition
) -> CGFloat {
let params = Params(
placeholderString: placeholderString,
compactPlaceholderString: compactPlaceholderString,
constrainedSize: constrainedSize,
expansionProgress: expansionProgress,
iconColor: iconColor,
foregroundColor: foregroundColor,
backgroundColor: backgroundColor,
controlColor: controlColor,
isActive: false,
additionalPlaceholderInset: 0.0
)
self.params = params
return self.updateLayout(params: params, transition: transition)
}
public func update(size: CGSize, isActive: Bool, additionalPlaceholderInset: CGFloat, transition: ContainedViewLayoutTransition) {
guard var params = self.params else {
return
}
params.constrainedSize = size
params.expansionProgress = 1.0
params.isActive = isActive
params.additionalPlaceholderInset = additionalPlaceholderInset
let _ = self.updateLayout(params: params, transition: transition)
}
private func updateLayout(params: Params, transition: ContainedViewLayoutTransition) -> CGFloat {
let labelLayout = TextNode.asyncLayout(self.labelNode)
let plainLabelLayout = TextNode.asyncLayout(self.plainLabelNode)
let currentForegroundColor = self.foregroundColor
let currentIconColor = self.iconColor
let placeholderString: NSAttributedString?
if params.constrainedSize.width < 350.0 {
placeholderString = params.compactPlaceholderString
} else {
placeholderString = params.placeholderString
}
let (labelLayoutResult, labelApply) = labelLayout(TextNodeLayoutArguments(attributedString: placeholderString, backgroundColor: .clear, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: params.constrainedSize, alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (_, plainLabelApply) = plainLabelLayout(TextNodeLayoutArguments(attributedString: placeholderString, backgroundColor: .clear, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: params.constrainedSize, alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
var updatedColor: UIColor?
var updatedIconImage: UIImage?
if !currentForegroundColor.isEqual(params.foregroundColor) {
updatedColor = params.foregroundColor
}
if !currentIconColor.isEqual(params.iconColor) {
updatedIconImage = generateLoupeIcon(color: params.iconColor)
}
let height = params.constrainedSize.height * params.expansionProgress
let _ = labelApply()
let _ = plainLabelApply()
self.fillBackgroundColor = params.backgroundColor
self.foregroundColor = params.foregroundColor
self.iconColor = params.iconColor
self.plainBackgroundView.isUserInteractionEnabled = params.expansionProgress > 0.9999
if let updatedColor {
self.plainBackgroundView.backgroundColor = updatedColor
}
if let updatedIconImage {
self.iconNode.image = updatedIconImage
self.plainIconNode.image = updatedIconImage
}
self.placeholderString = placeholderString
var iconSize = CGSize()
var totalWidth = labelLayoutResult.size.width
var spacing: CGFloat = 4.0
if params.isActive {
spacing = 2.0
}
let iconX: CGFloat
if let iconImage = self.iconNode.image {
iconSize = iconImage.size
totalWidth += iconSize.width + spacing
if params.isActive {
iconX = 8.0
} else {
iconX = floor((params.constrainedSize.width - totalWidth) / 2.0)
}
transition.updateFrame(node: self.iconNode, frame: CGRect(origin: CGPoint(x: iconX, y: floorToScreenPixels((height - iconSize.height) / 2.0)), size: iconSize))
transition.updateFrame(node: self.plainIconNode, frame: CGRect(origin: CGPoint(x: iconX, y: floorToScreenPixels((height - iconSize.height) / 2.0)), size: iconSize))
} else {
iconX = 12.0
}
var textOffset: CGFloat = 0.0
if params.constrainedSize.height >= 36.0 {
textOffset += 1.0
}
let labelX: CGFloat = iconX + iconSize.width + spacing + params.additionalPlaceholderInset
let labelFrame = CGRect(origin: CGPoint(x: labelX, y: floorToScreenPixels((height - labelLayoutResult.size.height) / 2.0) + textOffset), size: labelLayoutResult.size)
transition.updateFrame(node: self.labelNode, frame: labelFrame)
transition.updateFrame(node: self.plainLabelNode, frame: labelFrame)
var innerAlpha = max(0.0, params.expansionProgress - 0.77) / 0.23
if innerAlpha > 0.9999 {
innerAlpha = 1.0
} else if innerAlpha < 0.0001 {
innerAlpha = 0.0
}
if self.labelNode.alpha != innerAlpha {
if !transition.isAnimated {
self.labelNode.layer.removeAnimation(forKey: "opacity")
self.iconNode.layer.removeAnimation(forKey: "opacity")
self.plainLabelNode.layer.removeAnimation(forKey: "opacity")
self.plainIconNode.layer.removeAnimation(forKey: "opacity")
}
transition.updateAlpha(node: self.labelNode, alpha: innerAlpha)
transition.updateAlpha(node: self.iconNode, alpha: innerAlpha)
transition.updateAlpha(node: self.plainLabelNode, alpha: innerAlpha)
transition.updateAlpha(node: self.plainIconNode, alpha: innerAlpha)
}
let outerAlpha = min(0.3, params.expansionProgress) / 0.3
let cornerRadius = height * 0.5
if self.plainBackgroundView.layer.cornerRadius != cornerRadius {
if !transition.isAnimated {
self.plainBackgroundView.layer.removeAnimation(forKey: "cornerRadius")
}
transition.updateCornerRadius(layer: self.plainBackgroundView.layer, cornerRadius: cornerRadius)
}
var plainBackgroundAlpha = outerAlpha
if params.isActive {
plainBackgroundAlpha = 0.0
}
if self.plainBackgroundView.alpha != plainBackgroundAlpha {
if !transition.isAnimated {
self.plainBackgroundView.layer.removeAnimation(forKey: "opacity")
}
transition.updateAlpha(layer: self.plainBackgroundView.layer, alpha: plainBackgroundAlpha)
}
var backgroundFrame = CGRect(origin: CGPoint(), size: CGSize(width: params.constrainedSize.width, height: height))
if params.isActive {
backgroundFrame.size.width -= 44.0 + 8.0
}
if self.plainBackgroundView.frame != backgroundFrame {
if !transition.isAnimated {
self.plainBackgroundView.layer.removeAnimation(forKey: "position")
self.plainBackgroundView.layer.removeAnimation(forKey: "bounds")
}
transition.updateFrame(view: self.plainBackgroundView, frame: CGRect(origin: CGPoint(), size: CGSize(width: params.constrainedSize.width, height: height)))
}
if let glassBackgroundView = self.glassBackgroundView {
transition.updatePosition(layer: glassBackgroundView.layer, position: backgroundFrame.center)
transition.updateBounds(layer: glassBackgroundView.layer, bounds: CGRect(origin: CGPoint(), size: backgroundFrame.size))
var backgroundAlpha: CGFloat = 1.0
if backgroundFrame.height < 16.0 {
backgroundAlpha = max(0.0, min(1.0, backgroundFrame.height / 16.0))
}
if !params.isActive {
backgroundAlpha = 0.0
}
ComponentTransition(transition).setAlpha(view: glassBackgroundView, alpha: backgroundAlpha)
let isDark = params.backgroundColor.hsb.b < 0.5
if params.isActive {
glassBackgroundView.update(size: backgroundFrame.size, cornerRadius: backgroundFrame.height * 0.5, isDark: isDark, tintColor: .init(kind: .panel, color: UIColor(white: isDark ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: ComponentTransition(transition))
}
if params.isActive {
let transition = ComponentTransition(transition)
let closeFrame = CGRect(origin: CGPoint(x: params.constrainedSize.width - 44.0, y: 0.0), size: CGSize(width: 44.0, height: 44.0))
let close: (background: GlassBackgroundView, icon: UIImageView)
var closeTransition = transition
if let current = self.close {
close = current
} else {
closeTransition = closeTransition.withAnimation(.none)
close = (GlassBackgroundView(), UIImageView())
self.close = close
close.icon.image = generateImage(CGSize(width: 40.0, height: 40.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setLineWidth(2.0)
context.setLineCap(.round)
context.setStrokeColor(UIColor.white.cgColor)
context.beginPath()
context.move(to: CGPoint(x: 12.0, y: 12.0))
context.addLine(to: CGPoint(x: size.width - 12.0, y: size.height - 12.0))
context.move(to: CGPoint(x: size.width - 12.0, y: 12.0))
context.addLine(to: CGPoint(x: 12.0, y: size.height - 12.0))
context.strokePath()
})?.withRenderingMode(.alwaysTemplate)
close.background.contentView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.onCloseTapGesture(_:))))
close.background.contentView.addSubview(close.icon)
self.insertSubview(close.background, at: 0)
if let image = close.icon.image {
close.icon.frame = image.size.centered(in: CGRect(origin: CGPoint(), size: closeFrame.size))
}
close.background.frame = closeFrame.offsetBy(dx: closeFrame.width + 40.0, dy: 0.0)
let isDark = params.backgroundColor.hsb.b < 0.5
close.background.update(size: close.background.bounds.size, cornerRadius: close.background.bounds.height * 0.5, isDark: isDark, tintColor: .init(kind: .panel, color: UIColor(white: isDark ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: .immediate)
ComponentTransition.immediate.setScale(view: close.background, scale: 0.001)
}
close.icon.tintColor = params.controlColor
transition.setPosition(view: close.background, position: closeFrame.center)
transition.setBounds(view: close.background, bounds: CGRect(origin: CGPoint(), size: closeFrame.size))
transition.setScale(view: close.background, scale: 1.0)
if let image = close.icon.image {
transition.setFrame(view: close.icon, frame: image.size.centered(in: CGRect(origin: CGPoint(), size: closeFrame.size)))
}
let isDark = params.backgroundColor.hsb.b < 0.5
close.background.update(size: closeFrame.size, cornerRadius: closeFrame.height * 0.5, isDark: isDark, tintColor: .init(kind: .panel, color: UIColor(white: isDark ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: closeTransition)
} else {
let transition = ComponentTransition(transition)
if let close = self.close {
self.close = nil
let closeBackground = close.background
let closeFrame = CGRect(origin: CGPoint(x: params.constrainedSize.width - 44.0, y: 0.0), size: CGSize(width: 44.0, height: 44.0)).offsetBy(dx: 44.0 + 40.0, dy: 0.0)
transition.setPosition(view: closeBackground, position: closeFrame.center)
transition.setBounds(view: closeBackground, bounds: CGRect(origin: CGPoint(), size: closeFrame.size))
let isDark = params.backgroundColor.hsb.b < 0.5
closeBackground.update(size: closeFrame.size, cornerRadius: closeFrame.height * 0.5, isDark: isDark, tintColor: .init(kind: .panel, color: UIColor(white: isDark ? 0.0 : 1.0, alpha: 0.6)), isInteractive: true, transition: transition)
transition.setScale(view: closeBackground, scale: 0.001, completion: { [weak closeBackground] _ in
closeBackground?.removeFromSuperview()
})
}
}
}
/*if let accessoryComponentContainer = self.accessoryComponentContainer {
accessoryComponentContainer.frame = CGRect(origin: CGPoint(x: constrainedSize.width - accessoryComponentContainer.bounds.width - 4.0, y: floor((constrainedSize.height * expansionProgress - accessoryComponentContainer.bounds.height) / 2.0)), size: accessoryComponentContainer.bounds.size)
transition.updateAlpha(layer: accessoryComponentContainer.layer, alpha: innerAlpha)
}*/
return height
}
public func updatePlaceholderVisibility(isVisible: Bool) {
self.labelNode.isHidden = !isVisible
self.plainLabelNode.isHidden = !isVisible
}
public func updateSearchIconVisibility(isVisible: Bool) {
self.iconNode.isHidden = !isVisible
self.plainIconNode.isHidden = !isVisible
}
}
public class SearchBarPlaceholderNode: ASDisplayNode {
private struct Params {
var placeholderString: NSAttributedString?
var compactPlaceholderString: NSAttributedString?
var constrainedSize: CGSize
var expansionProgress: CGFloat
var iconColor: UIColor
var foregroundColor: UIColor
var backgroundColor: UIColor
var controlColor: UIColor
init(placeholderString: NSAttributedString?, compactPlaceholderString: NSAttributedString?, constrainedSize: CGSize, expansionProgress: CGFloat, iconColor: UIColor, foregroundColor: UIColor, backgroundColor: UIColor, controlColor: UIColor) {
self.placeholderString = placeholderString
self.compactPlaceholderString = compactPlaceholderString
self.constrainedSize = constrainedSize
self.expansionProgress = expansionProgress
self.iconColor = iconColor
self.foregroundColor = foregroundColor
self.backgroundColor = backgroundColor
self.controlColor = controlColor
}
}
public var activate: (() -> Void)?
private let containerView: UIView
private let contentView: SearchBarPlaceholderContentView
public var backgroundView: UIView {
if let glassBackgroundView = self.contentView.glassBackgroundView {
return glassBackgroundView
} else {
return self.contentView.plainBackgroundView
}
}
public var iconNode: ASImageNode {
return self.contentView.iconNode
}
public var labelNode: TextNode {
return self.contentView.labelNode
}
public var fieldStyle: SearchBarStyle {
return self.contentView.fieldStyle
}
var pointerInteraction: PointerInteraction?
public var placeholderString: NSAttributedString? {
return self.contentView.placeholderString
}
private(set) var accessoryComponentContainer: UIView?
private(set) var accessoryComponentView: ComponentHostView<Empty>?
private var params: Params?
private var currentLayoutHeight: CGFloat?
private var isTakenOut: Bool = false
public init(fieldStyle: SearchBarStyle = .legacy) {
self.containerView = UIView()
self.contentView = SearchBarPlaceholderContentView(fieldStyle: fieldStyle)
super.init()
self.addSubnode(self.backgroundNode)
self.addSubnode(self.iconNode)
self.addSubnode(self.labelNode)
self.backgroundNode.isUserInteractionEnabled = true
self.view.addSubview(self.containerView)
self.containerView.addSubview(self.contentView)
}
override public func didLoad() {
super.didLoad()
let gestureRecognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.backgroundTap(_:)))
gestureRecognizer.highlight = { [weak self] point in
/*gestureRecognizer.highlight = { [weak self] point in
guard let strongSelf = self else {
return
}
if let _ = point {
strongSelf.backgroundNode.layer.animate(from: (strongSelf.backgroundNode.backgroundColor ?? strongSelf.foregroundColor).cgColor, to: strongSelf.foregroundColor.withMultipliedBrightnessBy(0.9).cgColor, keyPath: "backgroundColor", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.2)
strongSelf.backgroundNode.backgroundColor = strongSelf.foregroundColor.withMultipliedBrightnessBy(0.9)
} else {
strongSelf.backgroundNode.layer.animate(from: (strongSelf.backgroundNode.backgroundColor ?? strongSelf.foregroundColor).cgColor, to: strongSelf.foregroundColor.cgColor, keyPath: "backgroundColor", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.4)
strongSelf.backgroundNode.backgroundColor = strongSelf.foregroundColor
if let backgroundNode = strongSelf.contentView.backgroundNode {
if let _ = point {
backgroundNode.layer.animate(from: (backgroundNode.backgroundColor ?? strongSelf.foregroundColor).cgColor, to: strongSelf.foregroundColor.withMultipliedBrightnessBy(0.9).cgColor, keyPath: "backgroundColor", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.2)
backgroundNode.backgroundColor = strongSelf.foregroundColor.withMultipliedBrightnessBy(0.9)
} else {
backgroundNode.layer.animate(from: (backgroundNode.backgroundColor ?? strongSelf.foregroundColor).cgColor, to: strongSelf.foregroundColor.cgColor, keyPath: "backgroundColor", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.4)
backgroundNode.backgroundColor = strongSelf.foregroundColor
}
}
}
}*/
gestureRecognizer.tapActionAtPoint = { _ in
return .waitForSingleTap
}
self.backgroundNode.view.addGestureRecognizer(gestureRecognizer)
self.containerView.addGestureRecognizer(gestureRecognizer)
self.pointerInteraction = PointerInteraction(node: self, style: .caret, willEnter: { [weak self] in
/*self.pointerInteraction = PointerInteraction(node: self, style: .caret, willEnter: { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.backgroundNode.backgroundColor = strongSelf.foregroundColor.withMultipliedBrightnessBy(0.95)
if let backgroundNode = strongSelf.contentView.backgroundNode {
backgroundNode.backgroundColor = strongSelf.foregroundColor.withMultipliedBrightnessBy(0.95)
}
}, willExit: { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.backgroundNode.backgroundColor = strongSelf.foregroundColor
})
if let backgroundNode = strongSelf.contentView.backgroundNode {
backgroundNode.backgroundColor = strongSelf.foregroundColor
}
})*/
}
public func setAccessoryComponent(component: AnyComponent<Empty>?) {
if let component = component {
/*if let component = component {
let accessoryComponentContainer: UIView
if let current = self.accessoryComponentContainer {
accessoryComponentContainer = current
@@ -142,119 +549,43 @@ public class SearchBarPlaceholderNode: ASDisplayNode {
accessoryComponentView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak accessoryComponentView] _ in
accessoryComponentView?.removeFromSuperview()
})
}*/
}
public func takeContents() -> SearchBarPlaceholderContentView {
self.isTakenOut = true
return self.contentView
}
public func putBackContents() {
self.isTakenOut = false
self.containerView.addSubview(self.contentView)
if let params = self.params {
let _ = self.update(params: params, transition: .immediate)
}
}
public func asyncLayout() -> (_ placeholderString: NSAttributedString?, _ compactPlaceholderString: NSAttributedString?, _ constrainedSize: CGSize, _ expansionProgress: CGFloat, _ iconColor: UIColor, _ foregroundColor: UIColor, _ backgroundColor: UIColor, _ transition: ContainedViewLayoutTransition) -> (CGFloat, () -> Void) {
let labelLayout = TextNode.asyncLayout(self.labelNode)
let currentForegroundColor = self.foregroundColor
let currentIconColor = self.iconColor
public func updateLayout(placeholderString: NSAttributedString?, compactPlaceholderString: NSAttributedString?, constrainedSize: CGSize, expansionProgress: CGFloat, iconColor: UIColor, foregroundColor: UIColor, backgroundColor: UIColor, controlColor: UIColor, transition: ContainedViewLayoutTransition) -> CGFloat {
let params = Params(placeholderString: placeholderString, compactPlaceholderString: compactPlaceholderString, constrainedSize: constrainedSize, expansionProgress: expansionProgress, iconColor: iconColor, foregroundColor: foregroundColor, backgroundColor: backgroundColor, controlColor: controlColor)
self.params = params
return { fullPlaceholderString, compactPlaceholderString, constrainedSize, expansionProgress, iconColor, foregroundColor, backgroundColor, transition in
let placeholderString: NSAttributedString?
if constrainedSize.width < 350.0 {
placeholderString = compactPlaceholderString
} else {
placeholderString = fullPlaceholderString
}
let (labelLayoutResult, labelApply) = labelLayout(TextNodeLayoutArguments(attributedString: placeholderString, backgroundColor: .clear, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: constrainedSize, alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
var updatedColor: UIColor?
var updatedIconImage: UIImage?
if !currentForegroundColor.isEqual(foregroundColor) {
updatedColor = foregroundColor
}
if !currentIconColor.isEqual(iconColor) {
updatedIconImage = generateLoupeIcon(color: iconColor)
}
let height = constrainedSize.height * expansionProgress
return (height, { [weak self] in
if let strongSelf = self {
let _ = labelApply()
strongSelf.fillBackgroundColor = backgroundColor
strongSelf.foregroundColor = foregroundColor
strongSelf.iconColor = iconColor
strongSelf.backgroundNode.isUserInteractionEnabled = expansionProgress > 0.9999
if let updatedColor = updatedColor {
strongSelf.backgroundNode.backgroundColor = updatedColor
}
if let updatedIconImage = updatedIconImage {
strongSelf.iconNode.image = updatedIconImage
}
strongSelf.placeholderString = placeholderString
var iconSize = CGSize()
var totalWidth = labelLayoutResult.size.width
let spacing: CGFloat = 6.0
if let iconImage = strongSelf.iconNode.image {
iconSize = iconImage.size
totalWidth += iconSize.width + spacing
transition.updateFrame(node: strongSelf.iconNode, frame: CGRect(origin: CGPoint(x: floor((constrainedSize.width - totalWidth) / 2.0), y: floorToScreenPixels((height - iconSize.height) / 2.0)), size: iconSize))
}
var textOffset: CGFloat = 0.0
if constrainedSize.height >= 36.0 {
textOffset += 1.0
}
let labelFrame = CGRect(origin: CGPoint(x: floor((constrainedSize.width - totalWidth) / 2.0) + iconSize.width + spacing, y: floorToScreenPixels((height - labelLayoutResult.size.height) / 2.0) + textOffset), size: labelLayoutResult.size)
transition.updateFrame(node: strongSelf.labelNode, frame: labelFrame)
var innerAlpha = max(0.0, expansionProgress - 0.77) / 0.23
if innerAlpha > 0.9999 {
innerAlpha = 1.0
} else if innerAlpha < 0.0001 {
innerAlpha = 0.0
}
if strongSelf.labelNode.alpha != innerAlpha {
if !transition.isAnimated {
strongSelf.labelNode.layer.removeAnimation(forKey: "opacity")
strongSelf.iconNode.layer.removeAnimation(forKey: "opacity")
}
transition.updateAlpha(node: strongSelf.labelNode, alpha: innerAlpha)
transition.updateAlpha(node: strongSelf.iconNode, alpha: innerAlpha)
}
let outerAlpha = min(0.3, expansionProgress) / 0.3
let cornerRadius = min(strongSelf.fieldStyle.cornerDiameter / 2.0, height / 2.0)
if strongSelf.backgroundNode.cornerRadius != cornerRadius {
if !transition.isAnimated {
strongSelf.backgroundNode.layer.removeAnimation(forKey: "cornerRadius")
}
transition.updateCornerRadius(node: strongSelf.backgroundNode, cornerRadius: cornerRadius)
}
if strongSelf.backgroundNode.alpha != outerAlpha {
if !transition.isAnimated {
strongSelf.backgroundNode.layer.removeAnimation(forKey: "opacity")
}
transition.updateAlpha(node: strongSelf.backgroundNode, alpha: outerAlpha)
}
if strongSelf.backgroundNode.frame != CGRect(origin: CGPoint(), size: CGSize(width: constrainedSize.width, height: height)) {
if !transition.isAnimated {
strongSelf.backgroundNode.layer.removeAnimation(forKey: "position")
strongSelf.backgroundNode.layer.removeAnimation(forKey: "bounds")
}
transition.updateFrame(node: strongSelf.backgroundNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: constrainedSize.width, height: height)))
}
if let accessoryComponentContainer = strongSelf.accessoryComponentContainer {
accessoryComponentContainer.frame = CGRect(origin: CGPoint(x: constrainedSize.width - accessoryComponentContainer.bounds.width - 4.0, y: floor((constrainedSize.height * expansionProgress - accessoryComponentContainer.bounds.height) / 2.0)), size: accessoryComponentContainer.bounds.size)
transition.updateAlpha(layer: accessoryComponentContainer.layer, alpha: innerAlpha)
}
}
})
if self.isTakenOut {
return self.currentLayoutHeight ?? 44.0
} else {
let height = self.update(params: params, transition: transition)
self.currentLayoutHeight = height
return height
}
}
private func update(params: Params, transition: ContainedViewLayoutTransition) -> CGFloat {
let height = self.contentView.updateLayout(placeholderString: params.placeholderString, compactPlaceholderString: params.compactPlaceholderString, constrainedSize: params.constrainedSize, expansionProgress: params.expansionProgress, iconColor: params.iconColor, foregroundColor: params.foregroundColor, backgroundColor: params.backgroundColor, controlColor: params.controlColor, transition: transition)
let size = CGSize(width: params.constrainedSize.width, height: height)
transition.updateFrame(view: self.containerView, frame: CGRect(origin: CGPoint(), size: size))
transition.updateFrame(view: self.contentView, frame: CGRect(origin: CGPoint(), size: size))
return height
}
@objc private func backgroundTap(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
if case .ended = recognizer.state {
self.activate?()