|  |  |  | // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import UIKit | 
					
						
							|  |  |  | import SessionUIKit | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | public final class InputTextView: UITextView, UITextViewDelegate { | 
					
						
							|  |  |  |     private weak var snDelegate: InputTextViewDelegate? | 
					
						
							|  |  |  |     private let maxWidth: CGFloat | 
					
						
							|  |  |  |     private lazy var heightConstraint = self.set(.height, to: minHeight) | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     public override var text: String? { didSet { handleTextChanged() } } | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // MARK: - UI Components | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     private lazy var placeholderLabel: UILabel = { | 
					
						
							|  |  |  |         let result = UILabel() | 
					
						
							|  |  |  |         result.font = .systemFont(ofSize: Values.mediumFontSize) | 
					
						
							|  |  |  |         result.text = "vc_conversation_input_prompt".localized() | 
					
						
							|  |  |  |         result.themeTextColor = .textSecondary | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         return result | 
					
						
							|  |  |  |     }() | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     // MARK: - Settings | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     private let minHeight: CGFloat = 22 | 
					
						
							|  |  |  |     private let maxHeight: CGFloat = 80 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // MARK: - Lifecycle | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     init(delegate: InputTextViewDelegate, maxWidth: CGFloat) { | 
					
						
							|  |  |  |         snDelegate = delegate | 
					
						
							|  |  |  |         self.maxWidth = maxWidth | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         super.init(frame: CGRect.zero, textContainer: nil) | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         setUpViewHierarchy() | 
					
						
							|  |  |  |         self.delegate = self | 
					
						
							|  |  |  |         self.isAccessibilityElement = true | 
					
						
							|  |  |  |         self.accessibilityLabel = "vc_conversation_input_prompt".localized() | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     public override init(frame: CGRect, textContainer: NSTextContainer?) { | 
					
						
							|  |  |  |         preconditionFailure("Use init(delegate:) instead.") | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     public required init?(coder: NSCoder) { | 
					
						
							|  |  |  |         preconditionFailure("Use init(delegate:) instead.") | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     public override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool { | 
					
						
							|  |  |  |         if action == #selector(paste(_:)) { | 
					
						
							|  |  |  |             if UIPasteboard.general.hasImages { | 
					
						
							|  |  |  |                 return true | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return super.canPerformAction(action, withSender: sender) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     public override func paste(_ sender: Any?) { | 
					
						
							|  |  |  |         if let image = UIPasteboard.general.image { | 
					
						
							|  |  |  |             snDelegate?.didPasteImageFromPasteboard(self, image: image) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         super.paste(sender) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     private func setUpViewHierarchy() { | 
					
						
							|  |  |  |         showsHorizontalScrollIndicator = false | 
					
						
							|  |  |  |         showsVerticalScrollIndicator = false | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         font = .systemFont(ofSize: Values.mediumFontSize) | 
					
						
							|  |  |  |         themeBackgroundColor = .clear | 
					
						
							|  |  |  |         themeTextColor = .textPrimary | 
					
						
							|  |  |  |         themeTintColor = .primary | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         heightConstraint.isActive = true | 
					
						
							|  |  |  |         let horizontalInset: CGFloat = 2 | 
					
						
							|  |  |  |         textContainerInset = UIEdgeInsets(top: 0, left: horizontalInset, bottom: 0, right: horizontalInset) | 
					
						
							|  |  |  |         addSubview(placeholderLabel) | 
					
						
							|  |  |  |         placeholderLabel.pin(.leading, to: .leading, of: self, withInset: horizontalInset + 3) // Slight visual adjustment | 
					
						
							|  |  |  |         placeholderLabel.pin(.top, to: .top, of: self) | 
					
						
							|  |  |  |         pin(.trailing, to: .trailing, of: placeholderLabel, withInset: horizontalInset) | 
					
						
							|  |  |  |         pin(.bottom, to: .bottom, of: placeholderLabel) | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         ThemeManager.onThemeChange(observer: self) { [weak self] theme, _ in | 
					
						
							|  |  |  |             switch theme.interfaceStyle { | 
					
						
							|  |  |  |                 case .light: self?.keyboardAppearance = .light | 
					
						
							|  |  |  |                 default: self?.keyboardAppearance = .dark | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // MARK: - Updating | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     public func textViewDidChange(_ textView: UITextView) { | 
					
						
							|  |  |  |         handleTextChanged() | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     private func handleTextChanged() { | 
					
						
							|  |  |  |         defer { snDelegate?.inputTextViewDidChangeContent(self) } | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         placeholderLabel.isHidden = !(text ?? "").isEmpty | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         let height = frame.height | 
					
						
							|  |  |  |         let size = sizeThatFits(CGSize(width: maxWidth, height: .greatestFiniteMagnitude)) | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         // `textView.contentSize` isn't accurate when restoring a multiline draft, so we set it here manually | 
					
						
							|  |  |  |         self.contentSize = size | 
					
						
							|  |  |  |         let newHeight = size.height.clamp(minHeight, maxHeight) | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         guard newHeight != height else { return } | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         heightConstraint.constant = newHeight | 
					
						
							|  |  |  |         snDelegate?.inputTextViewDidChangeSize(self) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // MARK: - InputTextViewDelegate | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | protocol InputTextViewDelegate: AnyObject { | 
					
						
							|  |  |  |     func inputTextViewDidChangeSize(_ inputTextView: InputTextView) | 
					
						
							|  |  |  |     func inputTextViewDidChangeContent(_ inputTextView: InputTextView) | 
					
						
							|  |  |  |     func didPasteImageFromPasteboard(_ inputTextView: InputTextView, image: UIImage) | 
					
						
							|  |  |  | } |