Compare commits

...

4 Commits

Author SHA1 Message Date
Shadowfacts d9517047d7
Fix previewing video/audio attachments 2020-03-20 22:48:28 -04:00
Shadowfacts bef3388fe8
Move attachment view corner radius to individual views
Masking the container makes context menu interactions look weird
2020-03-20 22:34:50 -04:00
Shadowfacts 2e8241d734
Move attachment context menu interaction to AttachmentView 2020-03-20 22:28:23 -04:00
Shadowfacts c9c001d403
Improve attachment previewing
- Set correct preview size
- Don't show controls
2020-03-20 22:13:04 -04:00
7 changed files with 159 additions and 22 deletions

View File

@ -117,6 +117,7 @@
D646C956213B365700269FB5 /* LargeImageExpandAnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D646C955213B365700269FB5 /* LargeImageExpandAnimationController.swift */; }; D646C956213B365700269FB5 /* LargeImageExpandAnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D646C955213B365700269FB5 /* LargeImageExpandAnimationController.swift */; };
D646C958213B367000269FB5 /* LargeImageShrinkAnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D646C957213B367000269FB5 /* LargeImageShrinkAnimationController.swift */; }; D646C958213B367000269FB5 /* LargeImageShrinkAnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D646C957213B367000269FB5 /* LargeImageShrinkAnimationController.swift */; };
D646C95A213B5D0500269FB5 /* LargeImageInteractionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D646C959213B5D0500269FB5 /* LargeImageInteractionController.swift */; }; D646C95A213B5D0500269FB5 /* LargeImageInteractionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D646C959213B5D0500269FB5 /* LargeImageInteractionController.swift */; };
D647D92824257BEB0005044F /* AttachmentPreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D647D92724257BEB0005044F /* AttachmentPreviewViewController.swift */; };
D64BC18623C1253A000D0238 /* AssetPreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64BC18523C1253A000D0238 /* AssetPreviewViewController.swift */; }; D64BC18623C1253A000D0238 /* AssetPreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64BC18523C1253A000D0238 /* AssetPreviewViewController.swift */; };
D64BC18823C1640A000D0238 /* PinStatusActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64BC18723C1640A000D0238 /* PinStatusActivity.swift */; }; D64BC18823C1640A000D0238 /* PinStatusActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64BC18723C1640A000D0238 /* PinStatusActivity.swift */; };
D64BC18A23C16487000D0238 /* UnpinStatusActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64BC18923C16487000D0238 /* UnpinStatusActivity.swift */; }; D64BC18A23C16487000D0238 /* UnpinStatusActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64BC18923C16487000D0238 /* UnpinStatusActivity.swift */; };
@ -398,6 +399,7 @@
D646C955213B365700269FB5 /* LargeImageExpandAnimationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeImageExpandAnimationController.swift; sourceTree = "<group>"; }; D646C955213B365700269FB5 /* LargeImageExpandAnimationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeImageExpandAnimationController.swift; sourceTree = "<group>"; };
D646C957213B367000269FB5 /* LargeImageShrinkAnimationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeImageShrinkAnimationController.swift; sourceTree = "<group>"; }; D646C957213B367000269FB5 /* LargeImageShrinkAnimationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeImageShrinkAnimationController.swift; sourceTree = "<group>"; };
D646C959213B5D0500269FB5 /* LargeImageInteractionController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeImageInteractionController.swift; sourceTree = "<group>"; }; D646C959213B5D0500269FB5 /* LargeImageInteractionController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeImageInteractionController.swift; sourceTree = "<group>"; };
D647D92724257BEB0005044F /* AttachmentPreviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentPreviewViewController.swift; sourceTree = "<group>"; };
D64BC18523C1253A000D0238 /* AssetPreviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetPreviewViewController.swift; sourceTree = "<group>"; }; D64BC18523C1253A000D0238 /* AssetPreviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetPreviewViewController.swift; sourceTree = "<group>"; };
D64BC18723C1640A000D0238 /* PinStatusActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinStatusActivity.swift; sourceTree = "<group>"; }; D64BC18723C1640A000D0238 /* PinStatusActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinStatusActivity.swift; sourceTree = "<group>"; };
D64BC18923C16487000D0238 /* UnpinStatusActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnpinStatusActivity.swift; sourceTree = "<group>"; }; D64BC18923C16487000D0238 /* UnpinStatusActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnpinStatusActivity.swift; sourceTree = "<group>"; };
@ -570,6 +572,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
04D14BAE22B34A2800642648 /* GalleryViewController.swift */, 04D14BAE22B34A2800642648 /* GalleryViewController.swift */,
D647D92724257BEB0005044F /* AttachmentPreviewViewController.swift */,
); );
path = "Attachment Gallery"; path = "Attachment Gallery";
sourceTree = "<group>"; sourceTree = "<group>";
@ -1698,6 +1701,7 @@
04586B4322B301470021BD04 /* AppearancePrefsView.swift in Sources */, 04586B4322B301470021BD04 /* AppearancePrefsView.swift in Sources */,
D67C57AF21E28EAD00C3118B /* Array+Uniques.swift in Sources */, D67C57AF21E28EAD00C3118B /* Array+Uniques.swift in Sources */,
D6945C3223AC4D36005C403C /* HashtagTimelineViewController.swift in Sources */, D6945C3223AC4D36005C403C /* HashtagTimelineViewController.swift in Sources */,
D647D92824257BEB0005044F /* AttachmentPreviewViewController.swift in Sources */,
D66362752137068A00C9CBA2 /* Visibility+Helpers.swift in Sources */, D66362752137068A00C9CBA2 /* Visibility+Helpers.swift in Sources */,
D6C693FC2162FE6F007D6A6D /* LoadingViewController.swift in Sources */, D6C693FC2162FE6F007D6A6D /* LoadingViewController.swift in Sources */,
D646C95A213B5D0500269FB5 /* LargeImageInteractionController.swift in Sources */, D646C95A213B5D0500269FB5 /* LargeImageInteractionController.swift in Sources */,

View File

@ -0,0 +1,46 @@
//
// AttachmentPreviewViewController.swift
// Tusker
//
// Created by Shadowfacts on 3/20/20.
// Copyright © 2020 Shadowfacts. All rights reserved.
//
import UIKit
import Pachyderm
import Gifu
class AttachmentPreviewViewController: UIViewController {
let attachment: Attachment
init(attachment: Attachment) {
self.attachment = attachment
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func loadView() {
if let data = ImageCache.attachments.get(attachment.url),
let image = UIImage(data: data) {
let imageView: UIImageView
if attachment.url.pathExtension == "gif" {
let gifView = GIFImageView(image: image)
gifView.animate(withGIFData: data)
imageView = gifView
} else {
imageView = UIImageView(image: image)
}
imageView.contentMode = .scaleAspectFit
imageView.backgroundColor = .black
view = imageView
preferredContentSize = image.size
} else {
view = UIActivityIndicatorView(style: .large)
}
}
}

View File

@ -72,8 +72,8 @@ extension EnhancedTableViewController {
if let viewController = animator.previewViewController { if let viewController = animator.previewViewController {
animator.preferredCommitStyle = .pop animator.preferredCommitStyle = .pop
animator.addCompletion { animator.addCompletion {
if viewController is LargeImageViewController || viewController is GalleryViewController || viewController is SFSafariViewController { if let customPresenting = viewController as? CustomPreviewPresenting {
self.present(viewController, animated: true) customPresenting.presentFromPreview(presenter: self)
} else { } else {
self.show(viewController, sender: nil) self.show(viewController, sender: nil)
} }

View File

@ -20,6 +20,10 @@ protocol MenuPreviewProvider {
} }
protocol CustomPreviewPresenting {
func presentFromPreview(presenter: UIViewController)
}
extension MenuPreviewProvider { extension MenuPreviewProvider {
private var mastodonController: MastodonController? { navigationDelegate?.apiController } private var mastodonController: MastodonController? { navigationDelegate?.apiController }
@ -76,3 +80,21 @@ extension MenuPreviewProvider {
} }
} }
extension LargeImageViewController: CustomPreviewPresenting {
func presentFromPreview(presenter: UIViewController) {
presenter.present(self, animated: true)
}
}
extension GalleryViewController: CustomPreviewPresenting {
func presentFromPreview(presenter: UIViewController) {
presenter.present(self, animated: true)
}
}
extension SFSafariViewController: CustomPreviewPresenting {
func presentFromPreview(presenter: UIViewController) {
presenter.present(self, animated: true)
}
}

View File

@ -12,7 +12,8 @@ import Gifu
import AVFoundation import AVFoundation
protocol AttachmentViewDelegate: class { protocol AttachmentViewDelegate: class {
func showAttachmentsGallery(startingAt index: Int) func attachmentViewGallery(startingAt index: Int) -> UIViewController
func attachmentViewPresent(_ vc: UIViewController, animated: Bool)
} }
class AttachmentView: UIImageView, GIFAnimatable { class AttachmentView: UIImageView, GIFAnimatable {
@ -55,6 +56,8 @@ class AttachmentView: UIImageView, GIFAnimatable {
addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(imagePressed))) addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(imagePressed)))
NotificationCenter.default.addObserver(self, selector: #selector(preferencesChanged), name: .preferencesChanged, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(preferencesChanged), name: .preferencesChanged, object: nil)
addInteraction(UIContextMenuInteraction(delegate: self))
} }
@objc func preferencesChanged() { @objc func preferencesChanged() {
@ -151,8 +154,37 @@ class AttachmentView: UIImageView, GIFAnimatable {
updateImageIfNeeded() updateImageIfNeeded()
} }
func showGallery() {
if let delegate = delegate {
let gallery = delegate.attachmentViewGallery(startingAt: index)
delegate.attachmentViewPresent(gallery, animated: true)
}
}
@objc func imagePressed() { @objc func imagePressed() {
delegate?.showAttachmentsGallery(startingAt: index) showGallery()
} }
} }
extension AttachmentView: UIContextMenuInteractionDelegate {
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
return UIContextMenuConfiguration(identifier: nil, previewProvider: { () -> UIViewController? in
if self.attachment.kind == .image {
return AttachmentPreviewViewController(attachment: self.attachment)
} else {
return self.delegate?.attachmentViewGallery(startingAt: self.index)
}
}, actionProvider: nil)
}
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) {
animator.addCompletion {
animator.preferredCommitStyle = .pop
if let gallery = animator.previewViewController as? GalleryViewController {
self.delegate?.attachmentViewPresent(gallery, animated: true)
} else {
self.showGallery()
}
}
}
}

View File

@ -64,12 +64,20 @@ class AttachmentsContainerView: UIView {
switch attachments.count { switch attachments.count {
case 1: case 1:
let attachmentView = createAttachmentView(index: 0) let attachmentView = createAttachmentView(index: 0)
attachmentView.layer.cornerRadius = 5
attachmentView.layer.masksToBounds = true
fillView(attachmentView) fillView(attachmentView)
sendSubviewToBack(attachmentView) sendSubviewToBack(attachmentView)
accessibilityElements.append(attachmentView) accessibilityElements.append(attachmentView)
case 2: case 2:
let left = createAttachmentView(index: 0) let left = createAttachmentView(index: 0)
left.layer.cornerRadius = 5
left.layer.maskedCorners = [.layerMinXMinYCorner, .layerMinXMaxYCorner]
left.layer.masksToBounds = true
let right = createAttachmentView(index: 1) let right = createAttachmentView(index: 1)
right.layer.cornerRadius = 5
right.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMaxXMaxYCorner]
right.layer.masksToBounds = true
let stack = createAttachmentsStack(axis: .horizontal, arrangedSubviews: [ let stack = createAttachmentsStack(axis: .horizontal, arrangedSubviews: [
left, left,
right right
@ -83,8 +91,17 @@ class AttachmentsContainerView: UIView {
accessibilityElements.append(right) accessibilityElements.append(right)
case 3: case 3:
let left = createAttachmentView(index: 0) let left = createAttachmentView(index: 0)
left.layer.cornerRadius = 5
left.layer.maskedCorners = [.layerMinXMinYCorner, .layerMinXMaxYCorner]
left.layer.masksToBounds = true
let topRight = createAttachmentView(index: 1) let topRight = createAttachmentView(index: 1)
topRight.layer.cornerRadius = 5
topRight.layer.maskedCorners = .layerMaxXMinYCorner
topRight.layer.masksToBounds = true
let bottomRight = createAttachmentView(index: 2) let bottomRight = createAttachmentView(index: 2)
bottomRight.layer.cornerRadius = 5
bottomRight.layer.maskedCorners = .layerMaxXMaxYCorner
bottomRight.layer.masksToBounds = true
let outerStack = createAttachmentsStack(axis: .horizontal, arrangedSubviews: [ let outerStack = createAttachmentsStack(axis: .horizontal, arrangedSubviews: [
left, left,
createAttachmentsStack(axis: .vertical, arrangedSubviews: [ createAttachmentsStack(axis: .vertical, arrangedSubviews: [
@ -103,13 +120,25 @@ class AttachmentsContainerView: UIView {
accessibilityElements.append(bottomRight) accessibilityElements.append(bottomRight)
case 4: case 4:
let topLeft = createAttachmentView(index: 0) let topLeft = createAttachmentView(index: 0)
topLeft.layer.cornerRadius = 5
topLeft.layer.maskedCorners = .layerMinXMinYCorner
topLeft.layer.masksToBounds = true
let bottomLeft = createAttachmentView(index: 2) let bottomLeft = createAttachmentView(index: 2)
bottomLeft.layer.cornerRadius = 5
bottomLeft.layer.maskedCorners = .layerMinXMaxYCorner
bottomLeft.layer.masksToBounds = true
let left = createAttachmentsStack(axis: .vertical, arrangedSubviews: [ let left = createAttachmentsStack(axis: .vertical, arrangedSubviews: [
topLeft, topLeft,
bottomLeft bottomLeft
]) ])
let topRight = createAttachmentView(index: 1) let topRight = createAttachmentView(index: 1)
topRight.layer.cornerRadius = 5
topRight.layer.maskedCorners = .layerMaxXMinYCorner
topRight.layer.masksToBounds = true
let bottomRight = createAttachmentView(index: 3) let bottomRight = createAttachmentView(index: 3)
bottomRight.layer.cornerRadius = 5
bottomRight.layer.maskedCorners = .layerMaxXMaxYCorner
bottomRight.layer.masksToBounds = true
let outerStack = createAttachmentsStack(axis: .horizontal, arrangedSubviews: [ let outerStack = createAttachmentsStack(axis: .horizontal, arrangedSubviews: [
left, left,
createAttachmentsStack(axis: .vertical, arrangedSubviews: [ createAttachmentsStack(axis: .vertical, arrangedSubviews: [
@ -135,6 +164,9 @@ class AttachmentsContainerView: UIView {
moreView.translatesAutoresizingMaskIntoConstraints = false moreView.translatesAutoresizingMaskIntoConstraints = false
moreView.isUserInteractionEnabled = true moreView.isUserInteractionEnabled = true
moreView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(moreViewTapped))) moreView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(moreViewTapped)))
moreView.layer.cornerRadius = 5
moreView.layer.maskedCorners = .layerMaxXMaxYCorner
moreView.layer.masksToBounds = true
let moreLabel = UILabel() let moreLabel = UILabel()
moreLabel.text = "\(attachments.count - 3) more..." moreLabel.text = "\(attachments.count - 3) more..."
moreLabel.textColor = .secondaryLabel moreLabel.textColor = .secondaryLabel
@ -144,12 +176,21 @@ class AttachmentsContainerView: UIView {
moreView.accessibilityLabel = moreLabel.text moreView.accessibilityLabel = moreLabel.text
let topLeft = createAttachmentView(index: 0) let topLeft = createAttachmentView(index: 0)
topLeft.layer.cornerRadius = 5
topLeft.layer.maskedCorners = .layerMinXMinYCorner
topLeft.layer.masksToBounds = true
let bottomLeft = createAttachmentView(index: 2) let bottomLeft = createAttachmentView(index: 2)
bottomLeft.layer.cornerRadius = 5
bottomLeft.layer.maskedCorners = .layerMinXMaxYCorner
bottomLeft.layer.masksToBounds = true
let left = createAttachmentsStack(axis: .vertical, arrangedSubviews: [ let left = createAttachmentsStack(axis: .vertical, arrangedSubviews: [
topLeft, topLeft,
bottomLeft bottomLeft
]) ])
let topRight = createAttachmentView(index: 1) let topRight = createAttachmentView(index: 1)
topRight.layer.cornerRadius = 5
topRight.layer.maskedCorners = .layerMaxXMinYCorner
topRight.layer.masksToBounds = true
let outerStack = createAttachmentsStack(axis: .horizontal, arrangedSubviews: [ let outerStack = createAttachmentsStack(axis: .horizontal, arrangedSubviews: [
left, left,
createAttachmentsStack(axis: .vertical, arrangedSubviews: [ createAttachmentsStack(axis: .vertical, arrangedSubviews: [
@ -325,7 +366,10 @@ class AttachmentsContainerView: UIView {
@objc func moreViewTapped() { @objc func moreViewTapped() {
guard attachments.count > 4 else { return } guard attachments.count > 4 else { return }
// the more view shows up in place of the fourth attachemtn view, show tapping it should start at the fourth attachment // the more view shows up in place of the fourth attachemtn view, show tapping it should start at the fourth attachment
delegate?.showAttachmentsGallery(startingAt: 3) if let delegate = delegate {
let gallery = delegate.attachmentViewGallery(startingAt: 3)
delegate.attachmentViewPresent(gallery, animated: true)
}
} }
} }

View File

@ -84,8 +84,6 @@ class BaseStatusTableViewCell: UITableViewCell {
avatarImageView.layer.masksToBounds = true avatarImageView.layer.masksToBounds = true
attachmentsView.delegate = self attachmentsView.delegate = self
attachmentsView.layer.cornerRadius = 5
attachmentsView.layer.masksToBounds = true
collapseButton.layer.masksToBounds = true collapseButton.layer.masksToBounds = true
collapseButton.layer.cornerRadius = 5 collapseButton.layer.cornerRadius = 5
@ -312,10 +310,14 @@ class BaseStatusTableViewCell: UITableViewCell {
} }
extension BaseStatusTableViewCell: AttachmentViewDelegate { extension BaseStatusTableViewCell: AttachmentViewDelegate {
func showAttachmentsGallery(startingAt index: Int) { func attachmentViewGallery(startingAt index: Int) -> UIViewController {
guard let status = mastodonController.cache.status(for: statusID) else { fatalError("Missing cached status \(statusID!)") } guard let status = mastodonController.cache.status(for: statusID) else { fatalError("Missing cached status \(statusID!)") }
let sourceViews = status.attachments.map(attachmentsView.getAttachmentView(for:)) let sourceViews = status.attachments.map(attachmentsView.getAttachmentView(for:))
delegate?.showGallery(attachments: status.attachments, sourceViews: sourceViews, startIndex: index) return delegate!.gallery(attachments: status.attachments, sourceViews: sourceViews, startIndex: index)
}
func attachmentViewPresent(_ vc: UIViewController, animated: Bool) {
delegate?.show(vc)
} }
} }
@ -329,19 +331,6 @@ extension BaseStatusTableViewCell: MenuPreviewProvider {
content: { ProfileTableViewController(accountID: self.accountID, mastodonController: mastodonController) }, content: { ProfileTableViewController(accountID: self.accountID, mastodonController: mastodonController) },
actions: { self.actionsForProfile(accountID: self.accountID, sourceView: self.avatarImageView) } actions: { self.actionsForProfile(accountID: self.accountID, sourceView: self.avatarImageView) }
) )
} else if attachmentsView.frame.contains(location) {
let attachmentsViewLocation = attachmentsView.convert(location, from: self)
if let attachmentView = attachmentsView.attachmentViews.allObjects.first(where: { $0.frame.contains(attachmentsViewLocation) }) {
return (
content: {
let attachments = self.attachmentsView.attachments!
let sourceViews = attachments.map(self.attachmentsView.getAttachmentView(for:))
let startIndex = sourceViews.firstIndex(of: attachmentView)!
return self.delegate?.gallery(attachments: attachments, sourceViews: sourceViews, startIndex: startIndex)
},
actions: { [] }
)
}
} }
return self.getStatusCellPreviewProviders(for: location, sourceViewController: sourceViewController) return self.getStatusCellPreviewProviders(for: location, sourceViewController: sourceViewController)
} }