Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions FlipcashTests/Chat/ChatMediaViewerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,28 @@ struct ChatMediaViewerTests {
#expect(shared == nil)
}

@Test("A tap hides the close and share buttons, and a second tap brings them back")
func tapTogglesControls() throws {
let viewer = ChatMediaViewerController(request: try #require(request(message(media()), localImage: Self.localImage))) { _ in }
viewer.loadViewIfNeeded()

viewer.setControlsHidden(true, animated: false)
#expect(viewer.areControlsHidden)
for button in [viewer.closeButton, viewer.shareButton] {
#expect(button.alpha == 0)
#expect(!button.isUserInteractionEnabled)
#expect(button.accessibilityElementsHidden)
}

viewer.setControlsHidden(false, animated: false)
#expect(!viewer.areControlsHidden)
for button in [viewer.closeButton, viewer.shareButton] {
#expect(button.alpha == 1)
#expect(button.isUserInteractionEnabled)
#expect(!button.accessibilityElementsHidden)
}
}

// MARK: - Transcript wiring

private func transcript(_ message: ChatMessage, localImage: UIImage? = nil) -> (ChatViewController, ChatMediaCell) {
Expand Down
37 changes: 35 additions & 2 deletions FlipcashUI/Sources/FlipcashUI/Chat/ChatMediaViewerController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ public struct ChatMediaViewerRequest {
}

/// A full-screen photo, zoomed in from the tapped transcript row: pinch or double-tap to zoom, swipe
/// down or close to put it back, and share once the photo itself has loaded.
/// down or close to put it back, tap to hide or show the buttons, and share once the photo itself
/// has loaded.
public final class ChatMediaViewerController: UIViewController, UIScrollViewDelegate {

private static let maximumZoomScale: CGFloat = 4
Expand All @@ -64,7 +65,10 @@ public final class ChatMediaViewerController: UIViewController, UIScrollViewDele
let scrollView = UIScrollView()
let imageView = UIImageView()
let shareButton = UIButton(configuration: .plain())
private let closeButton = UIButton(configuration: .plain())
let closeButton = UIButton(configuration: .plain())

/// Whether a single tap has put the close and share buttons away.
private(set) var areControlsHidden = false

/// The photo itself, once drawn — never the placeholder, so share never hands out a BlurHash.
private var loadedImage: UIImage? {
Expand Down Expand Up @@ -113,6 +117,10 @@ public final class ChatMediaViewerController: UIViewController, UIScrollViewDele
doubleTap.numberOfTapsRequired = 2
scrollView.addGestureRecognizer(doubleTap)

let singleTap = UITapGestureRecognizer(target: self, action: #selector(singleTapped))
singleTap.require(toFail: doubleTap)
scrollView.addGestureRecognizer(singleTap)

configure(closeButton, symbol: "xmark", label: "Close", action: #selector(closeTapped))
configure(shareButton, symbol: "square.and.arrow.up", label: "Share", action: #selector(shareTapped))

Expand Down Expand Up @@ -199,6 +207,31 @@ public final class ChatMediaViewerController: UIViewController, UIScrollViewDele
scrollView.zoom(to: CGRect(x: point.x - size.width / 2, y: point.y - size.height / 2, width: size.width, height: size.height), animated: true)
}

// MARK: - Controls

@objc private func singleTapped() {
setControlsHidden(!areControlsHidden, animated: true)
}

/// Fades the close and share buttons out or back in; hidden buttons take no touches and drop out
/// of VoiceOver.
func setControlsHidden(_ hidden: Bool, animated: Bool) {
areControlsHidden = hidden
for button in [closeButton, shareButton] {
button.isUserInteractionEnabled = !hidden
button.accessibilityElementsHidden = hidden
}
let fade = { [closeButton, shareButton] in
closeButton.alpha = hidden ? 0 : 1
shareButton.alpha = hidden ? 0 : 1
}
if animated {
UIView.animate(withDuration: 0.2, delay: 0, options: [.beginFromCurrentState, .allowUserInteraction], animations: fade)
} else {
fade()
}
}

// MARK: - Actions

@objc private func closeTapped() {
Expand Down
Loading