Skip to content

Commit

Permalink
Added capability to attachment enabling selection without display in … (
Browse files Browse the repository at this point in the history
#332)

* Added capability to attachment enabling selection without display in container. Also added ability to get focussed child view

* Fixed tests

* Made attachment.selectRangeInContainer discardableResult
  • Loading branch information
rajdeep authored Aug 8, 2024
1 parent b8d59a2 commit 4908280
Show file tree
Hide file tree
Showing 6 changed files with 154 additions and 6 deletions.
47 changes: 42 additions & 5 deletions Proton/Sources/Swift/Attachment/Attachment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ open class Attachment: NSTextAttachment, BoundsObserving {
private(set) var cachedContainerSize: CGSize?
private var indexInContainer: Int?
private let backgroundColor: UIColor?
private var showSelectionViewWhenSelected = true

var cachedBounds: CGRect?

Expand Down Expand Up @@ -182,6 +183,19 @@ open class Attachment: NSTextAttachment, BoundsObserving {
isRenderingAsync && isAsyncRendered == false
}

/// Returns `true` if any of the child views is first Responder, else false
public var isFocussed: Bool {
contentView?.firstResponderChildView() != nil
}

/// Returns the child view that is first Responder, else nil
public var firstResponderChildView: UIView? {
contentView?.firstResponderChildView()
}

/// Determines if attachment is in selected range in the container `EditorView`
public var isInSelectedRange: Bool { isSelected }

var isImageBasedAttachment: Bool {
self.view == nil
}
Expand All @@ -190,12 +204,10 @@ open class Attachment: NSTextAttachment, BoundsObserving {
return view?.superview != nil
}

/// Determines if attachment is in selected range in the container `EditorView`
public var isInSelectedRange: Bool { isSelected }

var isSelected: Bool = false {
didSet {
guard let view = self.view else { return }
guard let view = self.view,
showSelectionViewWhenSelected else { return }
if isSelected {
selectionView.addTo(parent: view)
} else {
Expand Down Expand Up @@ -398,9 +410,30 @@ open class Attachment: NSTextAttachment, BoundsObserving {
view?.removeFromSuperview()
containerEditorView = nil
}

/// Selects attachment range in container `EditorView`. This does not shows attachment itself as selected nor does it makes
/// any changes to first responder. To show attachment as selected, see `setSelected`
/// - Returns: `true` if attachment range was selected, else `false`
@discardableResult
public func selectRangeInContainer() -> Bool {
guard let container = containerEditorView,
let range = rangeInContainer(),
range.isValidIn(container.textInput) else { return false }
selectContainerWithoutSelfSelection {
container.selectedRange = range
}
return true
}

private func selectContainerWithoutSelfSelection(_ closure: () -> Void) {
showSelectionViewWhenSelected = false
closure()
showSelectionViewWhenSelected = true
}

/// Selects the attachment in Editor.
/// - Parameter isSelected: `true` to set selected, else `false`
/// - Parameters:
/// - isSelected: `true` to set selected, else `false`
public func setSelected(_ isSelected: Bool) {
guard let containerEditor = containerEditorView,
let range = rangeInContainer() else { return }
Expand Down Expand Up @@ -666,6 +699,10 @@ extension UIView {
containerAttachmentFor(view: self)
}

func firstResponderChildView() -> UIView? {
return isFirstResponder ? self : subviews.compactMap { $0.firstResponderChildView() }.first
}

private func containerAttachmentFor(view: UIView?) -> AttachmentContentView? {
guard view != nil else { return nil }
guard let attachmentView = view as? AttachmentContentView else {
Expand Down
113 changes: 112 additions & 1 deletion Proton/Tests/Attachments/ViewAttachmentSnapshotTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ import SnapshotTesting
@testable import Proton

class ViewAttachmentSnapshotTests: SnapshotTestCase {
var attachmentOffset = CGPoint(x: 0, y: -3)

override func setUp() {
super.setUp()
attachmentOffset = CGPoint(x: 0, y: -3)
}

func testMatchContentRendering() {
let viewController = EditorTestViewController()
let textView = viewController.editor
Expand Down Expand Up @@ -110,7 +117,100 @@ class ViewAttachmentSnapshotTests: SnapshotTestCase {
assertSnapshot(matching: viewController.view, as: .image, record: recordMode)
}

func testSetsSelectionWithDisplay() {
let viewController = EditorTestViewController()
let textView = viewController.editor

let attachment1 = makeTextFieldAttachment(text: NSAttributedString(string: "Test text"))

textView.replaceCharacters(in: .zero, with: "Short text ")
textView.insertAttachment(in: textView.textEndRange, attachment: attachment1)

textView.selectedRange = .zero

attachment1.setSelected(true)
viewController.render(size: CGSize(width: 300, height: 120))

XCTAssertNotNil(attachment1.rangeInContainer())
XCTAssertTrue(attachment1.isSelected)
XCTAssertEqual(attachment1.containerEditorView?.selectedRange, attachment1.rangeInContainer())
assertSnapshot(matching: viewController.view, as: .image, record: recordMode)
}

func testSetsSelectionWithoutDisplay() {
let viewController = EditorTestViewController()
let textView = viewController.editor

let attachment1 = makeTextFieldAttachment(text: NSAttributedString(string: "Test text"))

textView.replaceCharacters(in: .zero, with: "Short text ")
textView.insertAttachment(in: textView.textEndRange, attachment: attachment1)

textView.selectedRange = .zero

XCTAssertTrue(attachment1.selectRangeInContainer())
viewController.render(size: CGSize(width: 300, height: 120))

XCTAssertNotNil(attachment1.rangeInContainer())
XCTAssertTrue(attachment1.isSelected)
XCTAssertEqual(attachment1.containerEditorView?.selectedRange, attachment1.rangeInContainer())
assertSnapshot(matching: viewController.view, as: .image, record: recordMode)
}

func testGetsFocussedChildView() {
let window = UIWindow(frame: CGRect(origin: .zero, size: CGSize(width: 300, height: 800)))
window.makeKeyAndVisible()

let viewController = EditorTestViewController()
window.rootViewController = viewController

let textView = viewController.editor
let attachment1 = makeTextFieldAttachment(text: NSAttributedString(string: "Test text"))

textView.replaceCharacters(in: .zero, with: "Short text ")
textView.insertAttachment(in: textView.textEndRange, attachment: attachment1)

textView.selectedRange = .zero

viewController.render(size: CGSize(width: 300, height: 120))

XCTAssertTrue((attachment1.contentView as? AutogrowingTextField)?.becomeFirstResponder() ?? false)
XCTAssertTrue(attachment1.isFocussed)

XCTAssertTrue(attachment1.firstResponderChildView is AutogrowingTextField)

assertSnapshot(matching: viewController.view, as: .image, record: recordMode)
}

func testReturnsNilForNonFocussedChildView() {
let window = UIWindow(frame: CGRect(origin: .zero, size: CGSize(width: 300, height: 800)))
window.makeKeyAndVisible()

let viewController = EditorTestViewController()
window.rootViewController = viewController

let textView = viewController.editor
let attachment1 = makeTextFieldAttachment(text: NSAttributedString(string: "Test text"))

textView.replaceCharacters(in: .zero, with: "Short text ")
textView.insertAttachment(in: textView.textEndRange, attachment: attachment1)

textView.selectedRange = .zero

viewController.render(size: CGSize(width: 300, height: 120))

XCTAssertTrue((attachment1.contentView as? AutogrowingTextField)?.becomeFirstResponder() ?? false)
XCTAssertTrue(attachment1.isFocussed)
XCTAssertTrue((attachment1.contentView as? AutogrowingTextField)?.resignFirstResponder() ?? false)

XCTAssertFalse(attachment1.isFocussed)
XCTAssertNil(attachment1.firstResponderChildView)

assertSnapshot(matching: viewController.view, as: .image, record: recordMode)
}

private func makeDummyAttachment(text: String, size: AttachmentSize) -> Attachment {
attachmentOffset = CGPoint(x: 0, y: -3)
let textView = RichTextAttachmentView(context: RichTextViewContext())
textView.textContainerInset = .zero
textView.layoutMargins = .zero
Expand All @@ -120,10 +220,21 @@ class ViewAttachmentSnapshotTests: SnapshotTestCase {
attachment.offsetProvider = self
return attachment
}

private func makeTextFieldAttachment(text: NSAttributedString) -> Attachment {
attachmentOffset = CGPoint(x: 0, y: -4.5)
let textField = AutogrowingTextField()
let textFieldAttachment = Attachment(textField, size: .matchContent)
textFieldAttachment.offsetProvider = self
textField.attributedText = text
textField.layer.borderColor = UIColor.black.cgColor
textField.layer.borderWidth = 1
return textFieldAttachment
}
}

extension ViewAttachmentSnapshotTests: AttachmentOffsetProviding {
func offset(for attachment: Attachment, in textContainer: NSTextContainer, proposedLineFragment lineFrag: CGRect, glyphPosition position: CGPoint, characterIndex charIndex: Int) -> CGPoint {
return CGPoint(x: 0, y: -3)
return attachmentOffset
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 4908280

Please sign in to comment.