Finalize timestamp sharing

This commit is contained in:
Isaac
2025-01-24 21:03:39 +04:00
parent d0b5f8b400
commit c21ebb06b5
21 changed files with 696 additions and 43 deletions

View File

@@ -4,6 +4,9 @@ import AsyncDisplayKit
import Display
import TelegramPresentationData
import AppBundle
import ComponentFlow
import MultilineTextComponent
import AnimatedTextComponent
private func generateClearIcon(color: UIColor) -> UIImage? {
return generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Clear"), color: color)
@@ -55,19 +58,154 @@ public extension ShareInputFieldNodeTheme {
}
}
private final class ShareInputCopyComponent: Component {
let theme: ShareInputFieldNodeTheme
let strings: PresentationStrings
let text: String
let action: () -> Void
init(
theme: ShareInputFieldNodeTheme,
strings: PresentationStrings,
text: String,
action: @escaping () -> Void
) {
self.theme = theme
self.strings = strings
self.text = text
self.action = action
}
static func ==(lhs: ShareInputCopyComponent, rhs: ShareInputCopyComponent) -> Bool {
if lhs.theme !== rhs.theme {
return false
}
if lhs.strings !== rhs.strings {
return false
}
if lhs.text != rhs.text {
return false
}
return true
}
final class View: UIView {
let text = ComponentView<Empty>()
let button = ComponentView<Empty>()
let textMask = UIImageView()
var component: ShareInputCopyComponent?
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func update(component: ShareInputCopyComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
let textChanged = self.component != nil && self.component?.text != component.text
self.component = component
var textItems: [AnimatedTextComponent.Item] = []
if let range = component.text.range(of: "?", options: .backwards) {
textItems.append(AnimatedTextComponent.Item(id: 0, isUnbreakable: true, content: .text(String(component.text[component.text.startIndex ..< range.lowerBound]))))
textItems.append(AnimatedTextComponent.Item(id: 1, isUnbreakable: true, content: .text(String(component.text[range.lowerBound...]))))
} else {
textItems.append(AnimatedTextComponent.Item(id: 0, isUnbreakable: true, content: .text(component.text)))
}
let sideInset: CGFloat = 12.0
let textSize = self.text.update(
transition: textChanged ? .spring(duration: 0.4) : .immediate,
component: AnyComponent(AnimatedTextComponent(
font: Font.regular(17.0),
color: component.theme.textColor,
items: textItems,
animateScale: false
)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 1000.0)
)
let textFrame = CGRect(origin: CGPoint(x: sideInset, y: floor((availableSize.height - textSize.height) * 0.5)), size: textSize)
if let textView = self.text.view {
if textView.superview == nil {
self.addSubview(textView)
textView.mask = self.textMask
}
textView.frame = textFrame
}
let buttonSize = self.button.update(
transition: .immediate,
component: AnyComponent(Button(
content: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: component.strings.Conversation_LinkDialogCopy, font: Font.regular(17.0), textColor: component.theme.accentColor))
)),
action: { [weak self] in
guard let self else {
return
}
self.component?.action()
}
).minSize(CGSize(width: 0.0, height: availableSize.height))),
environment: {},
containerSize: CGSize(width: availableSize.width - 40.0, height: 1000.0)
)
let buttonFrame = CGRect(origin: CGPoint(x: availableSize.width - sideInset - buttonSize.width, y: floor((availableSize.height - buttonSize.height) * 0.5)), size: buttonSize)
if let buttonView = self.button.view {
if buttonView.superview == nil {
self.addSubview(buttonView)
}
buttonView.frame = buttonFrame
}
if self.textMask.image == nil {
let gradientWidth: CGFloat = 26.0
self.textMask.image = generateGradientImage(size: CGSize(width: gradientWidth, height: 8.0), colors: [
UIColor(white: 1.0, alpha: 1.0),
UIColor(white: 1.0, alpha: 1.0),
UIColor(white: 1.0, alpha: 0.0)
], locations: [
0.0,
1.0 / gradientWidth,
1.0
], direction: .horizontal)?.resizableImage(withCapInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: gradientWidth - 1.0), resizingMode: .stretch)
self.textMask.frame = CGRect(origin: CGPoint(), size: CGSize(width: max(0.0, buttonFrame.minX - 4.0 - textFrame.minX), height: textFrame.height))
}
return availableSize
}
}
func makeView() -> View {
return View(frame: CGRect())
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}
public final class ShareInputFieldNode: ASDisplayNode, ASEditableTextNodeDelegate {
private let theme: ShareInputFieldNodeTheme
private let strings: PresentationStrings
private let backgroundNode: ASImageNode
private let textInputNode: EditableTextNode
private let placeholderNode: ASTextNode
private let clearButton: HighlightableButtonNode
private var copyView: ComponentView<Empty>?
public var updateHeight: (() -> Void)?
public var updateText: ((String) -> Void)?
private let backgroundInsets = UIEdgeInsets(top: 16.0, left: 16.0, bottom: 1.0, right: 16.0)
private let inputInsets = UIEdgeInsets(top: 10.0, left: 8.0, bottom: 10.0, right: 22.0)
private let accessoryButtonsWidth: CGFloat = 10.0
private var inputCopyText: String?
public var onInputCopyText: (() -> Void)?
private var selectTextOnce: Bool = false
@@ -77,7 +215,7 @@ public final class ShareInputFieldNode: ASDisplayNode, ASEditableTextNodeDelegat
}
set {
self.textInputNode.attributedText = NSAttributedString(string: newValue, font: Font.regular(17.0), textColor: self.theme.textColor)
self.placeholderNode.isHidden = !newValue.isEmpty
self.placeholderNode.isHidden = !newValue.isEmpty || self.inputCopyText != nil
self.clearButton.isHidden = newValue.isEmpty
}
}
@@ -88,8 +226,9 @@ public final class ShareInputFieldNode: ASDisplayNode, ASEditableTextNodeDelegat
}
}
public init(theme: ShareInputFieldNodeTheme, placeholder: String) {
public init(theme: ShareInputFieldNodeTheme, strings: PresentationStrings, placeholder: String) {
self.theme = theme
self.strings = strings
self.backgroundNode = ASImageNode()
self.backgroundNode.isLayerBacked = true
@@ -136,10 +275,11 @@ public final class ShareInputFieldNode: ASDisplayNode, ASEditableTextNodeDelegat
self.selectTextOnce = true
}
public func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
public func updateLayout(width: CGFloat, inputCopyText: String?, transition: ContainedViewLayoutTransition) -> CGFloat {
let backgroundInsets = self.backgroundInsets
let inputInsets = self.inputInsets
let accessoryButtonsWidth = self.accessoryButtonsWidth
self.inputCopyText = inputCopyText
let textFieldHeight = self.calculateTextFieldMetrics(width: width)
let panelHeight = textFieldHeight + backgroundInsets.top + backgroundInsets.bottom
@@ -156,6 +296,43 @@ public final class ShareInputFieldNode: ASDisplayNode, ASEditableTextNodeDelegat
transition.updateFrame(node: self.textInputNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + inputInsets.left, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.size.width - inputInsets.left - inputInsets.right - accessoryButtonsWidth, height: backgroundFrame.size.height)))
self.textInputNode.isUserInteractionEnabled = inputCopyText == nil
self.textInputNode.isHidden = inputCopyText != nil
self.placeholderNode.isHidden = !(self.textInputNode.textView.text ?? "").isEmpty || self.inputCopyText != nil
if let inputCopyText {
let copyView: ComponentView<Empty>
if let current = self.copyView {
copyView = current
} else {
copyView = ComponentView()
self.copyView = copyView
}
let copyViewSize = copyView.update(
transition: .immediate,
component: AnyComponent(ShareInputCopyComponent(
theme: self.theme,
strings: self.strings,
text: inputCopyText,
action: {
self.onInputCopyText?()
}
)),
environment: {},
containerSize: backgroundFrame.size
)
let copyViewFrame = CGRect(origin: backgroundFrame.origin, size: copyViewSize)
if let copyComponentView = copyView.view {
if copyComponentView.superview == nil {
self.view.addSubview(copyComponentView)
}
copyComponentView.frame = copyViewFrame
}
} else if let copyView = self.copyView {
self.copyView = nil
copyView.view?.removeFromSuperview()
}
return panelHeight
}
@@ -170,7 +347,7 @@ public final class ShareInputFieldNode: ASDisplayNode, ASEditableTextNodeDelegat
@objc public func editableTextNodeDidUpdateText(_ editableTextNode: ASEditableTextNode) {
self.updateTextNodeText(animated: true)
self.updateText?(editableTextNode.attributedText?.string ?? "")
self.placeholderNode.isHidden = !(editableTextNode.textView.text ?? "").isEmpty
self.placeholderNode.isHidden = !(editableTextNode.textView.text ?? "").isEmpty || self.inputCopyText != nil
}
public func editableTextNodeDidBeginEditing(_ editableTextNode: ASEditableTextNode) {
@@ -185,7 +362,7 @@ public final class ShareInputFieldNode: ASDisplayNode, ASEditableTextNodeDelegat
}
public func editableTextNodeDidFinishEditing(_ editableTextNode: ASEditableTextNode) {
self.placeholderNode.isHidden = !(editableTextNode.textView.text ?? "").isEmpty
self.placeholderNode.isHidden = !(editableTextNode.textView.text ?? "").isEmpty || self.inputCopyText != nil
self.clearButton.isHidden = true
}
@@ -194,9 +371,13 @@ public final class ShareInputFieldNode: ASDisplayNode, ASEditableTextNodeDelegat
let inputInsets = self.inputInsets
let accessoryButtonsWidth = self.accessoryButtonsWidth
let unboundTextFieldHeight = max(33.0, ceil(self.textInputNode.measure(CGSize(width: width - backgroundInsets.left - backgroundInsets.right - inputInsets.left - inputInsets.right - accessoryButtonsWidth, height: CGFloat.greatestFiniteMagnitude)).height))
return min(61.0, max(41.0, unboundTextFieldHeight))
if self.inputCopyText != nil {
return 41.0
} else {
let unboundTextFieldHeight = max(33.0, ceil(self.textInputNode.measure(CGSize(width: width - backgroundInsets.left - backgroundInsets.right - inputInsets.left - inputInsets.right - accessoryButtonsWidth, height: CGFloat.greatestFiniteMagnitude)).height))
return min(61.0, max(41.0, unboundTextFieldHeight))
}
}
private func updateTextNodeText(animated: Bool) {