This commit is contained in:
Isaac
2025-12-12 23:58:00 +08:00
parent 3982aa3f9e
commit a63c90aa00
41 changed files with 550 additions and 503 deletions

View File

@@ -894,6 +894,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
@@ -972,6 +975,9 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate {
if let takenSearchPlaceholderContentView = self.takenSearchPlaceholderContentView {
takenSearchPlaceholderContentView.updateSearchIconVisibility(isVisible: !self.activity)
}
if let inlineSearchPlaceholderContentsView = self.inlineSearchPlaceholderContentsView {
inlineSearchPlaceholderContentsView.updateSearchIconVisibility(isVisible: !self.activity)
}
}
}
}
@@ -999,14 +1005,21 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate {
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
@@ -1043,7 +1056,9 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate {
super.init()
switch self.fieldStyle {
case .glass, .inlineNavigation:
case .glass:
break
case .inlineNavigation:
break
case .legacy, .modern:
self.addSubnode(self.backgroundNode)
@@ -1085,11 +1100,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
@@ -1122,6 +1137,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)
@@ -1184,6 +1200,45 @@ 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 searchPlaceholderFrame = CGRect(origin: CGPoint(x: 16.0, y: 0.0), size: CGSize(width: max(0.0, boundingSize.width - 16.0 * 2.0), height: 44.0))
if case .glass = self.fieldStyle, self.takenSearchPlaceholderContentView == nil {
transition.updateFrame(node: self.inlineSearchPlaceholder, frame: searchPlaceholderFrame)
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 {
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, transition: transition)
transition.updateFrame(view: inlineSearchPlaceholderContentsView, frame: searchPlaceholderFrame)
}
}
if !self.isAnimatingOut, let takenSearchPlaceholderContentView = self.takenSearchPlaceholderContentView {
transition.updateFrame(view: takenSearchPlaceholderContentView, frame: searchPlaceholderFrame)
takenSearchPlaceholderContentView.update(size: searchPlaceholderFrame.size, isActive: true, transition: transition)
}
}
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
@@ -1201,6 +1256,8 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate {
}
public func animateIn(from node: SearchBarPlaceholderNode, duration: Double, timingFunction: String) {
self.inlineSearchPlaceholder.isHidden = true
let takenSearchPlaceholderContentView = node.takeContents()
takenSearchPlaceholderContentView.onCancel = { [weak self] in
guard let self else {
@@ -1210,6 +1267,9 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate {
}
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: 16.0, y: 0.0), size: CGSize(width: max(0.0, self.bounds.width - 16.0 * 2.0), height: 44.0))
@@ -1271,6 +1331,8 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate {
}
public func transitionOut(to node: SearchBarPlaceholderNode, transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) {
self.isAnimatingOut = true
/*let targetTextBackgroundFrame = node.view.convert(node.backgroundView.frame, to: self.view)
let duration: Double = transition.isAnimated ? 0.5 : 0.0
@@ -1531,6 +1593,7 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate {
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)

View File

@@ -49,13 +49,15 @@ public final class SearchBarPlaceholderContentView: UIView {
}
let fieldStyle: SearchBarStyle
let backgroundNode: ASDisplayNode?
let plainBackgroundView: UIImageView
let glassBackgroundView: GlassBackgroundView?
private var fillBackgroundColor: UIColor
private var foregroundColor: UIColor
private var iconColor: UIColor
let iconNode: ASImageNode
let labelNode: TextNode
let plainIconNode: ASImageNode
let plainLabelNode: TextNode
private var close: (background: GlassBackgroundView, icon: UIImageView)?
@@ -72,20 +74,13 @@ public final class SearchBarPlaceholderContentView: UIView {
self.foregroundColor = UIColor(rgb: 0xededed)
self.iconColor = UIColor(rgb: 0x000000, alpha: 0.0)
self.plainBackgroundView = UIImageView()
switch fieldStyle {
case .legacy, .modern:
let backgroundNode = ASDisplayNode()
backgroundNode.isLayerBacked = false
backgroundNode.displaysAsynchronously = false
backgroundNode.backgroundColor = self.foregroundColor
backgroundNode.cornerRadius = self.fieldStyle.cornerDiameter / 2.0
self.backgroundNode = backgroundNode
self.glassBackgroundView = nil
case .inlineNavigation, .glass:
self.glassBackgroundView = GlassBackgroundView()
self.backgroundNode = nil
}
self.iconNode = ASImageNode()
@@ -96,20 +91,27 @@ public final class SearchBarPlaceholderContentView: UIView {
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())
if let backgroundNode = self.backgroundNode {
backgroundNode.isUserInteractionEnabled = true
self.addSubview(backgroundNode.view)
}
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)
} else {
self.addSubview(self.iconNode.view)
self.addSubview(self.labelNode.view)
}
}
@@ -161,6 +163,7 @@ public final class SearchBarPlaceholderContentView: UIView {
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
@@ -172,6 +175,7 @@ public final class SearchBarPlaceholderContentView: UIView {
}
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?
@@ -185,19 +189,19 @@ public final class SearchBarPlaceholderContentView: UIView {
let height = params.constrainedSize.height * params.expansionProgress
let _ = labelApply()
let _ = plainLabelApply()
self.fillBackgroundColor = params.backgroundColor
self.foregroundColor = params.foregroundColor
self.iconColor = params.iconColor
if let backgroundNode = self.backgroundNode {
backgroundNode.isUserInteractionEnabled = params.expansionProgress > 0.9999
}
self.plainBackgroundView.isUserInteractionEnabled = params.expansionProgress > 0.9999
if let updatedColor, let backgroundNode = self.backgroundNode {
backgroundNode.backgroundColor = updatedColor
if let updatedColor {
self.plainBackgroundView.backgroundColor = updatedColor
}
if let updatedIconImage {
self.iconNode.image = updatedIconImage
self.plainIconNode.image = updatedIconImage
}
self.placeholderString = placeholderString
@@ -220,6 +224,7 @@ public final class SearchBarPlaceholderContentView: UIView {
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
}
@@ -230,6 +235,7 @@ public final class SearchBarPlaceholderContentView: UIView {
let labelX: CGFloat = iconX + iconSize.width + spacing
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 {
@@ -241,27 +247,36 @@ public final class SearchBarPlaceholderContentView: UIView {
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 = min(self.fieldStyle.cornerDiameter / 2.0, height / 2.0)
let cornerRadius = height * 0.5
if let backgroundNode = self.backgroundNode, backgroundNode.cornerRadius != cornerRadius {
if self.plainBackgroundView.layer.cornerRadius != cornerRadius {
if !transition.isAnimated {
backgroundNode.layer.removeAnimation(forKey: "cornerRadius")
self.plainBackgroundView.layer.removeAnimation(forKey: "cornerRadius")
}
transition.updateCornerRadius(node: backgroundNode, cornerRadius: cornerRadius)
transition.updateCornerRadius(layer: self.plainBackgroundView.layer, cornerRadius: cornerRadius)
}
if let backgroundNode = self.backgroundNode, backgroundNode.alpha != outerAlpha {
var plainBackgroundAlpha = outerAlpha
if params.isActive {
plainBackgroundAlpha = 0.0
}
if self.plainBackgroundView.alpha != plainBackgroundAlpha {
if !transition.isAnimated {
backgroundNode.layer.removeAnimation(forKey: "opacity")
self.plainBackgroundView.layer.removeAnimation(forKey: "opacity")
}
transition.updateAlpha(node: backgroundNode, alpha: outerAlpha)
transition.updateAlpha(layer: self.plainBackgroundView.layer, alpha: plainBackgroundAlpha)
}
var backgroundFrame = CGRect(origin: CGPoint(), size: CGSize(width: params.constrainedSize.width, height: height))
@@ -269,12 +284,12 @@ public final class SearchBarPlaceholderContentView: UIView {
backgroundFrame.size.width -= 44.0 + 8.0
}
if let backgroundNode = self.backgroundNode, backgroundNode.frame != backgroundFrame {
if self.plainBackgroundView.frame != backgroundFrame {
if !transition.isAnimated {
backgroundNode.layer.removeAnimation(forKey: "position")
backgroundNode.layer.removeAnimation(forKey: "bounds")
self.plainBackgroundView.layer.removeAnimation(forKey: "position")
self.plainBackgroundView.layer.removeAnimation(forKey: "bounds")
}
transition.updateFrame(node: backgroundNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: params.constrainedSize.width, height: height)))
transition.updateFrame(view: self.plainBackgroundView, frame: CGRect(origin: CGPoint(), size: CGSize(width: params.constrainedSize.width, height: height)))
}
if let glassBackgroundView = self.glassBackgroundView {
@@ -284,6 +299,9 @@ public final class SearchBarPlaceholderContentView: UIView {
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
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))
@@ -372,10 +390,12 @@ public final class SearchBarPlaceholderContentView: UIView {
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
}
}
@@ -408,12 +428,10 @@ public class SearchBarPlaceholderNode: ASDisplayNode {
private let contentView: SearchBarPlaceholderContentView
public var backgroundView: UIView {
if let backgroundNode = self.contentView.backgroundNode {
return backgroundNode.view
} else if let glassBackgroundView = self.contentView.glassBackgroundView {
if let glassBackgroundView = self.contentView.glassBackgroundView {
return glassBackgroundView
} else {
preconditionFailure()
return self.contentView.plainBackgroundView
}
}