session-ios/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentApprovalInputAcce...

209 lines
7.7 KiB
Swift
Raw Normal View History

// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import UIKit
2020-11-09 06:03:59 +01:00
import SessionUIKit
protocol AttachmentApprovalInputAccessoryViewDelegate: AnyObject {
2019-03-13 21:15:33 +01:00
func attachmentApprovalInputUpdateMediaRail()
2019-03-14 18:29:40 +01:00
func attachmentApprovalInputStartEditingCaptions()
func attachmentApprovalInputStopEditingCaptions()
2019-03-13 21:15:33 +01:00
}
// MARK: -
class AttachmentApprovalInputAccessoryView: UIView {
2019-03-13 21:15:33 +01:00
weak var delegate: AttachmentApprovalInputAccessoryViewDelegate?
let attachmentTextToolbar: AttachmentTextToolbar
2019-03-13 21:15:33 +01:00
let attachmentCaptionToolbar: AttachmentCaptionToolbar
let galleryRailView: GalleryRailView
2019-03-13 21:15:33 +01:00
let currentCaptionLabel = UILabel()
let currentCaptionWrapper = UIView()
var isEditingMediaMessage: Bool {
return attachmentTextToolbar.textView.isFirstResponder
}
2019-03-13 21:15:33 +01:00
private var isEditingCaptions: Bool = false
private var currentAttachmentItem: SignalAttachmentItem?
let kGalleryRailViewHeight: CGFloat = 72
2019-04-20 00:21:00 +02:00
required init() {
attachmentTextToolbar = AttachmentTextToolbar()
2019-03-13 21:15:33 +01:00
attachmentCaptionToolbar = AttachmentCaptionToolbar()
galleryRailView = GalleryRailView()
galleryRailView.scrollFocusMode = .keepWithinBounds
galleryRailView.autoSetDimension(.height, toSize: kGalleryRailViewHeight)
super.init(frame: .zero)
2019-03-13 21:15:33 +01:00
createContents()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func createContents() {
// Specifying auto-resizing mask and an intrinsic content size allows proper
// sizing when used as an input accessory view.
self.autoresizingMask = .flexibleHeight
self.translatesAutoresizingMaskIntoConstraints = false
self.themeBackgroundColor = .clear
preservesSuperviewLayoutMargins = true
2019-03-13 21:15:33 +01:00
// Use a background view that extends below the keyboard to avoid animation glitches.
let backgroundView = UIView()
backgroundView.themeBackgroundColor = .backgroundPrimary
2019-03-13 21:15:33 +01:00
addSubview(backgroundView)
2019-03-14 18:29:40 +01:00
backgroundView.autoPinEdgesToSuperviewEdges()
2019-03-13 21:15:33 +01:00
currentCaptionLabel.themeTextColor = .white
2020-03-17 06:18:53 +01:00
currentCaptionLabel.font = .systemFont(ofSize: Values.mediumFontSize)
2019-03-13 21:15:33 +01:00
currentCaptionLabel.numberOfLines = 5
currentCaptionLabel.lineBreakMode = .byWordWrapping
currentCaptionWrapper.isUserInteractionEnabled = true
currentCaptionWrapper.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(captionTapped)))
currentCaptionWrapper.addSubview(currentCaptionLabel)
currentCaptionLabel.autoPinEdgesToSuperviewMargins()
attachmentCaptionToolbar.attachmentCaptionToolbarDelegate = self
let stackView = UIStackView(arrangedSubviews: [currentCaptionWrapper, attachmentCaptionToolbar, galleryRailView, attachmentTextToolbar])
stackView.axis = .vertical
addSubview(stackView)
2019-03-14 18:29:40 +01:00
stackView.autoPinEdge(toSuperviewEdge: .top)
stackView.autoPinEdge(toSuperviewEdge: .leading)
stackView.autoPinEdge(toSuperviewEdge: .trailing)
// We pin to the superview's _margin_. Otherwise the notch breaks
// the layout if you hide the keyboard in the simulator (or if the
// user uses an external keyboard).
stackView.autoPinEdge(toSuperviewMargin: .bottom)
let galleryRailBlockingView: UIView = UIView()
galleryRailBlockingView.themeBackgroundColor = .backgroundPrimary
stackView.addSubview(galleryRailBlockingView)
galleryRailBlockingView.pin(.top, to: .bottom, of: attachmentTextToolbar)
galleryRailBlockingView.pin(.left, to: .left, of: stackView)
galleryRailBlockingView.pin(.right, to: .right, of: stackView)
galleryRailBlockingView.pin(.bottom, to: .bottom, of: stackView)
}
2019-03-13 21:15:33 +01:00
// MARK: - Events
@objc func captionTapped(sender: UIGestureRecognizer) {
guard sender.state == .recognized else { return }
2019-03-14 18:29:40 +01:00
delegate?.attachmentApprovalInputStartEditingCaptions()
2019-03-13 21:15:33 +01:00
}
// MARK:
private var shouldHideControls = false
2019-03-13 21:15:33 +01:00
private func updateContents() {
var hasCurrentCaption = false
if let currentAttachmentItem = currentAttachmentItem,
let captionText = currentAttachmentItem.captionText {
hasCurrentCaption = captionText.count > 0
attachmentCaptionToolbar.textView.text = captionText
currentCaptionLabel.text = captionText
} else {
attachmentCaptionToolbar.textView.text = nil
currentCaptionLabel.text = nil
}
attachmentCaptionToolbar.isHidden = !isEditingCaptions
currentCaptionWrapper.isHidden = isEditingCaptions || !hasCurrentCaption
attachmentTextToolbar.isHidden = isEditingCaptions
2019-03-28 23:29:19 +01:00
updateFirstResponder()
layoutSubviews()
}
private func updateFirstResponder() {
2019-03-13 21:15:33 +01:00
if (shouldHideControls) {
if attachmentCaptionToolbar.textView.isFirstResponder {
attachmentCaptionToolbar.textView.resignFirstResponder()
} else if attachmentTextToolbar.textView.isFirstResponder {
attachmentTextToolbar.textView.resignFirstResponder()
}
} else if (isEditingCaptions) {
2019-03-14 17:53:54 +01:00
// While editing captions, the keyboard should always remain visible.
2019-03-13 21:15:33 +01:00
if !attachmentCaptionToolbar.textView.isFirstResponder {
attachmentCaptionToolbar.textView.becomeFirstResponder()
}
} else {
if attachmentCaptionToolbar.textView.isFirstResponder {
attachmentCaptionToolbar.textView.resignFirstResponder()
}
2019-03-13 21:15:33 +01:00
}
2019-03-14 17:53:54 +01:00
// NOTE: We don't automatically make attachmentTextToolbar.textView
// first responder;
2019-03-13 21:15:33 +01:00
}
public func update(isEditingCaptions: Bool,
currentAttachmentItem: SignalAttachmentItem?,
shouldHideControls: Bool) {
// De-bounce
guard self.isEditingCaptions != isEditingCaptions ||
self.currentAttachmentItem != currentAttachmentItem ||
self.shouldHideControls != shouldHideControls else {
2019-03-28 23:29:19 +01:00
updateFirstResponder()
return
}
2019-03-13 21:15:33 +01:00
self.isEditingCaptions = isEditingCaptions
self.currentAttachmentItem = currentAttachmentItem
self.shouldHideControls = shouldHideControls
2019-03-13 21:15:33 +01:00
updateContents()
}
// MARK:
override var intrinsicContentSize: CGSize {
get {
// Since we have `self.autoresizingMask = UIViewAutoresizingFlexibleHeight`, we must specify
// an intrinsicContentSize. Specifying CGSize.zero causes the height to be determined by autolayout.
return CGSize.zero
}
}
2019-03-28 23:29:19 +01:00
public var hasFirstResponder: Bool {
return (isFirstResponder ||
attachmentCaptionToolbar.textView.isFirstResponder ||
attachmentTextToolbar.textView.isFirstResponder)
}
}
2019-03-13 21:15:33 +01:00
// MARK: -
extension AttachmentApprovalInputAccessoryView: AttachmentCaptionToolbarDelegate {
public func attachmentCaptionToolbarDidEdit(_ attachmentCaptionToolbar: AttachmentCaptionToolbar) {
guard let currentAttachmentItem = currentAttachmentItem else {
owsFailDebug("Missing currentAttachmentItem.")
return
}
// TODO: Look at refactoring this behaviour to consolidate attachment mutations
2019-03-13 21:15:33 +01:00
currentAttachmentItem.attachment.captionText = attachmentCaptionToolbar.textView.text
delegate?.attachmentApprovalInputUpdateMediaRail()
}
2019-03-14 18:29:40 +01:00
public func attachmentCaptionToolbarDidComplete() {
delegate?.attachmentApprovalInputStopEditingCaptions()
}
2019-03-13 21:15:33 +01:00
}