Finished off UI clean up

Fixed a couple more vertical alignment issues with certain attachment types.
Finished cleaning up the MediaMessageView UI code (removed old code).
pull/548/head
Morgan Pretty 3 years ago
parent 67ad965859
commit e6c90c5e18

@ -20,6 +20,18 @@ public enum SignalAttachmentError: Error {
case couldNotResizeImage case couldNotResizeImage
} }
@objc
public enum SignalAttachmentType: Int {
case text
case oversizeText
case image
case animatedImage
case video
case audio
case url
case unknown
}
extension String { extension String {
public var filenameWithoutExtension: String { public var filenameWithoutExtension: String {
return (self as NSString).deletingPathExtension return (self as NSString).deletingPathExtension
@ -435,6 +447,19 @@ public class SignalAttachment: NSObject {
return audioUTISet.union(videoUTISet).union(animatedImageUTISet).union(inputImageUTISet) return audioUTISet.union(videoUTISet).union(animatedImageUTISet).union(inputImageUTISet)
} }
@objc
public var fileType: SignalAttachmentType {
if isAnimatedImage { return .animatedImage }
if isImage { return .image }
if isVideo { return .video }
if isAudio { return .audio }
if isUrl { return .url }
if isOversizeText { return .oversizeText }
if isText { return .text }
return .unknown
}
@objc @objc
public var isImage: Bool { public var isImage: Bool {
return SignalAttachment.outputImageUTISet.contains(dataUTI) return SignalAttachment.outputImageUTISet.contains(dataUTI)

@ -45,6 +45,10 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD
// MARK: - UI // MARK: - UI
fileprivate static let verticalCenterOffset: CGFloat = (
AttachmentTextToolbar.kMinTextViewHeight + (AttachmentTextToolbar.kToolbarMargin * 2)
)
private lazy var scrollView: UIScrollView = { private lazy var scrollView: UIScrollView = {
// Scroll View - used to zoom/pan on images and video // Scroll View - used to zoom/pan on images and video
let scrollView: UIScrollView = UIScrollView() let scrollView: UIScrollView = UIScrollView()
@ -56,17 +60,6 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD
// Panning should stop pretty soon after the user stops scrolling // Panning should stop pretty soon after the user stops scrolling
scrollView.decelerationRate = UIScrollView.DecelerationRate.fast scrollView.decelerationRate = UIScrollView.DecelerationRate.fast
// If the content isn't zoomable then inset the content so it appears centered
if !isZoomable {
scrollView.isScrollEnabled = false
scrollView.contentInset = UIEdgeInsets(
top: 0,
leading: 0,
bottom: (AttachmentTextToolbar.kMinTextViewHeight + (AttachmentTextToolbar.kToolbarMargin * 2)),
trailing: 0
)
}
return scrollView return scrollView
}() }()
@ -112,6 +105,7 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD
private lazy var progressBar: PlayerProgressBar = { private lazy var progressBar: PlayerProgressBar = {
let progressBar: PlayerProgressBar = PlayerProgressBar() let progressBar: PlayerProgressBar = PlayerProgressBar()
progressBar.translatesAutoresizingMaskIntoConstraints = false
progressBar.player = videoPlayer?.avPlayer progressBar.player = videoPlayer?.avPlayer
progressBar.delegate = self progressBar.delegate = self
@ -161,7 +155,7 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD
contentContainerView.addSubview(scrollView) contentContainerView.addSubview(scrollView)
scrollView.addSubview(mediaMessageView) scrollView.addSubview(mediaMessageView)
if let editorView: ImageEditorView = imageEditorView { if attachment.isImage, let editorView: ImageEditorView = imageEditorView {
view.addSubview(editorView) view.addSubview(editorView)
imageEditorUpdateNavigationBar() imageEditorUpdateNavigationBar()
@ -235,12 +229,20 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD
mediaMessageView.heightAnchor.constraint(equalTo: view.heightAnchor) mediaMessageView.heightAnchor.constraint(equalTo: view.heightAnchor)
]) ])
if let editorView: ImageEditorView = imageEditorView { if attachment.isImage, let editorView: ImageEditorView = imageEditorView {
let size: CGSize = (attachment.image()?.size ?? CGSize.zero)
let isPortrait: Bool = (size.height > size.width)
NSLayoutConstraint.activate([ NSLayoutConstraint.activate([
editorView.topAnchor.constraint(equalTo: view.topAnchor), editorView.topAnchor.constraint(equalTo: view.topAnchor),
editorView.leftAnchor.constraint(equalTo: view.leftAnchor), editorView.leftAnchor.constraint(equalTo: view.leftAnchor),
editorView.rightAnchor.constraint(equalTo: view.rightAnchor), editorView.rightAnchor.constraint(equalTo: view.rightAnchor),
editorView.bottomAnchor.constraint(equalTo: view.bottomAnchor) editorView.bottomAnchor.constraint(
equalTo: view.bottomAnchor,
// Don't offset portrait images as they look fine vertically aligned, horizontal
// ones need to be pushed up a bit though
constant: (isPortrait ? 0 : -AttachmentPrepViewController.verticalCenterOffset)
)
]) ])
} }
@ -258,7 +260,10 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD
progressBar.heightAnchor.constraint(equalToConstant: 44), progressBar.heightAnchor.constraint(equalToConstant: 44),
playVideoButton.centerXAnchor.constraint(equalTo: contentContainerView.centerXAnchor), playVideoButton.centerXAnchor.constraint(equalTo: contentContainerView.centerXAnchor),
playVideoButton.centerYAnchor.constraint(equalTo: contentContainerView.centerYAnchor), playVideoButton.centerYAnchor.constraint(
equalTo: contentContainerView.centerYAnchor,
constant: -AttachmentPrepViewController.verticalCenterOffset
),
playVideoButton.widthAnchor.constraint(equalToConstant: playButtonSize), playVideoButton.widthAnchor.constraint(equalToConstant: playButtonSize),
playVideoButton.heightAnchor.constraint(equalToConstant: playButtonSize), playVideoButton.heightAnchor.constraint(equalToConstant: playButtonSize),
]) ])
@ -455,8 +460,14 @@ extension AttachmentPrepViewController: UIScrollViewDelegate {
// Allow the user to zoom out to 100% of the attachment size if it's smaller // Allow the user to zoom out to 100% of the attachment size if it's smaller
// than the screen // than the screen
fileprivate func resetContentInset() { fileprivate func resetContentInset() {
// If the content isn't zoomable then inset the content so it appears centered
guard isZoomable else { guard isZoomable else {
scrollView.contentOffset = CGPoint(x: 0, y: scrollView.contentInset.bottom) scrollView.contentInset = UIEdgeInsets(
top: -AttachmentPrepViewController.verticalCenterOffset,
leading: 0,
bottom: 0,
trailing: 0
)
return return
} }
@ -464,7 +475,7 @@ extension AttachmentPrepViewController: UIScrollViewDelegate {
let offsetY: CGFloat = max((scrollView.bounds.height - scrollView.contentSize.height) * 0.5, 0) let offsetY: CGFloat = max((scrollView.bounds.height - scrollView.contentSize.height) * 0.5, 0)
scrollView.contentInset = UIEdgeInsets( scrollView.contentInset = UIEdgeInsets(
top: offsetY, top: offsetY - AttachmentPrepViewController.verticalCenterOffset,
left: offsetX, left: offsetX,
bottom: 0, bottom: 0,
right: 0 right: 0

@ -20,10 +20,14 @@ public class MediaMessageView: UIView, OWSAudioPlayerDelegate {
public let mode: Mode public let mode: Mode
public let attachment: SignalAttachment public let attachment: SignalAttachment
public var audioPlayer: OWSAudioPlayer? public lazy var audioPlayer: OWSAudioPlayer? = {
guard let dataUrl = attachment.dataUrl else { return nil }
private var linkPreviewInfo: (url: String, draft: OWSLinkPreviewDraft?)? return OWSAudioPlayer(mediaUrl: dataUrl, audioBehavior: .playback, delegate: self)
}()
public var audioProgressSeconds: CGFloat = 0
public var audioDurationSeconds: CGFloat = 0
public var playbackState = AudioPlaybackState.stopped { public var playbackState = AudioPlaybackState.stopped {
didSet { didSet {
@ -33,12 +37,50 @@ public class MediaMessageView: UIView, OWSAudioPlayerDelegate {
} }
} }
public var audioProgressSeconds: CGFloat = 0 private lazy var validImage: UIImage? = {
public var audioDurationSeconds: CGFloat = 0 switch attachment.fileType {
case .image:
guard
attachment.isValidImage,
let image: UIImage = attachment.image(),
image.size.width > 0,
image.size.height > 0
else {
return nil
}
public var contentView: UIView? return image
case .video:
guard
attachment.isValidVideo,
let image: UIImage = attachment.videoPreview(),
image.size.width > 0,
image.size.height > 0
else {
return nil
}
return image
default: return nil
}
}()
private lazy var validAnimatedImage: YYImage? = {
guard
attachment.fileType == .animatedImage,
attachment.isValidImage,
let dataUrl: URL = attachment.dataUrl,
let image: YYImage = YYImage(contentsOfFile: dataUrl.path),
image.size.width > 0,
image.size.height > 0
else {
return nil
}
return image
}()
private var linkPreviewInfo: (url: String, draft: OWSLinkPreviewDraft?)?
// MARK: Initializers // MARK: Initializers
@ -55,11 +97,14 @@ public class MediaMessageView: UIView, OWSAudioPlayerDelegate {
self.attachment = attachment self.attachment = attachment
self.mode = mode self.mode = mode
super.init(frame: CGRect.zero) // Set the linkPreviewUrl if it's a url
if attachment.isUrl, let linkPreviewURL: String = OWSLinkPreview.previewURL(forRawBodyText: attachment.text()) {
createViews() self.linkPreviewInfo = (url: linkPreviewURL, draft: nil)
}
super.init(frame: CGRect.zero)
setupViews()
setupLayout() setupLayout()
} }
@ -74,7 +119,7 @@ public class MediaMessageView: UIView, OWSAudioPlayerDelegate {
stackView.translatesAutoresizingMaskIntoConstraints = false stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical stackView.axis = .vertical
stackView.alignment = .center stackView.alignment = .center
stackView.distribution = .equalSpacing stackView.distribution = .fill
switch mode { switch mode {
case .attachmentApproval: stackView.spacing = 2 case .attachmentApproval: stackView.spacing = 2
@ -97,8 +142,28 @@ public class MediaMessageView: UIView, OWSAudioPlayerDelegate {
let view: UIImageView = UIImageView() let view: UIImageView = UIImageView()
view.translatesAutoresizingMaskIntoConstraints = false view.translatesAutoresizingMaskIntoConstraints = false
view.contentMode = .scaleAspectFit view.contentMode = .scaleAspectFit
view.image = UIImage(named: "FileLarge")?.withRenderingMode(.alwaysTemplate)
view.tintColor = Colors.text
view.isHidden = true
// Override the image to the correct one
switch attachment.fileType {
case .image, .video:
if let validImage: UIImage = validImage {
view.layer.minificationFilter = .trilinear view.layer.minificationFilter = .trilinear
view.layer.magnificationFilter = .trilinear view.layer.magnificationFilter = .trilinear
view.image = validImage
}
case .url:
view.clipsToBounds = true
view.image = UIImage(named: "Link")?.withTint(Colors.text)
view.contentMode = .center
view.backgroundColor = (isDarkMode ? .black : UIColor.black.withAlphaComponent(0.06))
view.layer.cornerRadius = 8
default: break
}
return view return view
}() }()
@ -106,6 +171,7 @@ public class MediaMessageView: UIView, OWSAudioPlayerDelegate {
private lazy var fileTypeImageView: UIImageView = { private lazy var fileTypeImageView: UIImageView = {
let view: UIImageView = UIImageView() let view: UIImageView = UIImageView()
view.translatesAutoresizingMaskIntoConstraints = false view.translatesAutoresizingMaskIntoConstraints = false
view.isHidden = true
return view return view
}() }()
@ -113,16 +179,27 @@ public class MediaMessageView: UIView, OWSAudioPlayerDelegate {
private lazy var animatedImageView: YYAnimatedImageView = { private lazy var animatedImageView: YYAnimatedImageView = {
let view: YYAnimatedImageView = YYAnimatedImageView() let view: YYAnimatedImageView = YYAnimatedImageView()
view.translatesAutoresizingMaskIntoConstraints = false view.translatesAutoresizingMaskIntoConstraints = false
view.isHidden = true
if let image: YYImage = validAnimatedImage {
view.image = image
}
else {
view.contentMode = .scaleAspectFit
view.image = UIImage(named: "FileLarge")?.withRenderingMode(.alwaysTemplate)
view.tintColor = Colors.text
}
return view return view
}() }()
lazy var videoPlayButton: UIImageView = { lazy var videoPlayButton: UIImageView = {
let imageView: UIImageView = UIImageView(image: UIImage(named: "CirclePlay")) let view: UIImageView = UIImageView(image: UIImage(named: "CirclePlay"))
imageView.translatesAutoresizingMaskIntoConstraints = false view.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFit view.contentMode = .scaleAspectFit
view.isHidden = true
return imageView return view
}() }()
/// Note: This uses different assets from the `videoPlayButton` and has a 'Pause' state /// Note: This uses different assets from the `videoPlayButton` and has a 'Pause' state
@ -133,6 +210,7 @@ public class MediaMessageView: UIView, OWSAudioPlayerDelegate {
button.setBackgroundImage(UIColor.white.toImage(), for: .normal) button.setBackgroundImage(UIColor.white.toImage(), for: .normal)
button.setBackgroundImage(UIColor.white.darken(by: 0.2).toImage(), for: .highlighted) button.setBackgroundImage(UIColor.white.darken(by: 0.2).toImage(), for: .highlighted)
button.addTarget(self, action: #selector(audioPlayPauseButtonPressed), for: .touchUpInside) button.addTarget(self, action: #selector(audioPlayPauseButtonPressed), for: .touchUpInside)
button.isHidden = true
return button return button
}() }()
@ -140,21 +218,8 @@ public class MediaMessageView: UIView, OWSAudioPlayerDelegate {
private lazy var titleLabel: UILabel = { private lazy var titleLabel: UILabel = {
let label: UILabel = UILabel() let label: UILabel = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false label.translatesAutoresizingMaskIntoConstraints = false
label.textAlignment = .center
label.lineBreakMode = .byTruncatingMiddle
if let fileName: String = attachment.sourceFilename?.trimmingCharacters(in: .whitespacesAndNewlines), fileName.count > 0 {
label.text = fileName
}
else if let fileExtension: String = attachment.fileExtension {
label.text = String(
format: "ATTACHMENT_APPROVAL_FILE_EXTENSION_FORMAT".localized(),
fileExtension.uppercased()
)
}
label.isHidden = ((label.text?.count ?? 0) == 0)
// Styling
switch mode { switch mode {
case .attachmentApproval: case .attachmentApproval:
label.font = UIFont.ows_boldFont(withSize: ScaleFromIPhone5To7Plus(16, 22)) label.font = UIFont.ows_boldFont(withSize: ScaleFromIPhone5To7Plus(16, 22))
@ -169,19 +234,49 @@ public class MediaMessageView: UIView, OWSAudioPlayerDelegate {
label.textColor = Colors.accent label.textColor = Colors.accent
} }
// Content
switch attachment.fileType {
case .image, .animatedImage, .video: break // No title for these
case .url:
// If we have no link preview info at this point then assume link previews are disabled
guard let linkPreviewURL: String = linkPreviewInfo?.url else {
label.text = "vc_share_link_previews_disabled_title".localized()
break
}
label.font = .boldSystemFont(ofSize: Values.smallFontSize)
label.text = linkPreviewURL
label.textAlignment = .left
label.lineBreakMode = .byTruncatingTail
label.numberOfLines = 2
default:
if let fileName: String = attachment.sourceFilename?.trimmingCharacters(in: .whitespacesAndNewlines), fileName.count > 0 {
label.text = fileName
}
else if let fileExtension: String = attachment.fileExtension {
label.text = String(
format: "ATTACHMENT_APPROVAL_FILE_EXTENSION_FORMAT".localized(),
fileExtension.uppercased()
)
}
label.textAlignment = .center
label.lineBreakMode = .byTruncatingMiddle
}
// Hide the label if it has no content
label.isHidden = ((label.text?.count ?? 0) == 0)
return label return label
}() }()
private lazy var fileSizeLabel: UILabel = { private lazy var fileSizeLabel: UILabel = {
let fileSize: UInt = attachment.dataLength
let label: UILabel = UILabel() let label: UILabel = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false label.translatesAutoresizingMaskIntoConstraints = false
// Format string for file size label in call interstitial view.
// Embeds: {{file size as 'N mb' or 'N kb'}}.
label.text = String(format: "ATTACHMENT_APPROVAL_FILE_SIZE_FORMAT".localized(), OWSFormat.formatFileSize(UInt(fileSize)))
label.textAlignment = .center
// Styling
switch mode { switch mode {
case .attachmentApproval: case .attachmentApproval:
label.font = UIFont.ows_regularFont(withSize: ScaleFromIPhone5To7Plus(12, 18)) label.font = UIFont.ows_regularFont(withSize: ScaleFromIPhone5To7Plus(12, 18))
@ -196,137 +291,193 @@ public class MediaMessageView: UIView, OWSAudioPlayerDelegate {
label.textColor = Colors.accent label.textColor = Colors.accent
} }
return label // Content
}() switch attachment.fileType {
case .image, .animatedImage, .video: break // No size for these
// MARK: - Layout case .url:
// If we have no link preview info at this point then assume link previews are disabled
private func createViews() { if linkPreviewInfo == nil {
if attachment.isAnimatedImage { label.text = "vc_share_link_previews_disabled_explanation".localized()
createAnimatedPreview() label.textColor = Colors.text
} else if attachment.isImage { label.textAlignment = .center
createImagePreview() label.numberOfLines = 0
} else if attachment.isVideo { break
createVideoPreview()
} else if attachment.isAudio {
createAudioPreview()
} else if attachment.isUrl {
createUrlPreview()
} else if attachment.isText {
// Do nothing as we will just put the text in the 'message' input
} else {
createGenericPreview()
}
} }
private func setupLayout() { default:
// Bottom inset // Format string for file size label in call interstitial view.
// Embeds: {{file size as 'N mb' or 'N kb'}}.
let fileSize: UInt = attachment.dataLength
label.text = String(format: "ATTACHMENT_APPROVAL_FILE_SIZE_FORMAT".localized(), OWSFormat.formatFileSize(UInt(fileSize)))
label.textAlignment = .center
} }
// TODO: Any reason for not just using UIStackView // Hide the label if it has no content
private func wrapViewsInVerticalStack(subviews: [UIView]) -> UIView { label.isHidden = ((label.text?.count ?? 0) == 0)
assert(subviews.count > 0)
let stackView = UIView() return label
}()
var lastView: UIView? // MARK: - Layout
for subview in subviews {
stackView.addSubview(subview) private func setupViews() {
subview.autoHCenterInSuperview() // Plain text will just be put in the 'message' input so do nothing
guard attachment.fileType != .text && attachment.fileType != .oversizeText else { return }
if lastView == nil { // Setup the view hierarchy
subview.autoPinEdge(toSuperviewEdge: .top) addSubview(stackView)
} else { addSubview(loadingView)
subview.autoPinEdge(.top, to: .bottom, of: lastView!, withOffset: 10) addSubview(videoPlayButton)
}
lastView = subview stackView.addArrangedSubview(imageView)
} stackView.addArrangedSubview(animatedImageView)
if !titleLabel.isHidden { stackView.addArrangedSubview(UIView.vhSpacer(10, 10)) }
stackView.addArrangedSubview(titleLabel)
stackView.addArrangedSubview(fileSizeLabel)
lastView?.autoPinEdge(toSuperviewEdge: .bottom) imageView.addSubview(fileTypeImageView)
return stackView // Type-specific configurations
} switch attachment.fileType {
case .animatedImage: animatedImageView.isHidden = false
case .image: imageView.isHidden = false
case .video:
// Note: The 'attachmentApproval' mode provides it's own play button to keep
// it at the proper scale when zooming
imageView.isHidden = false
videoPlayButton.isHidden = (mode == .attachmentApproval)
case .audio:
// Hide the 'audioPlayPauseButton' if the 'audioPlayer' failed to get created
imageView.isHidden = false
audioPlayPauseButton.isHidden = (audioPlayer == nil)
setAudioIconToPlay()
private func wrapViewsInHorizontalStack(subviews: [UIView]) -> UIView { fileTypeImageView.image = UIImage(named: "table_ic_notification_sound")?
assert(subviews.count > 0) .withRenderingMode(.alwaysTemplate)
fileTypeImageView.tintColor = Colors.text
fileTypeImageView.isHidden = false
let stackView = UIView() // Note: There is an annoying bug where the MediaMessageView will fill the screen if the
// 'audioPlayPauseButton' is added anywhere within the view hierarchy causing issues with
// the min scale on 'image' and 'animatedImage' file types (assume it's actually any UIButton)
addSubview(audioPlayPauseButton)
var lastView: UIView? case .url:
for subview in subviews { imageView.isHidden = false
imageView.alpha = 0 // Not 'isHidden' because we want it to take up space in the UIStackView
loadingView.isHidden = false
stackView.addSubview(subview) if let linkPreviewUrl: String = linkPreviewInfo?.url {
subview.autoVCenterInSuperview() // Don't want to change the axis until we have a URL to start loading, otherwise the
// error message will be broken
stackView.axis = .horizontal
if lastView == nil { loadLinkPreview(linkPreviewURL: linkPreviewUrl)
subview.autoPinEdge(toSuperviewEdge: .left)
} else {
subview.autoPinEdge(.left, to: .right, of: lastView!, withOffset: 10)
} }
lastView = subview default: imageView.isHidden = false
}
} }
lastView?.autoPinEdge(toSuperviewEdge: .right) private func setupLayout() {
// Sizing calculations
let clampedRatio: CGFloat = {
switch attachment.fileType {
case .url: return 1
return stackView case .image, .video, .audio, .unknown:
} let imageSize: CGSize = (imageView.image?.size ?? CGSize(width: 1, height: 1))
let aspectRatio: CGFloat = (imageSize.width / imageSize.height)
return CGFloatClamp(aspectRatio, 0.05, 95.0)
// private func stackSpacing() -> CGFloat { case .animatedImage:
// switch mode { let imageSize: CGSize = (animatedImageView.image?.size ?? CGSize(width: 1, height: 1))
// case .large, .attachmentApproval: let aspectRatio: CGFloat = (imageSize.width / imageSize.height)
// return CGFloat(10)
// case .small:
// return CGFloat(5)
// }
// }
private func createAudioPreview() { return CGFloatClamp(aspectRatio, 0.05, 95.0)
guard let dataUrl = attachment.dataUrl else {
createGenericPreview() default: return 0
return
} }
}()
audioPlayer = OWSAudioPlayer(mediaUrl: dataUrl, audioBehavior: .playback, delegate: self) let maybeImageSize: CGFloat? = {
switch attachment.fileType {
case .image, .video:
if validImage != nil { return nil }
imageView.image = UIImage(named: "FileLarge")?.withRenderingMode(.alwaysTemplate) // If we don't have a valid image then use the 'generic' case
imageView.tintColor = Colors.text break
fileTypeImageView.image = UIImage(named: "table_ic_notification_sound")?
.withRenderingMode(.alwaysTemplate)
fileTypeImageView.tintColor = Colors.text
setAudioIconToPlay()
self.addSubview(stackView) case .animatedImage:
self.addSubview(audioPlayPauseButton) if validAnimatedImage != nil { return nil }
stackView.addArrangedSubview(imageView) // If we don't have a valid image then use the 'generic' case
stackView.addArrangedSubview(UIView.vSpacer(0)) break
stackView.addArrangedSubview(titleLabel)
stackView.addArrangedSubview(fileSizeLabel)
imageView.addSubview(fileTypeImageView) case .url:
switch mode {
case .large: return 120
case .attachmentApproval, .small: return 80
}
// Use the 'generic' case for these
case .audio, .unknown: break
let imageSize: CGFloat = { default: return nil
}
// Generic file size
switch mode { switch mode {
case .large: return 200 case .large: return 200
case .attachmentApproval: return 150 case .attachmentApproval: return 120
case .small: return 80 case .small: return 80
} }
}() }()
let imageSize: CGFloat = (maybeImageSize ?? 0)
let audioButtonSize: CGFloat = (imageSize / 2.5) let audioButtonSize: CGFloat = (imageSize / 2.5)
audioPlayPauseButton.layer.cornerRadius = (audioButtonSize / 2) audioPlayPauseButton.layer.cornerRadius = (audioButtonSize / 2)
// Actual layout
NSLayoutConstraint.activate([ NSLayoutConstraint.activate([
stackView.centerXAnchor.constraint(equalTo: centerXAnchor),
stackView.centerYAnchor.constraint(equalTo: centerYAnchor), stackView.centerYAnchor.constraint(equalTo: centerYAnchor),
stackView.widthAnchor.constraint(equalTo: widthAnchor),
stackView.heightAnchor.constraint(lessThanOrEqualTo: heightAnchor), stackView.heightAnchor.constraint(lessThanOrEqualTo: heightAnchor),
imageView.widthAnchor.constraint(equalToConstant: imageSize), (maybeImageSize != nil ?
imageView.heightAnchor.constraint(equalToConstant: imageSize), stackView.widthAnchor.constraint(
titleLabel.widthAnchor.constraint(equalTo: stackView.widthAnchor, constant: -(32 * 2)), equalTo: widthAnchor,
fileSizeLabel.widthAnchor.constraint(equalTo: stackView.widthAnchor, constant: -(32 * 2)), constant: (attachment.isUrl ? -(32 * 2) : 0) // Inset stackView for urls
) :
stackView.widthAnchor.constraint(lessThanOrEqualTo: widthAnchor)
),
imageView.widthAnchor.constraint(
equalTo: imageView.heightAnchor,
multiplier: clampedRatio
),
animatedImageView.widthAnchor.constraint(
equalTo: animatedImageView.heightAnchor,
multiplier: clampedRatio
),
// Note: AnimatedImage, Image and Video types should allow zooming so be lessThanOrEqualTo
// the view size but some other types should have specific sizes
animatedImageView.widthAnchor.constraint(lessThanOrEqualTo: widthAnchor),
animatedImageView.heightAnchor.constraint(lessThanOrEqualTo: heightAnchor),
(maybeImageSize != nil ?
imageView.widthAnchor.constraint(equalToConstant: imageSize) :
imageView.widthAnchor.constraint(lessThanOrEqualTo: widthAnchor)
),
(maybeImageSize != nil ?
imageView.heightAnchor.constraint(equalToConstant: imageSize) :
imageView.heightAnchor.constraint(lessThanOrEqualTo: heightAnchor)
),
fileTypeImageView.centerXAnchor.constraint(equalTo: imageView.centerXAnchor), fileTypeImageView.centerXAnchor.constraint(equalTo: imageView.centerXAnchor),
fileTypeImageView.centerYAnchor.constraint( fileTypeImageView.centerYAnchor.constraint(
@ -339,228 +490,41 @@ public class MediaMessageView: UIView, OWSAudioPlayerDelegate {
), ),
fileTypeImageView.widthAnchor.constraint(equalTo: imageView.widthAnchor, multiplier: 0.5), fileTypeImageView.widthAnchor.constraint(equalTo: imageView.widthAnchor, multiplier: 0.5),
audioPlayPauseButton.centerXAnchor.constraint(equalTo: imageView.centerXAnchor), videoPlayButton.centerXAnchor.constraint(equalTo: centerXAnchor),
audioPlayPauseButton.centerYAnchor.constraint(equalTo: imageView.centerYAnchor), videoPlayButton.centerYAnchor.constraint(equalTo: centerYAnchor),
audioPlayPauseButton.widthAnchor.constraint(equalToConstant: audioButtonSize),
audioPlayPauseButton.heightAnchor.constraint(equalToConstant: audioButtonSize)
])
}
private func createAnimatedPreview() {
guard attachment.isValidImage else {
createGenericPreview()
return
}
guard let dataUrl = attachment.dataUrl else {
createGenericPreview()
return
}
guard let image = YYImage(contentsOfFile: dataUrl.path) else {
createGenericPreview()
return
}
guard image.size.width > 0 && image.size.height > 0 else {
createGenericPreview()
return
}
animatedImageView.image = image
let aspectRatio: CGFloat = (image.size.width / image.size.height)
let clampedRatio: CGFloat = CGFloatClamp(aspectRatio, 0.05, 95.0)
addSubview(animatedImageView)
// addSubviewWithScaleAspectFitLayout(view: animatedImageView, aspectRatio: aspectRatio)
contentView = animatedImageView
NSLayoutConstraint.activate([ loadingView.centerXAnchor.constraint(equalTo: imageView.centerXAnchor),
animatedImageView.centerXAnchor.constraint(equalTo: centerXAnchor), loadingView.centerYAnchor.constraint(equalTo: imageView.centerYAnchor),
animatedImageView.centerYAnchor.constraint(equalTo: centerYAnchor), loadingView.widthAnchor.constraint(equalToConstant: ceil(imageSize / 3)),
animatedImageView.widthAnchor.constraint( loadingView.heightAnchor.constraint(equalToConstant: ceil(imageSize / 3))
equalTo: animatedImageView.heightAnchor,
multiplier: clampedRatio
),
animatedImageView.widthAnchor.constraint(lessThanOrEqualTo: widthAnchor),
animatedImageView.heightAnchor.constraint(lessThanOrEqualTo: heightAnchor)
]) ])
}
// private func addSubviewWithScaleAspectFitLayout(view: UIView, aspectRatio: CGFloat) {
// self.addSubview(view)
// // This emulates the behavior of contentMode = .scaleAspectFit using
// // iOS auto layout constraints.
// //
// // This allows ConversationInputToolbar to place the "cancel" button
// // in the upper-right hand corner of the preview content.
// view.autoCenterInSuperview()
// view.autoPin(toAspectRatio: aspectRatio)
// view.autoMatch(.width, to: .width, of: self, withMultiplier: 1.0, relation: .lessThanOrEqual)
// view.autoMatch(.height, to: .height, of: self, withMultiplier: 1.0, relation: .lessThanOrEqual)
// }
private func createImagePreview() {
guard attachment.isValidImage else {
createGenericPreview()
return
}
guard let image = attachment.image() else {
createGenericPreview()
return
}
guard image.size.width > 0 && image.size.height > 0 else {
createGenericPreview()
return
}
imageView.image = image
// imageView.layer.minificationFilter = .trilinear
// imageView.layer.magnificationFilter = .trilinear
let aspectRatio = image.size.width / image.size.height
let clampedRatio: CGFloat = CGFloatClamp(aspectRatio, 0.05, 95.0)
// addSubviewWithScaleAspectFitLayout(view: imageView, aspectRatio: aspectRatio)
contentView = imageView
// No inset for the text for URLs but there is for all other layouts
if (attachment.fileType != .url) {
NSLayoutConstraint.activate([ NSLayoutConstraint.activate([
imageView.centerXAnchor.constraint(equalTo: centerXAnchor), titleLabel.widthAnchor.constraint(equalTo: stackView.widthAnchor, constant: -(32 * 2)),
imageView.centerYAnchor.constraint(equalTo: centerYAnchor), fileSizeLabel.widthAnchor.constraint(equalTo: stackView.widthAnchor, constant: -(32 * 2))
imageView.widthAnchor.constraint(
equalTo: imageView.heightAnchor,
multiplier: clampedRatio
),
imageView.widthAnchor.constraint(lessThanOrEqualTo: widthAnchor),
imageView.heightAnchor.constraint(lessThanOrEqualTo: heightAnchor)
]) ])
} }
private func createVideoPreview() { // Note: There is an annoying bug where the MediaMessageView will fill the screen if the
guard attachment.isValidVideo else { // 'audioPlayPauseButton' is added anywhere within the view hierarchy causing issues with
createGenericPreview() // the min scale on 'image' and 'animatedImage' file types (assume it's actually any UIButton)
return if attachment.fileType == .audio {
}
guard let image = attachment.videoPreview() else {
createGenericPreview()
return
}
guard image.size.width > 0 && image.size.height > 0 else {
createGenericPreview()
return
}
imageView.image = image
self.addSubview(imageView)
let aspectRatio = image.size.width / image.size.height
let clampedRatio: CGFloat = CGFloatClamp(aspectRatio, 0.05, 95.0)
contentView = imageView
// Attachment approval provides it's own play button to keep it
// at the proper zoom scale.
if mode != .attachmentApproval {
self.addSubview(videoPlayButton)
}
NSLayoutConstraint.activate([ NSLayoutConstraint.activate([
imageView.centerXAnchor.constraint(equalTo: centerXAnchor), audioPlayPauseButton.centerXAnchor.constraint(equalTo: imageView.centerXAnchor),
imageView.centerYAnchor.constraint(equalTo: centerYAnchor), audioPlayPauseButton.centerYAnchor.constraint(equalTo: imageView.centerYAnchor),
imageView.widthAnchor.constraint( audioPlayPauseButton.widthAnchor.constraint(equalToConstant: audioButtonSize),
equalTo: imageView.heightAnchor, audioPlayPauseButton.heightAnchor.constraint(equalToConstant: audioButtonSize),
multiplier: clampedRatio
),
imageView.widthAnchor.constraint(lessThanOrEqualTo: widthAnchor),
imageView.heightAnchor.constraint(lessThanOrEqualTo: heightAnchor)
])
// Attachment approval provides it's own play button to keep it
// at the proper zoom scale.
if mode != .attachmentApproval {
self.addSubview(videoPlayButton)
NSLayoutConstraint.activate([
videoPlayButton.centerXAnchor.constraint(equalTo: centerXAnchor),
videoPlayButton.centerYAnchor.constraint(equalTo: centerYAnchor),
imageView.widthAnchor.constraint(equalToConstant: 72),
imageView.heightAnchor.constraint(equalToConstant: 72)
]) ])
} }
} }
private func createUrlPreview() { // MARK: - Link Loading
// If link previews aren't enabled then use a fallback state
guard let linkPreviewURL: String = OWSLinkPreview.previewURL(forRawBodyText: attachment.text()) else {
titleLabel.text = "vc_share_link_previews_disabled_title".localized()
titleLabel.isHidden = false
fileSizeLabel.text = "vc_share_link_previews_disabled_explanation".localized() private func loadLinkPreview(linkPreviewURL: String) {
fileSizeLabel.textColor = Colors.text
fileSizeLabel.numberOfLines = 0
self.addSubview(stackView)
stackView.addArrangedSubview(titleLabel)
stackView.addArrangedSubview(UIView.vSpacer(10))
stackView.addArrangedSubview(fileSizeLabel)
NSLayoutConstraint.activate([
stackView.centerXAnchor.constraint(equalTo: centerXAnchor),
stackView.centerYAnchor.constraint(equalTo: centerYAnchor),
stackView.widthAnchor.constraint(equalTo: widthAnchor, constant: -(32 * 2)),
stackView.heightAnchor.constraint(lessThanOrEqualTo: heightAnchor)
])
return
}
linkPreviewInfo = (url: linkPreviewURL, draft: nil)
stackView.axis = .horizontal
stackView.distribution = .fill
imageView.clipsToBounds = true
imageView.image = UIImage(named: "Link")?.withTint(Colors.text)
imageView.alpha = 0 // Not 'isHidden' because we want it to take up space in the UIStackView
imageView.contentMode = .center
imageView.backgroundColor = (isDarkMode ? .black : UIColor.black.withAlphaComponent(0.06))
imageView.layer.cornerRadius = 8
loadingView.isHidden = false
loadingView.startAnimating() loadingView.startAnimating()
titleLabel.font = .boldSystemFont(ofSize: Values.smallFontSize)
titleLabel.text = linkPreviewURL
titleLabel.textAlignment = .left
titleLabel.numberOfLines = 2
titleLabel.isHidden = false
self.addSubview(stackView)
self.addSubview(loadingView)
stackView.addArrangedSubview(imageView)
stackView.addArrangedSubview(UIView.vhSpacer(10, 0))
stackView.addArrangedSubview(titleLabel)
let imageSize: CGFloat = {
switch mode {
case .large: return 120
case .attachmentApproval, .small: return 80
}
}()
NSLayoutConstraint.activate([
stackView.centerXAnchor.constraint(equalTo: centerXAnchor),
stackView.centerYAnchor.constraint(equalTo: centerYAnchor),
stackView.widthAnchor.constraint(equalTo: widthAnchor, constant: -(32 * 2)),
stackView.heightAnchor.constraint(lessThanOrEqualTo: heightAnchor),
imageView.widthAnchor.constraint(equalToConstant: imageSize),
imageView.heightAnchor.constraint(equalToConstant: imageSize),
loadingView.centerXAnchor.constraint(equalTo: imageView.centerXAnchor),
loadingView.centerYAnchor.constraint(equalTo: imageView.centerYAnchor),
loadingView.widthAnchor.constraint(equalToConstant: ceil(imageSize / 3)),
loadingView.heightAnchor.constraint(equalToConstant: ceil(imageSize / 3))
])
// Build the link preview
OWSLinkPreview.tryToBuildPreviewInfo(previewUrl: linkPreviewURL) OWSLinkPreview.tryToBuildPreviewInfo(previewUrl: linkPreviewURL)
.done { [weak self] draft in .done { [weak self] draft in
// TODO: Look at refactoring this behaviour to consolidate attachment mutations // TODO: Look at refactoring this behaviour to consolidate attachment mutations
@ -580,8 +544,9 @@ public class MediaMessageView: UIView, OWSAudioPlayerDelegate {
} }
.catch { [weak self] _ in .catch { [weak self] _ in
self?.titleLabel.attributedText = NSMutableAttributedString(string: linkPreviewURL) self?.titleLabel.attributedText = NSMutableAttributedString(string: linkPreviewURL)
.rtlSafeAppend("\n")
.rtlSafeAppend( .rtlSafeAppend(
"\n\("vc_share_link_previews_error".localized())", "vc_share_link_previews_error".localized(),
attributes: [ attributes: [
NSAttributedString.Key.font: UIFont.ows_regularFont( NSAttributedString.Key.font: UIFont.ows_regularFont(
withSize: Values.verySmallFontSize withSize: Values.verySmallFontSize
@ -597,51 +562,6 @@ public class MediaMessageView: UIView, OWSAudioPlayerDelegate {
.retainUntilComplete() .retainUntilComplete()
} }
private func createGenericPreview() {
imageView.image = UIImage(named: "FileLarge")
self.addSubview(stackView)
stackView.addArrangedSubview(imageView)
stackView.addArrangedSubview(UIView.vSpacer(5))
stackView.addArrangedSubview(titleLabel)
stackView.addArrangedSubview(fileSizeLabel)
imageView.addSubview(fileTypeImageView)
let imageSize: CGFloat = {
switch mode {
case .large: return 200
case .attachmentApproval: return 150
case .small: return 80
}
}()
NSLayoutConstraint.activate([
stackView.centerYAnchor.constraint(equalTo: centerYAnchor),
stackView.widthAnchor.constraint(equalTo: widthAnchor),
stackView.heightAnchor.constraint(lessThanOrEqualTo: heightAnchor),
imageView.widthAnchor.constraint(equalToConstant: imageSize),
imageView.heightAnchor.constraint(equalToConstant: imageSize),
titleLabel.widthAnchor.constraint(equalTo: stackView.widthAnchor, constant: -(32 * 2)),
fileSizeLabel.widthAnchor.constraint(equalTo: stackView.widthAnchor, constant: -(32 * 2)),
fileTypeImageView.centerXAnchor.constraint(equalTo: imageView.centerXAnchor),
fileTypeImageView.centerYAnchor.constraint(
equalTo: imageView.centerYAnchor,
constant: 25
),
fileTypeImageView.widthAnchor.constraint(
equalTo: fileTypeImageView.heightAnchor,
multiplier: ((fileTypeImageView.image?.size.width ?? 1) / (fileTypeImageView.image?.size.height ?? 1))
),
fileTypeImageView.widthAnchor.constraint(
equalTo: imageView.widthAnchor, constant: -75
)
])
}
// MARK: - Event Handlers // MARK: - Event Handlers
@objc func audioPlayPauseButtonPressed(sender: UIButton) { @objc func audioPlayPauseButtonPressed(sender: UIButton) {

Loading…
Cancel
Save