mirror of https://github.com/oxen-io/session-ios
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
689 lines
25 KiB
Swift
689 lines
25 KiB
Swift
//
|
|
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
|
|
//
|
|
|
|
import Foundation
|
|
import MediaPlayer
|
|
import YYImage
|
|
import NVActivityIndicatorView
|
|
import SessionUIKit
|
|
|
|
public class MediaMessageView: UIView, OWSAudioPlayerDelegate {
|
|
public enum Mode: UInt {
|
|
case large
|
|
case small
|
|
case attachmentApproval
|
|
}
|
|
|
|
// MARK: Properties
|
|
|
|
public let mode: Mode
|
|
public let attachment: SignalAttachment
|
|
|
|
public var audioPlayer: OWSAudioPlayer?
|
|
|
|
private var linkPreviewInfo: (url: String, draft: OWSLinkPreviewDraft?)?
|
|
|
|
|
|
public var playbackState = AudioPlaybackState.stopped {
|
|
didSet {
|
|
AssertIsOnMainThread()
|
|
|
|
ensureButtonState()
|
|
}
|
|
}
|
|
|
|
public var audioProgressSeconds: CGFloat = 0
|
|
public var audioDurationSeconds: CGFloat = 0
|
|
|
|
public var contentView: UIView?
|
|
|
|
|
|
|
|
// MARK: Initializers
|
|
|
|
@available(*, unavailable, message:"use other constructor instead.")
|
|
required public init?(coder aDecoder: NSCoder) {
|
|
notImplemented()
|
|
}
|
|
|
|
// Currently we only use one mode (AttachmentApproval), so we could simplify this class, but it's kind
|
|
// of nice that it's written in a flexible way in case we'd want to use it elsewhere again in the future.
|
|
public required init(attachment: SignalAttachment, mode: MediaMessageView.Mode) {
|
|
if attachment.hasError { owsFailDebug(attachment.error.debugDescription) }
|
|
|
|
self.attachment = attachment
|
|
self.mode = mode
|
|
|
|
super.init(frame: CGRect.zero)
|
|
|
|
createViews()
|
|
|
|
|
|
setupLayout()
|
|
}
|
|
|
|
deinit {
|
|
NotificationCenter.default.removeObserver(self)
|
|
}
|
|
|
|
// MARK: - UI
|
|
|
|
private lazy var stackView: UIStackView = {
|
|
let stackView: UIStackView = UIStackView()
|
|
stackView.translatesAutoresizingMaskIntoConstraints = false
|
|
stackView.axis = .vertical
|
|
stackView.alignment = .center
|
|
stackView.distribution = .equalSpacing
|
|
|
|
switch mode {
|
|
case .attachmentApproval: stackView.spacing = 2
|
|
case .large: stackView.spacing = 10
|
|
case .small: stackView.spacing = 5
|
|
}
|
|
|
|
return stackView
|
|
}()
|
|
|
|
private lazy var loadingView: NVActivityIndicatorView = {
|
|
let view: NVActivityIndicatorView = NVActivityIndicatorView(frame: CGRect.zero, type: .circleStrokeSpin, color: Colors.text, padding: nil)
|
|
view.translatesAutoresizingMaskIntoConstraints = false
|
|
view.isHidden = true
|
|
|
|
return view
|
|
}()
|
|
|
|
private lazy var imageView: UIImageView = {
|
|
let view: UIImageView = UIImageView()
|
|
view.translatesAutoresizingMaskIntoConstraints = false
|
|
view.contentMode = .scaleAspectFit
|
|
view.layer.minificationFilter = .trilinear
|
|
view.layer.magnificationFilter = .trilinear
|
|
|
|
return view
|
|
}()
|
|
|
|
private lazy var fileTypeImageView: UIImageView = {
|
|
let view: UIImageView = UIImageView()
|
|
view.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
return view
|
|
}()
|
|
|
|
private lazy var animatedImageView: YYAnimatedImageView = {
|
|
let view: YYAnimatedImageView = YYAnimatedImageView()
|
|
view.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
return view
|
|
}()
|
|
|
|
lazy var videoPlayButton: UIImageView = {
|
|
let imageView: UIImageView = UIImageView(image: UIImage(named: "CirclePlay"))
|
|
imageView.translatesAutoresizingMaskIntoConstraints = false
|
|
imageView.contentMode = .scaleAspectFit
|
|
|
|
return imageView
|
|
}()
|
|
|
|
/// Note: This uses different assets from the `videoPlayButton` and has a 'Pause' state
|
|
private lazy var audioPlayPauseButton: UIButton = {
|
|
let button: UIButton = UIButton()
|
|
button.translatesAutoresizingMaskIntoConstraints = false
|
|
button.clipsToBounds = true
|
|
button.setBackgroundImage(UIColor.white.toImage(), for: .normal)
|
|
button.setBackgroundImage(UIColor.white.darken(by: 0.2).toImage(), for: .highlighted)
|
|
button.addTarget(self, action: #selector(audioPlayPauseButtonPressed), for: .touchUpInside)
|
|
|
|
return button
|
|
}()
|
|
|
|
private lazy var titleLabel: UILabel = {
|
|
let label: UILabel = UILabel()
|
|
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)
|
|
|
|
switch mode {
|
|
case .attachmentApproval:
|
|
label.font = UIFont.ows_boldFont(withSize: ScaleFromIPhone5To7Plus(16, 22))
|
|
label.textColor = Colors.text
|
|
|
|
case .large:
|
|
label.font = UIFont.ows_regularFont(withSize: ScaleFromIPhone5To7Plus(18, 24))
|
|
label.textColor = Colors.accent
|
|
|
|
case .small:
|
|
label.font = UIFont.ows_regularFont(withSize: ScaleFromIPhone5To7Plus(14, 14))
|
|
label.textColor = Colors.accent
|
|
}
|
|
|
|
return label
|
|
}()
|
|
|
|
private lazy var fileSizeLabel: UILabel = {
|
|
let fileSize: UInt = attachment.dataLength
|
|
|
|
let label: UILabel = UILabel()
|
|
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
|
|
|
|
switch mode {
|
|
case .attachmentApproval:
|
|
label.font = UIFont.ows_regularFont(withSize: ScaleFromIPhone5To7Plus(12, 18))
|
|
label.textColor = Colors.pinIcon
|
|
|
|
case .large:
|
|
label.font = UIFont.ows_regularFont(withSize: ScaleFromIPhone5To7Plus(18, 24))
|
|
label.textColor = Colors.accent
|
|
|
|
case .small:
|
|
label.font = UIFont.ows_regularFont(withSize: ScaleFromIPhone5To7Plus(14, 14))
|
|
label.textColor = Colors.accent
|
|
}
|
|
|
|
return label
|
|
}()
|
|
|
|
// MARK: - Layout
|
|
|
|
private func createViews() {
|
|
if attachment.isAnimatedImage {
|
|
createAnimatedPreview()
|
|
} else if attachment.isImage {
|
|
createImagePreview()
|
|
} else if attachment.isVideo {
|
|
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() {
|
|
// Bottom inset
|
|
}
|
|
|
|
// TODO: Any reason for not just using UIStackView
|
|
private func wrapViewsInVerticalStack(subviews: [UIView]) -> UIView {
|
|
assert(subviews.count > 0)
|
|
|
|
let stackView = UIView()
|
|
|
|
var lastView: UIView?
|
|
for subview in subviews {
|
|
|
|
stackView.addSubview(subview)
|
|
subview.autoHCenterInSuperview()
|
|
|
|
if lastView == nil {
|
|
subview.autoPinEdge(toSuperviewEdge: .top)
|
|
} else {
|
|
subview.autoPinEdge(.top, to: .bottom, of: lastView!, withOffset: 10)
|
|
}
|
|
|
|
lastView = subview
|
|
}
|
|
|
|
lastView?.autoPinEdge(toSuperviewEdge: .bottom)
|
|
|
|
return stackView
|
|
}
|
|
|
|
private func wrapViewsInHorizontalStack(subviews: [UIView]) -> UIView {
|
|
assert(subviews.count > 0)
|
|
|
|
let stackView = UIView()
|
|
|
|
var lastView: UIView?
|
|
for subview in subviews {
|
|
|
|
stackView.addSubview(subview)
|
|
subview.autoVCenterInSuperview()
|
|
|
|
if lastView == nil {
|
|
subview.autoPinEdge(toSuperviewEdge: .left)
|
|
} else {
|
|
subview.autoPinEdge(.left, to: .right, of: lastView!, withOffset: 10)
|
|
}
|
|
|
|
lastView = subview
|
|
}
|
|
|
|
lastView?.autoPinEdge(toSuperviewEdge: .right)
|
|
|
|
return stackView
|
|
}
|
|
|
|
// private func stackSpacing() -> CGFloat {
|
|
// switch mode {
|
|
// case .large, .attachmentApproval:
|
|
// return CGFloat(10)
|
|
// case .small:
|
|
// return CGFloat(5)
|
|
// }
|
|
// }
|
|
|
|
private func createAudioPreview() {
|
|
guard let dataUrl = attachment.dataUrl else {
|
|
createGenericPreview()
|
|
return
|
|
}
|
|
|
|
audioPlayer = OWSAudioPlayer(mediaUrl: dataUrl, audioBehavior: .playback, delegate: self)
|
|
|
|
imageView.image = UIImage(named: "FileLarge")?.withRenderingMode(.alwaysTemplate)
|
|
imageView.tintColor = Colors.text
|
|
fileTypeImageView.image = UIImage(named: "table_ic_notification_sound")?
|
|
.withRenderingMode(.alwaysTemplate)
|
|
fileTypeImageView.tintColor = Colors.text
|
|
setAudioIconToPlay()
|
|
|
|
self.addSubview(stackView)
|
|
self.addSubview(audioPlayPauseButton)
|
|
|
|
stackView.addArrangedSubview(imageView)
|
|
stackView.addArrangedSubview(UIView.vSpacer(0))
|
|
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
|
|
}
|
|
}()
|
|
let audioButtonSize: CGFloat = (imageSize / 2.5)
|
|
audioPlayPauseButton.layer.cornerRadius = (audioButtonSize / 2)
|
|
|
|
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: ceil(imageSize * 0.15)
|
|
),
|
|
fileTypeImageView.widthAnchor.constraint(
|
|
equalTo: fileTypeImageView.heightAnchor,
|
|
multiplier: ((fileTypeImageView.image?.size.width ?? 1) / (fileTypeImageView.image?.size.height ?? 1))
|
|
),
|
|
fileTypeImageView.widthAnchor.constraint(equalTo: imageView.widthAnchor, multiplier: 0.5),
|
|
|
|
audioPlayPauseButton.centerXAnchor.constraint(equalTo: imageView.centerXAnchor),
|
|
audioPlayPauseButton.centerYAnchor.constraint(equalTo: imageView.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([
|
|
animatedImageView.centerXAnchor.constraint(equalTo: centerXAnchor),
|
|
animatedImageView.centerYAnchor.constraint(equalTo: centerYAnchor),
|
|
animatedImageView.widthAnchor.constraint(
|
|
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
|
|
|
|
NSLayoutConstraint.activate([
|
|
imageView.centerXAnchor.constraint(equalTo: centerXAnchor),
|
|
imageView.centerYAnchor.constraint(equalTo: centerYAnchor),
|
|
imageView.widthAnchor.constraint(
|
|
equalTo: imageView.heightAnchor,
|
|
multiplier: clampedRatio
|
|
),
|
|
imageView.widthAnchor.constraint(lessThanOrEqualTo: widthAnchor),
|
|
imageView.heightAnchor.constraint(lessThanOrEqualTo: heightAnchor)
|
|
])
|
|
}
|
|
|
|
private func createVideoPreview() {
|
|
guard attachment.isValidVideo else {
|
|
createGenericPreview()
|
|
return
|
|
}
|
|
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([
|
|
imageView.centerXAnchor.constraint(equalTo: centerXAnchor),
|
|
imageView.centerYAnchor.constraint(equalTo: centerYAnchor),
|
|
imageView.widthAnchor.constraint(
|
|
equalTo: imageView.heightAnchor,
|
|
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() {
|
|
// 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()
|
|
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()
|
|
|
|
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)
|
|
.done { [weak self] draft in
|
|
// TODO: Look at refactoring this behaviour to consolidate attachment mutations
|
|
self?.attachment.linkPreviewDraft = draft
|
|
self?.linkPreviewInfo = (url: linkPreviewURL, draft: draft)
|
|
|
|
// Update the UI
|
|
self?.titleLabel.text = (draft.title ?? self?.titleLabel.text)
|
|
self?.loadingView.alpha = 0
|
|
self?.loadingView.stopAnimating()
|
|
self?.imageView.alpha = 1
|
|
|
|
if let jpegImageData: Data = draft.jpegImageData, let loadedImage: UIImage = UIImage(data: jpegImageData) {
|
|
self?.imageView.image = loadedImage
|
|
self?.imageView.contentMode = .scaleAspectFill
|
|
}
|
|
}
|
|
.catch { [weak self] _ in
|
|
self?.titleLabel.attributedText = NSMutableAttributedString(string: linkPreviewURL)
|
|
.rtlSafeAppend(
|
|
"\n\("vc_share_link_previews_error".localized())",
|
|
attributes: [
|
|
NSAttributedString.Key.font: UIFont.ows_regularFont(
|
|
withSize: Values.verySmallFontSize
|
|
),
|
|
NSAttributedString.Key.foregroundColor: self?.fileSizeLabel.textColor
|
|
]
|
|
.compactMapValues { $0 }
|
|
)
|
|
self?.loadingView.alpha = 0
|
|
self?.loadingView.stopAnimating()
|
|
self?.imageView.alpha = 1
|
|
}
|
|
.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
|
|
|
|
@objc func audioPlayPauseButtonPressed(sender: UIButton) {
|
|
audioPlayer?.togglePlayState()
|
|
}
|
|
|
|
// MARK: - OWSAudioPlayerDelegate
|
|
|
|
public func audioPlaybackState() -> AudioPlaybackState {
|
|
return playbackState
|
|
}
|
|
|
|
public func setAudioPlaybackState(_ value: AudioPlaybackState) {
|
|
playbackState = value
|
|
}
|
|
|
|
public func showInvalidAudioFileAlert() {
|
|
OWSAlerts.showErrorAlert(message: NSLocalizedString("INVALID_AUDIO_FILE_ALERT_ERROR_MESSAGE", comment: "Message for the alert indicating that an audio file is invalid."))
|
|
}
|
|
|
|
public func audioPlayerDidFinishPlaying(_ player: OWSAudioPlayer, successfully flag: Bool) {
|
|
// Do nothing
|
|
}
|
|
|
|
private func ensureButtonState() {
|
|
switch playbackState {
|
|
case .playing: setAudioIconToPause()
|
|
default: setAudioIconToPlay()
|
|
}
|
|
}
|
|
|
|
public func setAudioProgress(_ progress: CGFloat, duration: CGFloat) {
|
|
audioProgressSeconds = progress
|
|
audioDurationSeconds = duration
|
|
}
|
|
|
|
private func setAudioIconToPlay() {
|
|
audioPlayPauseButton.setImage(UIImage(named: "Play"), for: .normal)
|
|
}
|
|
|
|
private func setAudioIconToPause() {
|
|
audioPlayPauseButton.setImage(UIImage(named: "Pause"), for: .normal)
|
|
}
|
|
}
|