diff --git a/submodules/Display/Source/AlertController.swift b/submodules/Display/Source/AlertController.swift index 4a90a66ac2..f57b2359ec 100644 --- a/submodules/Display/Source/AlertController.swift +++ b/submodules/Display/Source/AlertController.swift @@ -88,6 +88,7 @@ open class AlertController: ViewController, StandalonePresentableController { private weak var existingAlertController: AlertController? + public var willDismiss: (() -> Void)? public var dismissed: (() -> Void)? public init(theme: AlertControllerTheme, contentNode: AlertContentNode, existingAlertController: AlertController? = nil, allowInputInset: Bool = true) { @@ -115,6 +116,7 @@ open class AlertController: ViewController, StandalonePresentableController { self.controllerNode.dismiss = { [weak self] in if let strongSelf = self, strongSelf.contentNode.dismissOnOutsideTap { + strongSelf.willDismiss?() strongSelf.controllerNode.animateOut { self?.dismiss() } diff --git a/submodules/ImportStickerPackUI/Sources/ImportStickerPackTitleController.swift b/submodules/ImportStickerPackUI/Sources/ImportStickerPackTitleController.swift index d7a39ca318..d751952a9d 100644 --- a/submodules/ImportStickerPackUI/Sources/ImportStickerPackTitleController.swift +++ b/submodules/ImportStickerPackUI/Sources/ImportStickerPackTitleController.swift @@ -11,12 +11,179 @@ import AccountContext import UrlEscaping import ActivityIndicator -private final class ImportStickerPackTitleInputFieldNode: ASDisplayNode, ASEditableTextNodeDelegate { +private class TextField: UITextField, UIScrollViewDelegate { + fileprivate func updatePrefixWidth(_ prefixWidth: CGFloat) { + let previousPrefixWidth = self.prefixWidth + self.prefixWidth = prefixWidth + let leftOffset = prefixWidth + if let scrollView = self.scrollView { + if scrollView.contentInset.left != leftOffset { + scrollView.contentInset = UIEdgeInsets(top: 0.0, left: leftOffset, bottom: 0.0, right: 0.0) + } + if leftOffset.isZero { + scrollView.contentOffset = CGPoint() + } else if self.prefixWidth != previousPrefixWidth { + scrollView.contentOffset = CGPoint(x: -leftOffset, y: 0.0) + } + self.updatePrefixPosition(transition: .immediate) + } + } + + private var prefixWidth: CGFloat = 0.0 + + let prefixLabel: ImmediateTextNode + var prefixString: NSAttributedString? { + didSet { + self.prefixLabel.attributedText = self.prefixString + self.setNeedsLayout() + } + } + + init() { + self.prefixLabel = ImmediateTextNode() + self.prefixLabel.isUserInteractionEnabled = false + self.prefixLabel.displaysAsynchronously = false + self.prefixLabel.maximumNumberOfLines = 1 + self.prefixLabel.truncationMode = .byTruncatingTail + + super.init(frame: CGRect()) + + self.addSubnode(self.prefixLabel) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func addSubview(_ view: UIView) { + super.addSubview(view) + + if let scrollView = view as? UIScrollView { + scrollView.delegate = self + } + } + + private weak var _scrollView: UIScrollView? + var scrollView: UIScrollView? { + if let scrollView = self._scrollView { + return scrollView + } + for view in self.subviews { + if let scrollView = view as? UIScrollView { + _scrollView = scrollView + return scrollView + } + } + return nil + } + + override func deleteBackward() { + super.deleteBackward() + + if let scrollView = self.scrollView { + if scrollView.contentSize.width <= scrollView.frame.width && scrollView.contentOffset.x > -scrollView.contentInset.left { + scrollView.contentOffset = CGPoint(x: max(scrollView.contentOffset.x - 5.0, -scrollView.contentInset.left), y: 0.0) + self.updatePrefixPosition() + } + } + } + + var fixAutoScroll: CGPoint? + func scrollViewDidScroll(_ scrollView: UIScrollView) { + if let fixAutoScroll = self.fixAutoScroll { + self.scrollView?.setContentOffset(fixAutoScroll, animated: true) + self.scrollView?.setContentOffset(fixAutoScroll, animated: false) + self.fixAutoScroll = nil + } else { + self.updatePrefixPosition() + } + } + + override func becomeFirstResponder() -> Bool { + if let contentOffset = self.scrollView?.contentOffset { + self.fixAutoScroll = contentOffset + Queue.mainQueue().after(0.1) { + self.fixAutoScroll = nil + } + } + return super.becomeFirstResponder() + } + + private func updatePrefixPosition(transition: ContainedViewLayoutTransition = .immediate) { + if let scrollView = self.scrollView { + transition.updateFrame(node: self.prefixLabel, frame: CGRect(origin: CGPoint(x: -scrollView.contentOffset.x - scrollView.contentInset.left, y: self.prefixLabel.frame.minY), size: self.prefixLabel.frame.size)) + } + } + + override var keyboardAppearance: UIKeyboardAppearance { + get { + return super.keyboardAppearance + } + set { + let resigning = self.isFirstResponder + if resigning { + self.resignFirstResponder() + } + super.keyboardAppearance = newValue + if resigning { + let _ = self.becomeFirstResponder() + } + } + } + + override func textRect(forBounds bounds: CGRect) -> CGRect { + if bounds.size.width.isZero { + return CGRect(origin: CGPoint(), size: CGSize()) + } + var rect = bounds.insetBy(dx: 0.0, dy: 4.0) + if #available(iOS 14.0, *) { + } else { + rect.origin.y += 1.0 + } + if !self.prefixWidth.isZero && self.scrollView?.superview == nil { + var offset = self.prefixWidth + if let scrollView = self.scrollView { + offset = scrollView.contentOffset.x * -1.0 + } + rect.origin.x += offset + rect.size.width -= offset + } + rect.size.width = max(rect.size.width, 10.0) + return rect + } + + override func editingRect(forBounds bounds: CGRect) -> CGRect { + return self.textRect(forBounds: bounds) + } + + override func layoutSubviews() { + super.layoutSubviews() + + let bounds = self.bounds + if bounds.size.width.isZero { + return + } + + var placeholderOffset: CGFloat = 0.0 + if #available(iOS 14.0, *) { + placeholderOffset = 1.0 + } else { + } + + let textRect = self.textRect(forBounds: bounds) + + 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: floorToScreenPixels((bounds.height - prefixSize.height) / 2.0)), size: prefixSize) + self.updatePrefixWidth(prefixSize.width) + } +} + +private final class ImportStickerPackTitleInputFieldNode: ASDisplayNode, UITextFieldDelegate { private var theme: PresentationTheme private let backgroundNode: ASImageNode - private let textInputNode: EditableTextNode - private let placeholderNode: ASTextNode - private let prefixNode: ASTextNode +// private let textInputNode: EditableTextNode + private let textInputNode: TextField private let clearButton: HighlightableButtonNode var updateHeight: (() -> Void)? @@ -32,8 +199,7 @@ private final class ImportStickerPackTitleInputFieldNode: ASDisplayNode, ASEdita } set { self.textInputNode.attributedText = NSAttributedString(string: newValue, font: Font.regular(14.0), textColor: self.theme.actionSheet.inputTextColor) - self.placeholderNode.isHidden = !newValue.isEmpty - if self.textInputNode.isFirstResponder() { + if self.textInputNode.isFirstResponder { self.clearButton.isHidden = newValue.isEmpty } else { self.clearButton.isHidden = true @@ -41,15 +207,9 @@ private final class ImportStickerPackTitleInputFieldNode: ASDisplayNode, ASEdita } } - var placeholder: String = "" { - didSet { - self.placeholderNode.attributedText = NSAttributedString(string: self.placeholder, font: Font.regular(14.0), textColor: self.theme.actionSheet.inputPlaceholderColor) - } - } - var prefix: String = "" { didSet { - self.prefixNode.attributedText = NSAttributedString(string: self.prefix, font: Font.regular(14.0), textColor: self.theme.actionSheet.inputTextColor) + self.textInputNode.prefixString = NSAttributedString(string: self.prefix, font: Font.regular(14.0), textColor: self.theme.actionSheet.inputTextColor) } } @@ -66,33 +226,22 @@ private final class ImportStickerPackTitleInputFieldNode: ASDisplayNode, ASEdita self.maxLength = maxLength self.backgroundNode = ASImageNode() - self.backgroundNode.isLayerBacked = true self.backgroundNode.displaysAsynchronously = false self.backgroundNode.displayWithoutProcessing = true self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 12.0, color: theme.actionSheet.inputHollowBackgroundColor, strokeColor: theme.actionSheet.inputBorderColor, strokeWidth: 1.0) - self.textInputNode = EditableTextNode() - self.textInputNode.typingAttributes = [NSAttributedString.Key.font.rawValue: Font.regular(14.0), NSAttributedString.Key.foregroundColor.rawValue: theme.actionSheet.inputTextColor] + self.textInputNode = TextField() + self.textInputNode.font = Font.regular(14.0) + self.textInputNode.typingAttributes = [NSAttributedString.Key.font: Font.regular(14.0), NSAttributedString.Key.foregroundColor: theme.actionSheet.inputTextColor] self.textInputNode.clipsToBounds = true - self.textInputNode.hitTestSlop = UIEdgeInsets(top: -5.0, left: -5.0, bottom: -5.0, right: -5.0) - self.textInputNode.textContainerInset = UIEdgeInsets(top: self.inputInsets.top, left: 0.0, bottom: self.inputInsets.bottom, right: 0.0) +// self.textInputNode.textContainerInset = UIEdgeInsets(top: self.inputInsets.top, left: 0.0, bottom: self.inputInsets.bottom, right: 0.0) self.textInputNode.keyboardAppearance = theme.rootController.keyboardColor.keyboardAppearance self.textInputNode.keyboardType = keyboardType self.textInputNode.autocapitalizationType = .sentences self.textInputNode.returnKeyType = returnKeyType self.textInputNode.autocorrectionType = .default self.textInputNode.tintColor = theme.actionSheet.controlAccentColor - - self.placeholderNode = ASTextNode() - self.placeholderNode.isUserInteractionEnabled = false - self.placeholderNode.displaysAsynchronously = false - self.placeholderNode.attributedText = NSAttributedString(string: placeholder, font: Font.regular(14.0), textColor: self.theme.actionSheet.inputPlaceholderColor) - - self.prefixNode = ASTextNode() - self.prefixNode.isUserInteractionEnabled = false - self.prefixNode.displaysAsynchronously = false - self.prefixNode.attributedText = NSAttributedString(string: placeholder, font: Font.regular(14.0), textColor: self.theme.actionSheet.inputPlaceholderColor) - + self.clearButton = HighlightableButtonNode() self.clearButton.imageNode.displaysAsynchronously = false self.clearButton.imageNode.displayWithoutProcessing = true @@ -101,24 +250,29 @@ private final class ImportStickerPackTitleInputFieldNode: ASDisplayNode, ASEdita self.clearButton.isHidden = true super.init() - - self.textInputNode.delegate = self - + self.addSubnode(self.backgroundNode) - self.addSubnode(self.textInputNode) - self.addSubnode(self.placeholderNode) - self.addSubnode(self.prefixNode) self.addSubnode(self.clearButton) self.clearButton.addTarget(self, action: #selector(self.clearPressed), forControlEvents: .touchUpInside) } + override func didLoad() { + super.didLoad() + + self.textInputNode.delegate = self + self.view.insertSubview(self.textInputNode, aboveSubview: self.backgroundNode.view) + } + + func selectAll() { + self.textInputNode.selectAll(nil) + } + func updateTheme(_ theme: PresentationTheme) { self.theme = theme self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 12.0, color: self.theme.actionSheet.inputHollowBackgroundColor, strokeColor: self.theme.actionSheet.inputBorderColor, strokeWidth: 1.0) self.textInputNode.keyboardAppearance = self.theme.rootController.keyboardColor.keyboardAppearance - self.placeholderNode.attributedText = NSAttributedString(string: self.placeholderNode.attributedText?.string ?? "", font: Font.regular(17.0), textColor: self.theme.actionSheet.inputPlaceholderColor) self.textInputNode.tintColor = self.theme.actionSheet.controlAccentColor self.clearButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Clear"), color: theme.actionSheet.inputClearButtonColor), for: []) } @@ -133,13 +287,7 @@ private final class ImportStickerPackTitleInputFieldNode: ASDisplayNode, ASEdita let backgroundFrame = CGRect(origin: CGPoint(x: backgroundInsets.left, y: backgroundInsets.top), size: CGSize(width: width - backgroundInsets.left - backgroundInsets.right, height: panelHeight - backgroundInsets.top - backgroundInsets.bottom)) transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame) - let placeholderSize = self.placeholderNode.measure(backgroundFrame.size) - transition.updateFrame(node: self.placeholderNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + inputInsets.left, y: backgroundFrame.minY + floor((backgroundFrame.size.height - placeholderSize.height) / 2.0)), size: placeholderSize)) - - let prefixSize = self.prefixNode.measure(backgroundFrame.size) - transition.updateFrame(node: self.prefixNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + inputInsets.left, y: backgroundFrame.minY + floor((backgroundFrame.size.height - prefixSize.height) / 2.0)), size: prefixSize)) - - transition.updateFrame(node: self.textInputNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + inputInsets.left + prefixSize.width, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.size.width - inputInsets.left - inputInsets.right - 20.0, height: backgroundFrame.size.height))) + transition.updateFrame(view: self.textInputNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + inputInsets.left, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.size.width - inputInsets.left - inputInsets.right - 20.0, height: backgroundFrame.size.height))) if let image = self.clearButton.image(for: []) { transition.updateFrame(node: self.clearButton, frame: CGRect(origin: CGPoint(x: backgroundFrame.maxX - 8.0 - image.size.width, y: backgroundFrame.minY + floor((backgroundFrame.size.height - image.size.height) / 2.0)), size: image.size)) @@ -149,51 +297,46 @@ private final class ImportStickerPackTitleInputFieldNode: ASDisplayNode, ASEdita } func activateInput() { - self.textInputNode.becomeFirstResponder() + let _ = self.textInputNode.becomeFirstResponder() } func deactivateInput() { self.textInputNode.resignFirstResponder() } - @objc func editableTextNodeDidUpdateText(_ editableTextNode: ASEditableTextNode) { - self.updateTextNodeText(animated: true) - self.textChanged?(editableTextNode.textView.text) - self.placeholderNode.isHidden = !(editableTextNode.textView.text ?? "").isEmpty - self.clearButton.isHidden = !self.placeholderNode.isHidden + func textFieldDidBeginEditing(_ textField: UITextField) { + self.clearButton.isHidden = (textField.text ?? "").isEmpty } - func editableTextNodeDidBeginEditing(_ editableTextNode: ASEditableTextNode) { - self.clearButton.isHidden = (editableTextNode.textView.text ?? "").isEmpty - } - - func editableTextNodeDidFinishEditing(_ editableTextNode: ASEditableTextNode) { + func textFieldDidEndEditing(_ textField: UITextField) { self.clearButton.isHidden = true } - func editableTextNode(_ editableTextNode: ASEditableTextNode, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { + func textFieldDidUpdateText(_ text: String) { + self.updateTextNodeText(animated: true) + self.textChanged?(text) + self.clearButton.isHidden = (text).isEmpty + } + + func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { if self.disabled { return false } - let updatedText = (editableTextNode.textView.text as NSString).replacingCharacters(in: range, with: text) + let updatedText = ((textField.text ?? "") as NSString).replacingCharacters(in: range, with: string) if updatedText.count > maxLength { self.textInputNode.layer.addShakeAnimation() return false } - if text == "\n" { + if string == "\n" { self.complete?() return false } + self.textFieldDidUpdateText(updatedText) return true } private func calculateTextFieldMetrics(width: CGFloat) -> CGFloat { - let backgroundInsets = self.backgroundInsets - let inputInsets = self.inputInsets - - let unboundTextFieldHeight = max(33.0, ceil(self.textInputNode.measure(CGSize(width: width - backgroundInsets.left - backgroundInsets.right - inputInsets.left - inputInsets.right - 20.0, height: CGFloat.greatestFiniteMagnitude)).height)) - - return min(61.0, max(33.0, unboundTextFieldHeight)) + return 33.0 } private func updateTextNodeText(animated: Bool) { @@ -208,11 +351,11 @@ private final class ImportStickerPackTitleInputFieldNode: ASDisplayNode, ASEdita } @objc func clearPressed() { - self.placeholderNode.isHidden = false self.clearButton.isHidden = true self.textInputNode.attributedText = nil self.updateHeight?() + self.textChanged?("") } } @@ -552,6 +695,9 @@ func importStickerPackTitleController(context: AccountContext, title: String, te contentNode.inputFieldNode.textChanged = { [weak contentNode] title in contentNode?.actionNodes.last?.actionEnabled = !title.trimmingTrailingSpaces().isEmpty } + controller.willDismiss = { [weak contentNode] in + contentNode?.inputFieldNode.deactivateInput() + } controller.dismissed = { presentationDataDisposable.dispose() } @@ -603,6 +749,11 @@ func importStickerPackShortNameController(context: AccountContext, title: String let checkDisposable = MetaDisposable() var value = value ?? "" contentNode.actionNodes.last?.actionEnabled = !value.isEmpty + if !value.isEmpty { + Queue.mainQueue().after(0.25) { + contentNode.inputFieldNode.selectAll() + } + } contentNode.inputFieldNode.textChanged = { [weak contentNode] value in if value.isEmpty { checkDisposable.set(nil) @@ -634,6 +785,9 @@ func importStickerPackShortNameController(context: AccountContext, title: String })) } } + controller.willDismiss = { [weak contentNode] in + contentNode?.inputFieldNode.deactivateInput() + } controller.dismissed = { presentationDataDisposable.dispose() } diff --git a/submodules/TelegramUI/Sources/FetchCachedRepresentations.swift b/submodules/TelegramUI/Sources/FetchCachedRepresentations.swift index 55fc7edd11..b47047ed4e 100644 --- a/submodules/TelegramUI/Sources/FetchCachedRepresentations.swift +++ b/submodules/TelegramUI/Sources/FetchCachedRepresentations.swift @@ -168,7 +168,13 @@ private func videoFirstFrameData(account: Account, resource: MediaResource, chun private func fetchCachedStickerAJpegRepresentation(account: Account, resource: MediaResource, resourceData: MediaResourceData, representation: CachedStickerAJpegRepresentation) -> Signal { return Signal({ subscriber in if let data = try? Data(contentsOf: URL(fileURLWithPath: resourceData.path), options: [.mappedIfSafe]) { - if let image = WebP.convert(fromWebP: data) { + var image: UIImage? + if let webpImage = WebP.convert(fromWebP: data) { + image = webpImage + } else if let pngImage = UIImage(data: data) { + image = pngImage + } + if let image = image { let path = NSTemporaryDirectory() + "\(Int64.random(in: Int64.min ... Int64.max))" let url = URL(fileURLWithPath: path)