session-ios/Session/Conversations/Input View/InputView.swift

543 lines
22 KiB
Swift
Raw Normal View History

// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import UIKit
import Combine
import SessionUIKit
import SessionMessagingKit
import SessionUtilitiesKit
import SignalUtilitiesKit
2021-01-29 01:46:32 +01:00
final class InputView: UIView, InputViewButtonDelegate, InputTextViewDelegate, MentionSelectionViewDelegate {
// MARK: - Variables
private static let linkPreviewViewInset: CGFloat = 6
private var disposables: Set<AnyCancellable> = Set()
private let threadVariant: SessionThread.Variant
2021-04-01 05:24:10 +02:00
private weak var delegate: InputViewDelegate?
var quoteDraftInfo: (model: QuotedReplyModel, isOutgoing: Bool)? { didSet { handleQuoteDraftChanged() } }
var linkPreviewInfo: (url: String, draft: LinkPreviewDraft?)?
2021-02-16 03:57:30 +01:00
private var voiceMessageRecordingView: VoiceMessageRecordingView?
2021-02-17 04:26:43 +01:00
private lazy var mentionsViewHeightConstraint = mentionsView.set(.height, to: 0)
2021-02-15 03:51:26 +01:00
2021-02-19 06:02:19 +01:00
private lazy var linkPreviewView: LinkPreviewView = {
let maxWidth: CGFloat = (self.additionalContentContainer.bounds.width - InputView.linkPreviewViewInset)
return LinkPreviewView(maxWidth: maxWidth) { [weak self] in
self?.linkPreviewInfo = nil
self?.additionalContentContainer.subviews.forEach { $0.removeFromSuperview() }
}
2021-02-15 03:51:26 +01:00
}()
2021-01-29 01:46:32 +01:00
var text: String {
get { inputTextView.text ?? "" }
2021-01-29 01:46:32 +01:00
set { inputTextView.text = newValue }
}
var selectedRange: NSRange {
get { inputTextView.selectedRange }
set { inputTextView.selectedRange = newValue }
}
var enabledMessageTypes: MessageInputTypes = .all {
didSet {
setEnabledMessageTypes(enabledMessageTypes, message: nil)
}
}
2021-01-29 01:46:32 +01:00
override var intrinsicContentSize: CGSize { CGSize.zero }
2021-02-19 03:25:31 +01:00
var lastSearchedText: String? { nil }
// MARK: - UI
private var bottomStackView: UIStackView?
private lazy var attachmentsButton: ExpandingAttachmentsButton = {
let result = ExpandingAttachmentsButton(delegate: delegate)
result.accessibilityLabel = "Attachments button"
result.accessibilityIdentifier = "Attachments button"
result.isAccessibilityElement = true
return result
}()
private lazy var voiceMessageButton: InputViewButton = {
let result = InputViewButton(icon: #imageLiteral(resourceName: "Microphone"), delegate: self)
result.accessibilityLabel = "New voice message"
result.accessibilityIdentifier = "New voice message"
result.isAccessibilityElement = true
return result
}()
private lazy var sendButton: InputViewButton = {
let result = InputViewButton(icon: #imageLiteral(resourceName: "ArrowUp"), isSendButton: true, delegate: self)
result.isHidden = true
result.accessibilityIdentifier = "Send message button"
result.accessibilityLabel = "Send message button"
result.isAccessibilityElement = true
return result
}()
private lazy var voiceMessageButtonContainer = container(for: voiceMessageButton)
2021-02-17 04:26:43 +01:00
private lazy var mentionsView: MentionSelectionView = {
let result: MentionSelectionView = MentionSelectionView()
2021-02-17 04:26:43 +01:00
result.delegate = self
2021-02-17 04:26:43 +01:00
return result
}()
2021-02-17 05:57:07 +01:00
private lazy var mentionsViewContainer: UIView = {
let result: UIView = UIView()
result.accessibilityLabel = "Mentions list"
result.accessibilityIdentifier = "Mentions list"
result.alpha = 0
2021-02-17 05:57:07 +01:00
let backgroundView = UIView()
backgroundView.themeBackgroundColor = .backgroundSecondary
2021-02-17 05:57:07 +01:00
backgroundView.alpha = Values.lowOpacity
result.addSubview(backgroundView)
backgroundView.pin(to: result)
let blurView: UIVisualEffectView = UIVisualEffectView()
2021-02-17 05:57:07 +01:00
result.addSubview(blurView)
blurView.pin(to: result)
ThemeManager.onThemeChange(observer: blurView) { [weak blurView] theme, _ in
switch theme.interfaceStyle {
case .light: blurView?.effect = UIBlurEffect(style: .light)
default: blurView?.effect = UIBlurEffect(style: .dark)
}
}
2021-02-17 05:57:07 +01:00
return result
}()
2021-03-01 23:33:31 +01:00
private lazy var inputTextView: InputTextView = {
2021-03-02 00:18:08 +01:00
// HACK: When restoring a draft the input text view won't have a frame yet, and therefore it won't
// be able to calculate what size it should be to accommodate the draft text. As a workaround, we
// just calculate the max width that the input text view is allowed to be and pass it in. See
// setUpViewHierarchy() for why these values are the way they are.
2021-03-01 23:33:31 +01:00
let adjustment = (InputViewButton.expandedSize - InputViewButton.size) / 2
let maxWidth = UIScreen.main.bounds.width - 2 * InputViewButton.expandedSize - 2 * Values.smallSpacing - 2 * (Values.mediumSpacing - adjustment)
let result = InputTextView(delegate: self, maxWidth: maxWidth)
result.accessibilityLabel = "Message input box"
result.accessibilityIdentifier = "Message input box"
result.isAccessibilityElement = true
return result
2021-03-01 23:33:31 +01:00
}()
private lazy var disabledInputLabel: UILabel = {
let label: UILabel = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = .systemFont(ofSize: Values.smallFontSize)
label.themeTextColor = .textPrimary
label.textAlignment = .center
label.alpha = 0
return label
}()
2021-02-10 04:43:57 +01:00
2021-02-26 04:11:58 +01:00
private lazy var additionalContentContainer = UIView()
2021-02-15 03:51:26 +01:00
// MARK: - Initialization
2021-01-29 01:46:32 +01:00
init(threadVariant: SessionThread.Variant, delegate: InputViewDelegate) {
self.threadVariant = threadVariant
2021-01-29 01:46:32 +01:00
self.delegate = delegate
2021-01-29 01:46:32 +01:00
super.init(frame: CGRect.zero)
2021-01-29 01:46:32 +01:00
setUpViewHierarchy()
}
2021-01-29 01:46:32 +01:00
override init(frame: CGRect) {
preconditionFailure("Use init(delegate:) instead.")
}
2021-01-29 01:46:32 +01:00
required init?(coder: NSCoder) {
preconditionFailure("Use init(delegate:) instead.")
}
2021-01-29 01:46:32 +01:00
private func setUpViewHierarchy() {
autoresizingMask = .flexibleHeight
2021-01-29 01:46:32 +01:00
// Background & blur
let backgroundView = UIView()
backgroundView.themeBackgroundColor = .backgroundSecondary
2021-01-29 01:46:32 +01:00
backgroundView.alpha = Values.lowOpacity
addSubview(backgroundView)
backgroundView.pin(to: self)
let blurView = UIVisualEffectView()
2021-01-29 01:46:32 +01:00
addSubview(blurView)
blurView.pin(to: self)
ThemeManager.onThemeChange(observer: blurView) { [weak blurView] theme, _ in
switch theme.interfaceStyle {
case .light: blurView?.effect = UIBlurEffect(style: .light)
default: blurView?.effect = UIBlurEffect(style: .dark)
}
}
2021-01-29 01:46:32 +01:00
// Separator
let separator = UIView()
separator.themeBackgroundColor = .borderSeparator
separator.set(.height, to: Values.separatorThickness)
2021-01-29 01:46:32 +01:00
addSubview(separator)
separator.pin([ UIView.HorizontalEdge.leading, UIView.VerticalEdge.top, UIView.HorizontalEdge.trailing ], to: self)
2021-01-29 01:46:32 +01:00
// Bottom stack view
2021-02-22 00:49:35 +01:00
let bottomStackView = UIStackView(arrangedSubviews: [ attachmentsButton, inputTextView, container(for: sendButton) ])
2021-01-29 01:46:32 +01:00
bottomStackView.axis = .horizontal
bottomStackView.spacing = Values.smallSpacing
2021-02-10 05:33:39 +01:00
bottomStackView.alignment = .center
self.bottomStackView = bottomStackView
2021-01-29 01:46:32 +01:00
// Main stack view
2021-02-22 00:49:35 +01:00
let mainStackView = UIStackView(arrangedSubviews: [ additionalContentContainer, bottomStackView ])
2021-01-29 01:46:32 +01:00
mainStackView.axis = .vertical
mainStackView.isLayoutMarginsRelativeArrangement = true
2021-01-29 01:46:32 +01:00
let adjustment = (InputViewButton.expandedSize - InputViewButton.size) / 2
2021-02-23 05:30:05 +01:00
mainStackView.layoutMargins = UIEdgeInsets(top: 2, leading: Values.mediumSpacing - adjustment, bottom: 2, trailing: Values.mediumSpacing - adjustment)
2021-01-29 01:46:32 +01:00
addSubview(mainStackView)
mainStackView.pin(.top, to: .bottom, of: separator)
mainStackView.pin([ UIView.HorizontalEdge.leading, UIView.HorizontalEdge.trailing ], to: self)
2021-02-26 04:11:58 +01:00
mainStackView.pin(.bottom, to: .bottom, of: self)
addSubview(disabledInputLabel)
disabledInputLabel.pin(.top, to: .top, of: mainStackView)
disabledInputLabel.pin(.leading, to: .leading, of: mainStackView)
disabledInputLabel.pin(.trailing, to: .trailing, of: mainStackView)
disabledInputLabel.set(.height, to: InputViewButton.expandedSize)
2021-02-17 04:26:43 +01:00
// Mentions
2021-02-22 03:36:26 +01:00
insertSubview(mentionsViewContainer, belowSubview: mainStackView)
mentionsViewContainer.pin([ UIView.HorizontalEdge.leading, UIView.HorizontalEdge.trailing ], to: self)
2021-02-17 05:57:07 +01:00
mentionsViewContainer.pin(.bottom, to: .top, of: self)
mentionsViewContainer.addSubview(mentionsView)
mentionsView.pin(to: mentionsViewContainer)
2021-02-17 04:26:43 +01:00
mentionsViewHeightConstraint.isActive = true
// Voice message button
addSubview(voiceMessageButtonContainer)
voiceMessageButtonContainer.center(in: sendButton)
2021-01-29 01:46:32 +01:00
}
// MARK: - Updating
2021-01-29 01:46:32 +01:00
func inputTextViewDidChangeSize(_ inputTextView: InputTextView) {
invalidateIntrinsicContentSize()
}
func inputTextViewDidChangeContent(_ inputTextView: InputTextView) {
let hasText = !text.isEmpty
sendButton.isHidden = !hasText
voiceMessageButtonContainer.isHidden = hasText
2021-02-15 05:07:38 +01:00
autoGenerateLinkPreviewIfPossible()
2021-04-01 05:24:10 +02:00
delegate?.inputTextViewDidChangeContent(inputTextView)
2021-01-29 01:46:32 +01:00
}
2021-12-13 05:51:42 +01:00
func didPasteImageFromPasteboard(_ inputTextView: InputTextView, image: UIImage) {
delegate?.didPasteImageFromPasteboard(image)
}
2021-02-10 04:43:57 +01:00
2021-03-02 00:18:08 +01:00
// We want to show either a link preview or a quote draft, but never both at the same time. When trying to
// generate a link preview, wait until we're sure that we'll be able to build a link preview from the given
// URL before removing the quote draft.
2021-02-10 04:43:57 +01:00
private func handleQuoteDraftChanged() {
2021-02-15 03:51:26 +01:00
additionalContentContainer.subviews.forEach { $0.removeFromSuperview() }
2021-02-15 04:45:46 +01:00
linkPreviewInfo = nil
2021-02-10 04:43:57 +01:00
guard let quoteDraftInfo = quoteDraftInfo else { return }
2021-03-02 00:18:08 +01:00
let hInset: CGFloat = 6 // Slight visual adjustment
2021-02-15 03:51:26 +01:00
let maxWidth = additionalContentContainer.bounds.width
let quoteView: QuoteView = QuoteView(
for: .draft,
authorId: quoteDraftInfo.model.authorId,
quotedText: quoteDraftInfo.model.body,
threadVariant: threadVariant,
currentUserPublicKey: quoteDraftInfo.model.currentUserPublicKey,
currentUserBlindedPublicKey: quoteDraftInfo.model.currentUserBlindedPublicKey,
direction: (quoteDraftInfo.isOutgoing ? .outgoing : .incoming),
attachment: quoteDraftInfo.model.attachment,
hInset: hInset,
maxWidth: maxWidth
) { [weak self] in
self?.quoteDraftInfo = nil
}
2021-02-15 03:51:26 +01:00
additionalContentContainer.addSubview(quoteView)
quoteView.pin(.leading, to: .leading, of: additionalContentContainer, withInset: hInset)
2021-02-15 03:51:26 +01:00
quoteView.pin(.top, to: .top, of: additionalContentContainer, withInset: 12)
quoteView.pin(.trailing, to: .trailing, of: additionalContentContainer, withInset: -hInset)
2021-02-15 03:51:26 +01:00
quoteView.pin(.bottom, to: .bottom, of: additionalContentContainer, withInset: -6)
}
2021-02-15 05:07:38 +01:00
private func autoGenerateLinkPreviewIfPossible() {
// Don't allow link previews on 'none' or 'textOnly' input
guard enabledMessageTypes == .all else { return }
2021-02-15 05:07:38 +01:00
// Suggest that the user enable link previews if they haven't already and we haven't
2021-02-15 04:45:46 +01:00
// told them about link previews yet
2021-02-15 03:51:26 +01:00
let text = inputTextView.text!
let areLinkPreviewsEnabled: Bool = Storage.shared[.areLinkPreviewsEnabled]
if
!LinkPreview.allPreviewUrls(forMessageBodyText: text).isEmpty &&
!areLinkPreviewsEnabled &&
!UserDefaults.standard[.hasSeenLinkPreviewSuggestion]
{
2021-04-01 05:24:10 +02:00
delegate?.showLinkPreviewSuggestionModal()
UserDefaults.standard[.hasSeenLinkPreviewSuggestion] = true
2021-02-15 05:07:38 +01:00
return
2021-02-15 03:51:26 +01:00
}
2021-02-15 04:45:46 +01:00
// Check that link previews are enabled
guard areLinkPreviewsEnabled else { return }
2021-02-15 05:07:38 +01:00
// Proceed
autoGenerateLinkPreview()
}
func autoGenerateLinkPreview() {
2021-02-15 04:45:46 +01:00
// Check that a valid URL is present
guard let linkPreviewURL = LinkPreview.previewUrl(for: text, selectedRange: inputTextView.selectedRange) else {
2021-02-15 03:51:26 +01:00
return
}
2021-02-15 04:45:46 +01:00
// Guard against obsolete updates
guard linkPreviewURL != self.linkPreviewInfo?.url else { return }
2021-02-19 04:33:04 +01:00
// Clear content container
additionalContentContainer.subviews.forEach { $0.removeFromSuperview() }
quoteDraftInfo = nil
2021-02-15 04:45:46 +01:00
// Set the state to loading
linkPreviewInfo = (url: linkPreviewURL, draft: nil)
linkPreviewView.update(with: LinkPreview.LoadingState(), isOutgoing: false)
2021-02-15 04:45:46 +01:00
// Add the link preview view
2021-02-15 03:51:26 +01:00
additionalContentContainer.addSubview(linkPreviewView)
linkPreviewView.pin(.leading, to: .leading, of: additionalContentContainer, withInset: InputView.linkPreviewViewInset)
2021-02-15 03:51:26 +01:00
linkPreviewView.pin(.top, to: .top, of: additionalContentContainer, withInset: 10)
linkPreviewView.pin(.trailing, to: .trailing, of: additionalContentContainer)
2021-02-15 03:51:26 +01:00
linkPreviewView.pin(.bottom, to: .bottom, of: additionalContentContainer, withInset: -4)
2021-02-15 04:45:46 +01:00
// Build the link preview
LinkPreview.tryToBuildPreviewInfo(previewUrl: linkPreviewURL)
Fixed a number of issues found during internal testing Added copy for an unrecoverable startup case Added some additional logs to better debug ValueObservation query errors Increased the pageSize to 20 on iPad devices (to prevent it immediately loading a second page) Cleaned up a bunch of threading logic (try to avoid overriding subscribe/receive threads specified at subscription) Consolidated the 'sendMessage' and 'sendAttachments' functions Updated the various frameworks to use 'DAWRF with DSYM' to allow for better debugging during debug mode (at the cost of a longer build time) Updated the logic to optimistically insert messages when sending to avoid any database write delays Updated the logic to avoid sending notifications for messages which are already marked as read by the config Fixed an issue where multiple paths could incorrectly get built at the same time in some cases Fixed an issue where other job queues could be started before the blockingQueue finishes Fixed a potential bug with the snode version comparison (was just a string comparison which would fail when getting to double-digit values) Fixed a bug where you couldn't remove the last reaction on a message Fixed the broken media message zoom animations Fixed a bug where the last message read in a conversation wouldn't be correctly detected as already read Fixed a bug where the QuoteView had no line limits (resulting in the '@You' mention background highlight being incorrectly positioned in the quote preview) Fixed a bug where a large number of configSyncJobs could be scheduled (only one would run at a time but this could result in performance impacts)
2023-06-23 09:54:29 +02:00
.subscribe(on: DispatchQueue.global(qos: .userInitiated))
.receive(on: DispatchQueue.main)
.sink(
receiveCompletion: { [weak self] result in
switch result {
case .finished: break
case .failure:
guard self?.linkPreviewInfo?.url == linkPreviewURL else { return } // Obsolete
self?.linkPreviewInfo = nil
self?.additionalContentContainer.subviews.forEach { $0.removeFromSuperview() }
}
},
receiveValue: { [weak self] draft in
guard self?.linkPreviewInfo?.url == linkPreviewURL else { return } // Obsolete
self?.linkPreviewInfo = (url: linkPreviewURL, draft: draft)
self?.linkPreviewView.update(with: LinkPreview.DraftState(linkPreviewDraft: draft), isOutgoing: false)
}
)
.store(in: &disposables)
2021-02-10 04:43:57 +01:00
}
func setEnabledMessageTypes(_ messageTypes: MessageInputTypes, message: String?) {
guard enabledMessageTypes != messageTypes else { return }
enabledMessageTypes = messageTypes
disabledInputLabel.text = (message ?? "")
attachmentsButton.isUserInteractionEnabled = (messageTypes == .all)
voiceMessageButton.isUserInteractionEnabled = (messageTypes == .all)
UIView.animate(withDuration: 0.3) { [weak self] in
self?.bottomStackView?.alpha = (messageTypes != .none ? 1 : 0)
self?.attachmentsButton.alpha = (messageTypes == .all ?
1 :
(messageTypes == .textOnly ? 0.4 : 0)
)
self?.voiceMessageButton.alpha = (messageTypes == .all ?
1 :
(messageTypes == .textOnly ? 0.4 : 0)
)
self?.disabledInputLabel.alpha = (messageTypes != .none ? 0 : Values.mediumOpacity)
}
}
// MARK: - Interaction
2021-02-22 00:49:35 +01:00
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
2021-03-02 00:18:08 +01:00
// Needed so that the user can tap the buttons when the expanding attachments button is expanded
2021-02-22 00:49:35 +01:00
let buttonContainers = [ attachmentsButton.mainButton, attachmentsButton.cameraButton,
attachmentsButton.libraryButton, attachmentsButton.documentButton, attachmentsButton.gifButton ]
if let buttonContainer: InputViewButton = buttonContainers.first(where: { $0.superview?.convert($0.frame, to: self).contains(point) == true }) {
2021-02-22 00:49:35 +01:00
return buttonContainer
}
return super.hitTest(point, with: event)
2021-02-22 00:49:35 +01:00
}
2021-02-17 04:26:43 +01:00
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
2021-02-22 00:49:35 +01:00
let buttonContainers = [ attachmentsButton.gifButtonContainer, attachmentsButton.documentButtonContainer,
attachmentsButton.libraryButtonContainer, attachmentsButton.cameraButtonContainer, attachmentsButton.mainButtonContainer ]
let isPointInsideAttachmentsButton = buttonContainers
.contains { $0.superview!.convert($0.frame, to: self).contains(point) }
2021-02-22 00:49:35 +01:00
if isPointInsideAttachmentsButton {
2021-03-02 00:18:08 +01:00
// Needed so that the user can tap the buttons when the expanding attachments button is expanded
2021-02-22 00:49:35 +01:00
return true
}
if mentionsViewContainer.frame.contains(point) {
2021-03-02 00:18:08 +01:00
// Needed so that the user can tap mentions
2021-02-17 04:26:43 +01:00
return true
}
return super.point(inside: point, with: event)
2021-02-17 04:26:43 +01:00
}
2021-01-29 01:46:32 +01:00
func handleInputViewButtonTapped(_ inputViewButton: InputViewButton) {
2021-04-01 05:24:10 +02:00
if inputViewButton == sendButton { delegate?.handleSendButtonTapped() }
2021-01-29 01:46:32 +01:00
}
2021-02-10 07:04:26 +01:00
func handleInputViewButtonLongPressBegan(_ inputViewButton: InputViewButton?) {
2021-02-16 09:28:32 +01:00
guard inputViewButton == voiceMessageButton else { return }
// Note: The 'showVoiceMessageUI' call MUST come before triggering 'startVoiceMessageRecording'
// because if something goes wrong it'll trigger `hideVoiceMessageUI` and we don't want it to
// end up in a state with the input content hidden
2021-02-16 09:28:32 +01:00
showVoiceMessageUI()
delegate?.startVoiceMessageRecording()
2021-02-16 09:28:32 +01:00
}
func handleInputViewButtonLongPressMoved(_ inputViewButton: InputViewButton, with touch: UITouch?) {
guard
let voiceMessageRecordingView: VoiceMessageRecordingView = voiceMessageRecordingView,
inputViewButton == voiceMessageButton,
let location = touch?.location(in: voiceMessageRecordingView)
else { return }
2021-02-16 09:28:32 +01:00
voiceMessageRecordingView.handleLongPressMoved(to: location)
}
func handleInputViewButtonLongPressEnded(_ inputViewButton: InputViewButton, with touch: UITouch?) {
guard
let voiceMessageRecordingView: VoiceMessageRecordingView = voiceMessageRecordingView,
inputViewButton == voiceMessageButton,
let location = touch?.location(in: voiceMessageRecordingView)
else { return }
voiceMessageRecordingView.handleLongPressEnded(at: location)
}
2021-01-29 01:46:32 +01:00
override func resignFirstResponder() -> Bool {
inputTextView.resignFirstResponder()
}
2021-02-15 03:51:26 +01:00
2022-05-20 08:44:53 +02:00
func handleLongPress(_ gestureRecognizer: UITapGestureRecognizer) {
2021-02-15 03:51:26 +01:00
// Not relevant in this case
}
2021-02-15 04:45:46 +01:00
@objc private func showVoiceMessageUI() {
guard let targetSuperview: UIView = voiceMessageButton.superview else { return }
2021-02-16 03:57:30 +01:00
voiceMessageRecordingView?.removeFromSuperview()
let voiceMessageButtonFrame = targetSuperview.convert(voiceMessageButton.frame, to: self)
let voiceMessageRecordingView = VoiceMessageRecordingView(
voiceMessageButtonFrame: voiceMessageButtonFrame,
delegate: delegate
)
2021-02-16 03:57:30 +01:00
voiceMessageRecordingView.alpha = 0
addSubview(voiceMessageRecordingView)
2021-02-16 03:57:30 +01:00
voiceMessageRecordingView.pin(to: self)
self.voiceMessageRecordingView = voiceMessageRecordingView
voiceMessageRecordingView.animate()
2021-02-22 00:49:35 +01:00
let allOtherViews = [ attachmentsButton, sendButton, inputTextView, additionalContentContainer ]
UIView.animate(withDuration: 0.25) {
allOtherViews.forEach { $0.alpha = 0 }
}
}
func hideVoiceMessageUI() {
2021-02-22 00:49:35 +01:00
let allOtherViews = [ attachmentsButton, sendButton, inputTextView, additionalContentContainer ]
UIView.animate(withDuration: 0.25, animations: {
allOtherViews.forEach { $0.alpha = 1 }
self.voiceMessageRecordingView?.alpha = 0
}, completion: { [weak self] _ in
self?.voiceMessageRecordingView?.removeFromSuperview()
self?.voiceMessageRecordingView = nil
})
}
2021-02-17 04:26:43 +01:00
func hideMentionsUI() {
UIView.animate(
withDuration: 0.25,
animations: { [weak self] in
self?.mentionsViewContainer.alpha = 0
},
completion: { [weak self] _ in
self?.mentionsViewHeightConstraint.constant = 0
self?.mentionsView.contentOffset = CGPoint.zero
}
)
2021-02-17 04:26:43 +01:00
}
func showMentionsUI(for candidates: [MentionInfo]) {
2021-02-17 05:57:07 +01:00
mentionsView.candidates = candidates
let mentionCellHeight = (ProfilePictureView.Size.message.viewSize + 2 * Values.smallSpacing)
2021-02-17 05:57:07 +01:00
mentionsViewHeightConstraint.constant = CGFloat(min(3, candidates.count)) * mentionCellHeight
2021-02-17 04:26:43 +01:00
layoutIfNeeded()
2021-02-17 04:26:43 +01:00
UIView.animate(withDuration: 0.25) {
2021-02-17 05:57:07 +01:00
self.mentionsViewContainer.alpha = 1
2021-02-17 04:26:43 +01:00
}
}
func handleMentionSelected(_ mentionInfo: MentionInfo, from view: MentionSelectionView) {
delegate?.handleMentionSelected(mentionInfo, from: view)
2021-02-17 04:26:43 +01:00
}
2022-07-01 08:42:55 +02:00
func tapableLabel(_ label: TappableLabel, didTapUrl url: String, atRange range: NSRange) {
// Do nothing
}
2021-02-17 04:26:43 +01:00
// MARK: - Convenience
private func container(for button: InputViewButton) -> UIView {
let result: UIView = UIView()
result.addSubview(button)
result.set(.width, to: InputViewButton.expandedSize)
result.set(.height, to: InputViewButton.expandedSize)
button.center(in: result)
return result
}
2021-01-29 01:46:32 +01:00
}
// MARK: - Delegate
2021-02-15 03:51:26 +01:00
protocol InputViewDelegate: ExpandingAttachmentsButtonDelegate, VoiceMessageRecordingViewDelegate {
2021-02-15 05:07:38 +01:00
func showLinkPreviewSuggestionModal()
2021-01-29 01:46:32 +01:00
func handleSendButtonTapped()
2021-02-17 04:26:43 +01:00
func inputTextViewDidChangeContent(_ inputTextView: InputTextView)
func handleMentionSelected(_ mentionInfo: MentionInfo, from view: MentionSelectionView)
2021-12-13 05:51:42 +01:00
func didPasteImageFromPasteboard(_ image: UIImage)
2021-01-29 01:46:32 +01:00
}