|
|
@ -44,8 +44,7 @@ public class MediaMessageView: UIView, OWSAudioPlayerDelegate {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private lazy var validImage: UIImage? = {
|
|
|
|
private lazy var validImage: UIImage? = {
|
|
|
|
switch attachment.fileType {
|
|
|
|
if attachment.isImage {
|
|
|
|
case .image:
|
|
|
|
|
|
|
|
guard
|
|
|
|
guard
|
|
|
|
attachment.isValidImage,
|
|
|
|
attachment.isValidImage,
|
|
|
|
let image: UIImage = attachment.image(),
|
|
|
|
let image: UIImage = attachment.image(),
|
|
|
@ -56,8 +55,8 @@ public class MediaMessageView: UIView, OWSAudioPlayerDelegate {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return image
|
|
|
|
return image
|
|
|
|
|
|
|
|
}
|
|
|
|
case .video:
|
|
|
|
else if attachment.isVideo {
|
|
|
|
guard
|
|
|
|
guard
|
|
|
|
attachment.isValidVideo,
|
|
|
|
attachment.isValidVideo,
|
|
|
|
let image: UIImage = attachment.videoPreview(),
|
|
|
|
let image: UIImage = attachment.videoPreview(),
|
|
|
@ -68,13 +67,13 @@ public class MediaMessageView: UIView, OWSAudioPlayerDelegate {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return image
|
|
|
|
return image
|
|
|
|
|
|
|
|
|
|
|
|
default: return nil
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}()
|
|
|
|
}()
|
|
|
|
private lazy var validAnimatedImage: YYImage? = {
|
|
|
|
private lazy var validAnimatedImage: YYImage? = {
|
|
|
|
guard
|
|
|
|
guard
|
|
|
|
attachment.fileType == .animatedImage,
|
|
|
|
attachment.isAnimatedImage,
|
|
|
|
attachment.isValidImage,
|
|
|
|
attachment.isValidImage,
|
|
|
|
let dataUrl: URL = attachment.dataUrl,
|
|
|
|
let dataUrl: URL = attachment.dataUrl,
|
|
|
|
let image: YYImage = YYImage(contentsOfFile: dataUrl.path),
|
|
|
|
let image: YYImage = YYImage(contentsOfFile: dataUrl.path),
|
|
|
@ -153,22 +152,19 @@ public class MediaMessageView: UIView, OWSAudioPlayerDelegate {
|
|
|
|
view.isHidden = true
|
|
|
|
view.isHidden = true
|
|
|
|
|
|
|
|
|
|
|
|
// Override the image to the correct one
|
|
|
|
// Override the image to the correct one
|
|
|
|
switch attachment.fileType {
|
|
|
|
if attachment.isImage || attachment.isVideo {
|
|
|
|
case .image, .video:
|
|
|
|
|
|
|
|
if let validImage: UIImage = validImage {
|
|
|
|
if let validImage: UIImage = validImage {
|
|
|
|
view.layer.minificationFilter = .trilinear
|
|
|
|
view.layer.minificationFilter = .trilinear
|
|
|
|
view.layer.magnificationFilter = .trilinear
|
|
|
|
view.layer.magnificationFilter = .trilinear
|
|
|
|
view.image = validImage
|
|
|
|
view.image = validImage
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
case .url:
|
|
|
|
else if attachment.isUrl {
|
|
|
|
view.clipsToBounds = true
|
|
|
|
view.clipsToBounds = true
|
|
|
|
view.image = UIImage(named: "Link")?.withTint(Colors.text)
|
|
|
|
view.image = UIImage(named: "Link")?.withTint(Colors.text)
|
|
|
|
view.contentMode = .center
|
|
|
|
view.contentMode = .center
|
|
|
|
view.backgroundColor = (isDarkMode ? .black : UIColor.black.withAlphaComponent(0.06))
|
|
|
|
view.backgroundColor = (isDarkMode ? .black : UIColor.black.withAlphaComponent(0.06))
|
|
|
|
view.layer.cornerRadius = 8
|
|
|
|
view.layer.cornerRadius = 8
|
|
|
|
|
|
|
|
|
|
|
|
default: break
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return view
|
|
|
|
return view
|
|
|
@ -225,7 +221,7 @@ public class MediaMessageView: UIView, OWSAudioPlayerDelegate {
|
|
|
|
let stackView: UIStackView = UIStackView()
|
|
|
|
let stackView: UIStackView = UIStackView()
|
|
|
|
stackView.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
stackView.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
stackView.axis = .vertical
|
|
|
|
stackView.axis = .vertical
|
|
|
|
stackView.alignment = (attachment.fileType == .url ? .leading : .center)
|
|
|
|
stackView.alignment = (attachment.isUrl ? .leading : .center)
|
|
|
|
stackView.distribution = .fill
|
|
|
|
stackView.distribution = .fill
|
|
|
|
|
|
|
|
|
|
|
|
switch mode {
|
|
|
|
switch mode {
|
|
|
@ -257,23 +253,21 @@ public class MediaMessageView: UIView, OWSAudioPlayerDelegate {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Content
|
|
|
|
// Content
|
|
|
|
switch attachment.fileType {
|
|
|
|
if attachment.isUrl {
|
|
|
|
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
|
|
|
|
// If we have no link preview info at this point then assume link previews are disabled
|
|
|
|
guard let linkPreviewURL: String = linkPreviewInfo?.url else {
|
|
|
|
if let linkPreviewURL: String = linkPreviewInfo?.url {
|
|
|
|
label.text = "vc_share_link_previews_disabled_title".localized()
|
|
|
|
|
|
|
|
break
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
label.font = .boldSystemFont(ofSize: Values.smallFontSize)
|
|
|
|
label.font = .boldSystemFont(ofSize: Values.smallFontSize)
|
|
|
|
label.text = linkPreviewURL
|
|
|
|
label.text = linkPreviewURL
|
|
|
|
label.textAlignment = .left
|
|
|
|
label.textAlignment = .left
|
|
|
|
label.lineBreakMode = .byTruncatingTail
|
|
|
|
label.lineBreakMode = .byTruncatingTail
|
|
|
|
label.numberOfLines = 2
|
|
|
|
label.numberOfLines = 2
|
|
|
|
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
else {
|
|
|
|
|
|
|
|
label.text = "vc_share_link_previews_disabled_title".localized()
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// Title for everything except these types
|
|
|
|
|
|
|
|
else if !attachment.isImage && !attachment.isAnimatedImage && !attachment.isVideo {
|
|
|
|
if let fileName: String = attachment.sourceFilename?.trimmingCharacters(in: .whitespacesAndNewlines), fileName.count > 0 {
|
|
|
|
if let fileName: String = attachment.sourceFilename?.trimmingCharacters(in: .whitespacesAndNewlines), fileName.count > 0 {
|
|
|
|
label.text = fileName
|
|
|
|
label.text = fileName
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -314,27 +308,25 @@ public class MediaMessageView: UIView, OWSAudioPlayerDelegate {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Content
|
|
|
|
// Content
|
|
|
|
switch attachment.fileType {
|
|
|
|
if attachment.isUrl {
|
|
|
|
case .image, .animatedImage, .video: break // No size for these
|
|
|
|
// We only load Link Previews for HTTPS urls so append an explanation for not
|
|
|
|
|
|
|
|
if let linkPreviewURL: String = linkPreviewInfo?.url {
|
|
|
|
case .url:
|
|
|
|
if let targetUrl: URL = URL(string: linkPreviewURL), targetUrl.scheme?.lowercased() != "https" {
|
|
|
|
|
|
|
|
label.font = UIFont.ows_regularFont(withSize: Values.verySmallFontSize)
|
|
|
|
|
|
|
|
label.text = "vc_share_link_previews_unsecure".localized()
|
|
|
|
|
|
|
|
label.textColor = (mode == .attachmentApproval ? Colors.pinIcon : Colors.accent)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
// If we have no link preview info at this point then assume link previews are disabled
|
|
|
|
// If we have no link preview info at this point then assume link previews are disabled
|
|
|
|
guard let linkPreviewURL: String = linkPreviewInfo?.url else {
|
|
|
|
else {
|
|
|
|
label.text = "vc_share_link_previews_disabled_explanation".localized()
|
|
|
|
label.text = "vc_share_link_previews_disabled_explanation".localized()
|
|
|
|
label.textColor = Colors.text
|
|
|
|
label.textColor = Colors.text
|
|
|
|
label.textAlignment = .center
|
|
|
|
label.textAlignment = .center
|
|
|
|
label.numberOfLines = 0
|
|
|
|
label.numberOfLines = 0
|
|
|
|
break
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// We only load Link Previews for HTTPS urls so append an explanation for not
|
|
|
|
|
|
|
|
if let targetUrl: URL = URL(string: linkPreviewURL), targetUrl.scheme?.lowercased() != "https" {
|
|
|
|
|
|
|
|
label.font = UIFont.ows_regularFont(withSize: Values.verySmallFontSize)
|
|
|
|
|
|
|
|
label.text = "vc_share_link_previews_unsecure".localized()
|
|
|
|
|
|
|
|
label.textColor = (mode == .attachmentApproval ? Colors.pinIcon : Colors.accent)
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Subtitle for everything else except these types
|
|
|
|
default:
|
|
|
|
else if !attachment.isImage && !attachment.isAnimatedImage && !attachment.isVideo {
|
|
|
|
// Format string for file size label in call interstitial view.
|
|
|
|
// Format string for file size label in call interstitial view.
|
|
|
|
// Embeds: {{file size as 'N mb' or 'N kb'}}.
|
|
|
|
// Embeds: {{file size as 'N mb' or 'N kb'}}.
|
|
|
|
let fileSize: UInt = attachment.dataLength
|
|
|
|
let fileSize: UInt = attachment.dataLength
|
|
|
@ -352,7 +344,7 @@ public class MediaMessageView: UIView, OWSAudioPlayerDelegate {
|
|
|
|
|
|
|
|
|
|
|
|
private func setupViews() {
|
|
|
|
private func setupViews() {
|
|
|
|
// Plain text will just be put in the 'message' input so do nothing
|
|
|
|
// Plain text will just be put in the 'message' input so do nothing
|
|
|
|
guard attachment.fileType != .text && attachment.fileType != .oversizeText else { return }
|
|
|
|
guard !attachment.isText && !attachment.isOversizeText else { return }
|
|
|
|
|
|
|
|
|
|
|
|
// Setup the view hierarchy
|
|
|
|
// Setup the view hierarchy
|
|
|
|
addSubview(stackView)
|
|
|
|
addSubview(stackView)
|
|
|
@ -370,17 +362,19 @@ public class MediaMessageView: UIView, OWSAudioPlayerDelegate {
|
|
|
|
imageView.addSubview(fileTypeImageView)
|
|
|
|
imageView.addSubview(fileTypeImageView)
|
|
|
|
|
|
|
|
|
|
|
|
// Type-specific configurations
|
|
|
|
// Type-specific configurations
|
|
|
|
switch attachment.fileType {
|
|
|
|
if attachment.isAnimatedImage {
|
|
|
|
case .animatedImage: animatedImageView.isHidden = false
|
|
|
|
animatedImageView.isHidden = false
|
|
|
|
case .image: imageView.isHidden = false
|
|
|
|
}
|
|
|
|
|
|
|
|
else if attachment.isImage {
|
|
|
|
case .video:
|
|
|
|
imageView.isHidden = false
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
else if attachment.isVideo {
|
|
|
|
// Note: The 'attachmentApproval' mode provides it's own play button to keep
|
|
|
|
// Note: The 'attachmentApproval' mode provides it's own play button to keep
|
|
|
|
// it at the proper scale when zooming
|
|
|
|
// it at the proper scale when zooming
|
|
|
|
imageView.isHidden = false
|
|
|
|
imageView.isHidden = false
|
|
|
|
videoPlayButton.isHidden = (mode == .attachmentApproval)
|
|
|
|
videoPlayButton.isHidden = (mode == .attachmentApproval)
|
|
|
|
|
|
|
|
}
|
|
|
|
case .audio:
|
|
|
|
else if attachment.isAudio {
|
|
|
|
// Hide the 'audioPlayPauseButton' if the 'audioPlayer' failed to get created
|
|
|
|
// Hide the 'audioPlayPauseButton' if the 'audioPlayer' failed to get created
|
|
|
|
imageView.isHidden = false
|
|
|
|
imageView.isHidden = false
|
|
|
|
audioPlayPauseButton.isHidden = (audioPlayer == nil)
|
|
|
|
audioPlayPauseButton.isHidden = (audioPlayer == nil)
|
|
|
@ -396,8 +390,8 @@ public class MediaMessageView: UIView, OWSAudioPlayerDelegate {
|
|
|
|
// 'audioPlayPauseButton' is added anywhere within the view hierarchy causing issues with
|
|
|
|
// '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)
|
|
|
|
// the min scale on 'image' and 'animatedImage' file types (assume it's actually any UIButton)
|
|
|
|
addSubview(audioPlayPauseButton)
|
|
|
|
addSubview(audioPlayPauseButton)
|
|
|
|
|
|
|
|
}
|
|
|
|
case .url:
|
|
|
|
else if attachment.isUrl {
|
|
|
|
imageView.isHidden = false
|
|
|
|
imageView.isHidden = false
|
|
|
|
imageView.alpha = 0 // Not 'isHidden' because we want it to take up space in the UIStackView
|
|
|
|
imageView.alpha = 0 // Not 'isHidden' because we want it to take up space in the UIStackView
|
|
|
|
loadingView.isHidden = false
|
|
|
|
loadingView.isHidden = false
|
|
|
@ -409,56 +403,49 @@ public class MediaMessageView: UIView, OWSAudioPlayerDelegate {
|
|
|
|
|
|
|
|
|
|
|
|
loadLinkPreview(linkPreviewURL: linkPreviewUrl)
|
|
|
|
loadLinkPreview(linkPreviewURL: linkPreviewUrl)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
default: imageView.isHidden = false
|
|
|
|
else {
|
|
|
|
|
|
|
|
imageView.isHidden = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private func setupLayout() {
|
|
|
|
private func setupLayout() {
|
|
|
|
// Plain text will just be put in the 'message' input so do nothing
|
|
|
|
// Plain text will just be put in the 'message' input so do nothing
|
|
|
|
guard attachment.fileType != .text && attachment.fileType != .oversizeText else { return }
|
|
|
|
guard !attachment.isText && !attachment.isOversizeText else { return }
|
|
|
|
|
|
|
|
|
|
|
|
// Sizing calculations
|
|
|
|
// Sizing calculations
|
|
|
|
let clampedRatio: CGFloat = {
|
|
|
|
let clampedRatio: CGFloat = {
|
|
|
|
switch attachment.fileType {
|
|
|
|
if attachment.isUrl {
|
|
|
|
case .url: return 1
|
|
|
|
return 1
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
case .image, .video, .audio, .unknown:
|
|
|
|
if attachment.isAnimatedImage {
|
|
|
|
let imageSize: CGSize = (imageView.image?.size ?? CGSize(width: 1, height: 1))
|
|
|
|
let imageSize: CGSize = (animatedImageView.image?.size ?? CGSize(width: 1, height: 1))
|
|
|
|
let aspectRatio: CGFloat = (imageSize.width / imageSize.height)
|
|
|
|
let aspectRatio: CGFloat = (imageSize.width / imageSize.height)
|
|
|
|
|
|
|
|
|
|
|
|
return CGFloatClamp(aspectRatio, 0.05, 95.0)
|
|
|
|
return CGFloatClamp(aspectRatio, 0.05, 95.0)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
case .animatedImage:
|
|
|
|
// All other types should maintain the ratio of the image in the 'imageView'
|
|
|
|
let imageSize: CGSize = (animatedImageView.image?.size ?? CGSize(width: 1, height: 1))
|
|
|
|
let imageSize: CGSize = (imageView.image?.size ?? CGSize(width: 1, height: 1))
|
|
|
|
let aspectRatio: CGFloat = (imageSize.width / imageSize.height)
|
|
|
|
let aspectRatio: CGFloat = (imageSize.width / imageSize.height)
|
|
|
|
|
|
|
|
|
|
|
|
return CGFloatClamp(aspectRatio, 0.05, 95.0)
|
|
|
|
return CGFloatClamp(aspectRatio, 0.05, 95.0)
|
|
|
|
|
|
|
|
|
|
|
|
default: return 0
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}()
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
|
|
let maybeImageSize: CGFloat? = {
|
|
|
|
let maybeImageSize: CGFloat? = {
|
|
|
|
switch attachment.fileType {
|
|
|
|
if attachment.isImage || attachment.isVideo {
|
|
|
|
case .image, .video:
|
|
|
|
|
|
|
|
if validImage != nil { return nil }
|
|
|
|
if validImage != nil { return nil }
|
|
|
|
|
|
|
|
|
|
|
|
// If we don't have a valid image then use the 'generic' case
|
|
|
|
// If we don't have a valid image then use the 'generic' case
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
else if attachment.isAnimatedImage {
|
|
|
|
case .animatedImage:
|
|
|
|
|
|
|
|
if validAnimatedImage != nil { return nil }
|
|
|
|
if validAnimatedImage != nil { return nil }
|
|
|
|
|
|
|
|
|
|
|
|
// If we don't have a valid image then use the 'generic' case
|
|
|
|
// If we don't have a valid image then use the 'generic' case
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
else if attachment.isUrl {
|
|
|
|
case .url: return 80
|
|
|
|
return 80
|
|
|
|
|
|
|
|
|
|
|
|
// Use the 'generic' case for these
|
|
|
|
|
|
|
|
case .audio, .unknown: break
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
default: return nil
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Generic file size
|
|
|
|
// Generic file size
|
|
|
@ -531,7 +518,7 @@ public class MediaMessageView: UIView, OWSAudioPlayerDelegate {
|
|
|
|
])
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
|
|
// No inset for the text for URLs but there is for all other layouts
|
|
|
|
// No inset for the text for URLs but there is for all other layouts
|
|
|
|
if (attachment.fileType != .url) {
|
|
|
|
if !attachment.isUrl {
|
|
|
|
NSLayoutConstraint.activate([
|
|
|
|
NSLayoutConstraint.activate([
|
|
|
|
titleLabel.widthAnchor.constraint(equalTo: stackView.widthAnchor, constant: -(32 * 2)),
|
|
|
|
titleLabel.widthAnchor.constraint(equalTo: stackView.widthAnchor, constant: -(32 * 2)),
|
|
|
|
subtitleLabel.widthAnchor.constraint(equalTo: stackView.widthAnchor, constant: -(32 * 2))
|
|
|
|
subtitleLabel.widthAnchor.constraint(equalTo: stackView.widthAnchor, constant: -(32 * 2))
|
|
|
@ -541,7 +528,7 @@ public class MediaMessageView: UIView, OWSAudioPlayerDelegate {
|
|
|
|
// Note: There is an annoying bug where the MediaMessageView will fill the screen if the
|
|
|
|
// 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
|
|
|
|
// '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)
|
|
|
|
// the min scale on 'image' and 'animatedImage' file types (assume it's actually any UIButton)
|
|
|
|
if attachment.fileType == .audio {
|
|
|
|
if attachment.isAudio {
|
|
|
|
NSLayoutConstraint.activate([
|
|
|
|
NSLayoutConstraint.activate([
|
|
|
|
audioPlayPauseButton.centerXAnchor.constraint(equalTo: imageView.centerXAnchor),
|
|
|
|
audioPlayPauseButton.centerXAnchor.constraint(equalTo: imageView.centerXAnchor),
|
|
|
|
audioPlayPauseButton.centerYAnchor.constraint(equalTo: imageView.centerYAnchor),
|
|
|
|
audioPlayPauseButton.centerYAnchor.constraint(equalTo: imageView.centerYAnchor),
|
|
|
|