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.
661 lines
26 KiB
Swift
661 lines
26 KiB
Swift
//
|
|
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
|
|
//
|
|
|
|
import Foundation
|
|
import MediaPlayer
|
|
import YYImage
|
|
import NVActivityIndicatorView
|
|
import SessionUIKit
|
|
import SessionMessagingKit
|
|
|
|
public protocol MediaMessageViewAudioDelegate: AnyObject {
|
|
func progressChanged(_ progressSeconds: CGFloat, durationSeconds: CGFloat)
|
|
}
|
|
|
|
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 lazy var audioPlayer: OWSAudioPlayer? = {
|
|
guard let dataUrl = attachment.dataUrl else { return nil }
|
|
|
|
return OWSAudioPlayer(mediaUrl: dataUrl, audioBehavior: .playback, delegate: self)
|
|
}()
|
|
|
|
public var wasPlayingAudio: Bool = false
|
|
public var audioProgressSeconds: CGFloat = 0
|
|
public var audioDurationSeconds: CGFloat = 0
|
|
public weak var audioDelegate: MediaMessageViewAudioDelegate?
|
|
|
|
public var playbackState = AudioPlaybackState.stopped {
|
|
didSet {
|
|
AssertIsOnMainThread()
|
|
|
|
ensureButtonState()
|
|
}
|
|
}
|
|
|
|
private lazy var validImage: UIImage? = {
|
|
if attachment.isImage {
|
|
guard
|
|
attachment.isValidImage,
|
|
let image: UIImage = attachment.image(),
|
|
image.size.width > 0,
|
|
image.size.height > 0
|
|
else {
|
|
return nil
|
|
}
|
|
|
|
return image
|
|
}
|
|
else if attachment.isVideo {
|
|
guard
|
|
attachment.isValidVideo,
|
|
let image: UIImage = attachment.videoPreview(),
|
|
image.size.width > 0,
|
|
image.size.height > 0
|
|
else {
|
|
return nil
|
|
}
|
|
|
|
return image
|
|
}
|
|
|
|
return nil
|
|
}()
|
|
private lazy var validAnimatedImage: YYImage? = {
|
|
guard
|
|
attachment.isAnimatedImage,
|
|
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: LinkPreviewDraft?)?
|
|
|
|
// 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
|
|
|
|
// Set the linkPreviewUrl if it's a url
|
|
if attachment.isUrl, let linkPreviewURL: String = LinkPreview.previewUrl(for: attachment.text()) {
|
|
self.linkPreviewInfo = (url: linkPreviewURL, draft: nil)
|
|
}
|
|
|
|
super.init(frame: CGRect.zero)
|
|
|
|
setupViews()
|
|
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 = .fill
|
|
|
|
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.image = UIImage(named: "FileLarge")?.withRenderingMode(.alwaysTemplate)
|
|
view.tintColor = Colors.text
|
|
view.isHidden = true
|
|
|
|
// Override the image to the correct one
|
|
if attachment.isImage || attachment.isVideo {
|
|
if let validImage: UIImage = validImage {
|
|
view.layer.minificationFilter = .trilinear
|
|
view.layer.magnificationFilter = .trilinear
|
|
view.image = validImage
|
|
}
|
|
}
|
|
else if attachment.isUrl {
|
|
view.clipsToBounds = true
|
|
view.image = UIImage(named: "Link")?.withRenderingMode(.alwaysTemplate)
|
|
view.tintColor = Colors.text
|
|
view.contentMode = .center
|
|
view.backgroundColor = (isDarkMode ? .black : UIColor.black.withAlphaComponent(0.06))
|
|
view.layer.cornerRadius = 8
|
|
}
|
|
|
|
return view
|
|
}()
|
|
|
|
private lazy var fileTypeImageView: UIImageView = {
|
|
let view: UIImageView = UIImageView()
|
|
view.translatesAutoresizingMaskIntoConstraints = false
|
|
view.isHidden = true
|
|
|
|
return view
|
|
}()
|
|
|
|
private lazy var animatedImageView: YYAnimatedImageView = {
|
|
let view: YYAnimatedImageView = YYAnimatedImageView()
|
|
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
|
|
}()
|
|
|
|
lazy var videoPlayButton: UIImageView = {
|
|
let view: UIImageView = UIImageView(image: UIImage(named: "CirclePlay"))
|
|
view.translatesAutoresizingMaskIntoConstraints = false
|
|
view.contentMode = .scaleAspectFit
|
|
view.isHidden = true
|
|
|
|
return view
|
|
}()
|
|
|
|
/// 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(isDarkMode: isDarkMode), for: .normal)
|
|
button.setBackgroundImage(UIColor.white.darken(by: 0.2).toImage(isDarkMode: isDarkMode), for: .highlighted)
|
|
button.addTarget(self, action: #selector(audioPlayPauseButtonPressed), for: .touchUpInside)
|
|
button.isHidden = true
|
|
|
|
return button
|
|
}()
|
|
|
|
private lazy var titleStackView: UIStackView = {
|
|
let stackView: UIStackView = UIStackView()
|
|
stackView.translatesAutoresizingMaskIntoConstraints = false
|
|
stackView.axis = .vertical
|
|
stackView.alignment = (attachment.isUrl && linkPreviewInfo?.url != nil ? .leading : .center)
|
|
stackView.distribution = .fill
|
|
|
|
switch mode {
|
|
case .attachmentApproval: stackView.spacing = 2
|
|
case .large: stackView.spacing = 10
|
|
case .small: stackView.spacing = 5
|
|
}
|
|
|
|
return stackView
|
|
}()
|
|
|
|
private lazy var titleLabel: UILabel = {
|
|
let label: UILabel = UILabel()
|
|
label.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
// Styling
|
|
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
|
|
}
|
|
|
|
// Content
|
|
if attachment.isUrl {
|
|
// If we have no link preview info at this point then assume link previews are disabled
|
|
if let linkPreviewURL: String = linkPreviewInfo?.url {
|
|
label.font = .boldSystemFont(ofSize: Values.smallFontSize)
|
|
label.text = linkPreviewURL
|
|
label.textAlignment = .left
|
|
label.lineBreakMode = .byTruncatingTail
|
|
label.numberOfLines = 2
|
|
}
|
|
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 {
|
|
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
|
|
}()
|
|
|
|
private lazy var subtitleLabel: UILabel = {
|
|
let label: UILabel = UILabel()
|
|
label.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
// Styling
|
|
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
|
|
}
|
|
|
|
// Content
|
|
if attachment.isUrl {
|
|
// We only load Link Previews for HTTPS urls so append an explanation for not
|
|
if let linkPreviewURL: String = linkPreviewInfo?.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
|
|
else {
|
|
label.text = "vc_share_link_previews_disabled_explanation".localized()
|
|
label.textColor = Colors.text
|
|
label.textAlignment = .center
|
|
label.numberOfLines = 0
|
|
}
|
|
}
|
|
// Subtitle for everything else except these types
|
|
else if !attachment.isImage && !attachment.isAnimatedImage && !attachment.isVideo {
|
|
// 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
|
|
}
|
|
|
|
// Hide the label if it has no content
|
|
label.isHidden = ((label.text?.count ?? 0) == 0)
|
|
|
|
return label
|
|
}()
|
|
|
|
// MARK: - Layout
|
|
|
|
private func setupViews() {
|
|
// Plain text will just be put in the 'message' input so do nothing
|
|
guard !attachment.isText else { return }
|
|
|
|
// Setup the view hierarchy
|
|
addSubview(stackView)
|
|
addSubview(loadingView)
|
|
addSubview(videoPlayButton)
|
|
|
|
stackView.addArrangedSubview(imageView)
|
|
stackView.addArrangedSubview(animatedImageView)
|
|
if !titleLabel.isHidden { stackView.addArrangedSubview(UIView.vhSpacer(10, 10)) }
|
|
stackView.addArrangedSubview(titleStackView)
|
|
|
|
titleStackView.addArrangedSubview(titleLabel)
|
|
titleStackView.addArrangedSubview(subtitleLabel)
|
|
|
|
imageView.addSubview(fileTypeImageView)
|
|
|
|
// Type-specific configurations
|
|
if attachment.isAnimatedImage {
|
|
animatedImageView.isHidden = false
|
|
}
|
|
else if attachment.isImage {
|
|
imageView.isHidden = false
|
|
}
|
|
else if attachment.isVideo {
|
|
// 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)
|
|
}
|
|
else if attachment.isAudio {
|
|
// Hide the 'audioPlayPauseButton' if the 'audioPlayer' failed to get created
|
|
imageView.isHidden = false
|
|
audioPlayPauseButton.isHidden = (audioPlayer == nil)
|
|
setAudioIconToPlay()
|
|
setAudioProgress(0, duration: (audioPlayer?.duration ?? 0))
|
|
|
|
fileTypeImageView.image = UIImage(named: "table_ic_notification_sound")?
|
|
.withRenderingMode(.alwaysTemplate)
|
|
fileTypeImageView.tintColor = Colors.text
|
|
fileTypeImageView.isHidden = false
|
|
|
|
// 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)
|
|
}
|
|
else if attachment.isUrl {
|
|
imageView.isHidden = false
|
|
imageView.alpha = 0 // Not 'isHidden' because we want it to take up space in the UIStackView
|
|
loadingView.isHidden = false
|
|
|
|
if let linkPreviewUrl: String = linkPreviewInfo?.url {
|
|
// 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
|
|
|
|
loadLinkPreview(linkPreviewURL: linkPreviewUrl)
|
|
}
|
|
}
|
|
else {
|
|
imageView.isHidden = false
|
|
}
|
|
}
|
|
|
|
private func setupLayout() {
|
|
// Plain text will just be put in the 'message' input so do nothing
|
|
guard !attachment.isText else { return }
|
|
|
|
// Sizing calculations
|
|
let clampedRatio: CGFloat = {
|
|
if attachment.isUrl {
|
|
return 1
|
|
}
|
|
|
|
if attachment.isAnimatedImage {
|
|
let imageSize: CGSize = (animatedImageView.image?.size ?? CGSize(width: 1, height: 1))
|
|
let aspectRatio: CGFloat = (imageSize.width / imageSize.height)
|
|
|
|
return CGFloatClamp(aspectRatio, 0.05, 95.0)
|
|
}
|
|
|
|
// All other types should maintain the ratio of the image in the 'imageView'
|
|
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)
|
|
}()
|
|
|
|
let maybeImageSize: CGFloat? = {
|
|
if attachment.isImage || attachment.isVideo {
|
|
if validImage != nil { return nil }
|
|
|
|
// If we don't have a valid image then use the 'generic' case
|
|
}
|
|
else if attachment.isAnimatedImage {
|
|
if validAnimatedImage != nil { return nil }
|
|
|
|
// If we don't have a valid image then use the 'generic' case
|
|
}
|
|
else if attachment.isUrl {
|
|
return 80
|
|
}
|
|
|
|
// Generic file size
|
|
switch mode {
|
|
case .large: return 200
|
|
case .attachmentApproval: return 120
|
|
case .small: return 80
|
|
}
|
|
}()
|
|
|
|
let imageSize: CGFloat = (maybeImageSize ?? 0)
|
|
let audioButtonSize: CGFloat = (imageSize / 2.5)
|
|
audioPlayPauseButton.layer.cornerRadius = (audioButtonSize / 2)
|
|
|
|
// Actual layout
|
|
NSLayoutConstraint.activate([
|
|
stackView.centerXAnchor.constraint(equalTo: centerXAnchor),
|
|
stackView.centerYAnchor.constraint(equalTo: centerYAnchor),
|
|
stackView.heightAnchor.constraint(lessThanOrEqualTo: heightAnchor),
|
|
|
|
(maybeImageSize != nil ?
|
|
stackView.widthAnchor.constraint(
|
|
equalTo: widthAnchor,
|
|
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.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),
|
|
|
|
videoPlayButton.centerXAnchor.constraint(equalTo: centerXAnchor),
|
|
videoPlayButton.centerYAnchor.constraint(equalTo: centerYAnchor),
|
|
|
|
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))
|
|
])
|
|
|
|
// No inset for the text for URLs but there is for all other layouts
|
|
if !attachment.isUrl {
|
|
NSLayoutConstraint.activate([
|
|
titleLabel.widthAnchor.constraint(equalTo: stackView.widthAnchor, constant: -(32 * 2)),
|
|
subtitleLabel.widthAnchor.constraint(equalTo: stackView.widthAnchor, constant: -(32 * 2))
|
|
])
|
|
}
|
|
|
|
// 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)
|
|
if attachment.isAudio {
|
|
NSLayoutConstraint.activate([
|
|
audioPlayPauseButton.centerXAnchor.constraint(equalTo: imageView.centerXAnchor),
|
|
audioPlayPauseButton.centerYAnchor.constraint(equalTo: imageView.centerYAnchor),
|
|
audioPlayPauseButton.widthAnchor.constraint(equalToConstant: audioButtonSize),
|
|
audioPlayPauseButton.heightAnchor.constraint(equalToConstant: audioButtonSize),
|
|
])
|
|
}
|
|
}
|
|
|
|
// MARK: - Link Loading
|
|
|
|
private func loadLinkPreview(linkPreviewURL: String) {
|
|
loadingView.startAnimating()
|
|
|
|
LinkPreview.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?.loadingView.alpha = 0
|
|
self?.loadingView.stopAnimating()
|
|
self?.imageView.alpha = 1
|
|
self?.titleLabel.numberOfLines = 1 // Truncates the URL at 1 line so the error is more readable
|
|
self?.subtitleLabel.isHidden = false
|
|
|
|
// Set the error text appropriately
|
|
if let targetUrl: URL = URL(string: linkPreviewURL), targetUrl.scheme?.lowercased() != "https" {
|
|
// This error case is handled already in the 'subtitleLabel' creation
|
|
}
|
|
else {
|
|
self?.subtitleLabel.font = UIFont.ows_regularFont(withSize: Values.verySmallFontSize)
|
|
self?.subtitleLabel.text = "vc_share_link_previews_error".localized()
|
|
self?.subtitleLabel.textColor = (self?.mode == .attachmentApproval ? Colors.pinIcon : Colors.accent )
|
|
self?.subtitleLabel.textAlignment = .left
|
|
}
|
|
}
|
|
.retainUntilComplete()
|
|
}
|
|
|
|
// MARK: - Functions
|
|
|
|
public func playAudio() {
|
|
audioPlayer?.play()
|
|
ensureButtonState()
|
|
}
|
|
|
|
public func pauseAudio() {
|
|
wasPlayingAudio = (audioPlayer?.isPlaying == true)
|
|
|
|
// If the 'audioPlayer' has a duration of 0 then we probably haven't played previously which
|
|
// will result in the audioPlayer having a 'duration' of 0 breaking the progressBar. We play
|
|
// the audio to get it to properly load the file right before pausing it so the data is
|
|
// loaded correctly
|
|
if audioPlayer?.duration == 0 {
|
|
audioPlayer?.play()
|
|
}
|
|
|
|
audioPlayer?.pause()
|
|
ensureButtonState()
|
|
}
|
|
|
|
public func setAudioTime(currentTime: TimeInterval) {
|
|
audioPlayer?.setCurrentTime(currentTime)
|
|
}
|
|
|
|
// 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) {
|
|
// Note: When the OWSAudioPlayer stops it sets the duration to 0 (which we want to ignore so
|
|
// the UI doesn't look buggy)
|
|
let finalDuration: CGFloat = (duration > 0 ? duration : audioDurationSeconds)
|
|
audioProgressSeconds = progress
|
|
audioDurationSeconds = finalDuration
|
|
|
|
audioDelegate?.progressChanged(progress, durationSeconds: finalDuration)
|
|
}
|
|
|
|
private func setAudioIconToPlay() {
|
|
audioPlayPauseButton.setImage(UIImage(named: "Play"), for: .normal)
|
|
}
|
|
|
|
private func setAudioIconToPause() {
|
|
audioPlayPauseButton.setImage(UIImage(named: "Pause"), for: .normal)
|
|
}
|
|
}
|