mirror of
https://github.com/lux-tel/session-ios.git
synced 2026-10-30 20:38:32 +00:00
• Tweaked the way gesture recognisers were setup in the message bubbles so they can now be based on the message type rather than the cell type • Fixed an annoying duplicate symbols build issue caused by the libSession build script • Fixed a bug with the styling of quotes • Fixed a bug where quotes that were missing author information could incorrectly show "You" as the author • Fixed an issue where the "Session Pro" menu item was visible when the feature was disabled (not currently connected to anything) • Fixed an issue where the disabled message input box wouldn't show errors correctly • Fixed an issue where message requests received via a community wouldn't result in the message request banner appearing • Fixed an issue where long pressing on a link in a message would show both the open link modal and the long press menu • Fixed an issue where outgoing voice messages wouldn't play correctly (incorrectly considered invalid attachments) • Fixed an issue where you couldn't paste images directly into the message input
132 lines
4.8 KiB
Swift
132 lines
4.8 KiB
Swift
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
|
|
|
import UIKit
|
|
import UniformTypeIdentifiers
|
|
|
|
public final class InputTextView: UITextView, UITextViewDelegate {
|
|
private static let defaultFont: UIFont = .systemFont(ofSize: Values.mediumFontSize)
|
|
private static let defaultThemeTextColor: ThemeValue = .textPrimary
|
|
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 = "message".localized()
|
|
result.themeTextColor = .textSecondary
|
|
|
|
return result
|
|
}()
|
|
|
|
// MARK: - Settings
|
|
|
|
public let minHeight: CGFloat = 22
|
|
public let maxHeight: CGFloat = 122
|
|
|
|
// MARK: - Lifecycle
|
|
|
|
public 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 = "Message"
|
|
}
|
|
|
|
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
|
|
UIPasteboard.general.hasImages,
|
|
let firstImageType: String = UIPasteboard.general.types.first(where: {
|
|
UTType($0)?.conforms(to: .image) == true
|
|
}),
|
|
let itemData: Data = UIPasteboard.general.data(forPasteboardType: firstImageType)
|
|
{
|
|
snDelegate?.didPasteImageDataFromPasteboard(self, imageData: itemData)
|
|
}
|
|
super.paste(sender)
|
|
}
|
|
|
|
private func setUpViewHierarchy() {
|
|
showsHorizontalScrollIndicator = false
|
|
showsVerticalScrollIndicator = false
|
|
|
|
/// **Note:** If we add any additional attributes here then we will need to update the logic in
|
|
/// `textView(_:,shouldChangeTextIn:replacementText:)` to match
|
|
font = InputTextView.defaultFont
|
|
themeBackgroundColor = .clear
|
|
themeTextColor = InputTextView.defaultThemeTextColor
|
|
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
|
|
self?.keyboardAppearance = theme.keyboardAppearance
|
|
}
|
|
}
|
|
|
|
// MARK: - Updating
|
|
|
|
@MainActor public func textViewDidChange(_ textView: UITextView) {
|
|
handleTextChanged()
|
|
}
|
|
|
|
@MainActor 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
|
|
|
|
public protocol InputTextViewDelegate: AnyObject {
|
|
@MainActor func inputTextViewDidChangeSize(_ inputTextView: InputTextView)
|
|
@MainActor func inputTextViewDidChangeContent(_ inputTextView: InputTextView)
|
|
@MainActor func didPasteImageDataFromPasteboard(_ inputTextView: InputTextView, imageData: Data)
|
|
}
|