//
// C o p y r i g h t ( c ) 2 0 1 9 O p e n W h i s p e r S y s t e m s . A l l r i g h t s r e s e r v e d .
//
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: P r o p e r t i e s
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: I n i t i a l i z e r s
@ available ( * , unavailable , message : " use other constructor instead. " )
required public init ? ( coder aDecoder : NSCoder ) {
notImplemented ( )
}
// C u r r e n t l y w e o n l y u s e o n e m o d e ( A t t a c h m e n t A p p r o v a l ) , s o w e c o u l d s i m p l i f y t h i s c l a s s , b u t i t ' s k i n d
// o f n i c e t h a t i t ' s w r i t t e n i n a f l e x i b l e w a y i n c a s e w e ' d w a n t t o u s e i t e l s e w h e r e a g a i n i n t h e f u t u r e .
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: - U I
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
} ( )
// / N o t e : T h i s u s e s d i f f e r e n t a s s e t s f r o m t h e ` v i d e o P l a y B u t t o n ` a n d h a s a ' P a u s e ' s t a t e
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
// F o r m a t s t r i n g f o r f i l e s i z e l a b e l i n c a l l i n t e r s t i t i a l v i e w .
// E m b e d s : { { f i l e s i z e a s ' N m b ' o r ' N k b ' } } .
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: - L a y o u t
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 {
// D o n o t h i n g a s w e w i l l j u s t p u t t h e t e x t i n t h e ' m e s s a g e ' i n p u t
} else {
createGenericPreview ( )
}
}
private func setupLayout ( ) {
// B o t t o m i n s e t
}
// TODO: A n y r e a s o n f o r n o t j u s t u s i n g U I S t a c k V i e w
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
}
// p r i v a t e f u n c s t a c k S p a c i n g ( ) - > C G F l o a t {
// s w i t c h m o d e {
// c a s e . l a r g e , . a t t a c h m e n t A p p r o v a l :
// r e t u r n C G F l o a t ( 1 0 )
// c a s e . s m a l l :
// r e t u r n C G F l o a t ( 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 )
// a d d S u b v i e w W i t h S c a l e A s p e c t F i t L a y o u t ( v i e w : a n i m a t e d I m a g e V i e w , a s p e c t R a t i o : a s p e c t R a t i o )
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 )
] )
}
// p r i v a t e f u n c a d d S u b v i e w W i t h S c a l e A s p e c t F i t L a y o u t ( v i e w : U I V i e w , a s p e c t R a t i o : C G F l o a t ) {
// s e l f . a d d S u b v i e w ( v i e w )
// / / T h i s e m u l a t e s t h e b e h a v i o r o f c o n t e n t M o d e = . s c a l e A s p e c t F i t u s i n g
// / / i O S a u t o l a y o u t c o n s t r a i n t s .
// / /
// / / T h i s a l l o w s C o n v e r s a t i o n I n p u t T o o l b a r t o p l a c e t h e " c a n c e l " b u t t o n
// / / i n t h e u p p e r - r i g h t h a n d c o r n e r o f t h e p r e v i e w c o n t e n t .
// v i e w . a u t o C e n t e r I n S u p e r v i e w ( )
// v i e w . a u t o P i n ( t o A s p e c t R a t i o : a s p e c t R a t i o )
// v i e w . a u t o M a t c h ( . w i d t h , t o : . w i d t h , o f : s e l f , w i t h M u l t i p l i e r : 1 . 0 , r e l a t i o n : . l e s s T h a n O r E q u a l )
// v i e w . a u t o M a t c h ( . h e i g h t , t o : . h e i g h t , o f : s e l f , w i t h M u l t i p l i e r : 1 . 0 , r e l a t i o n : . l e s s T h a n O r E q u a l )
// }
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
// i m a g e V i e w . l a y e r . m i n i f i c a t i o n F i l t e r = . t r i l i n e a r
// i m a g e V i e w . l a y e r . m a g n i f i c a t i o n F i l t e r = . t r i l i n e a r
let aspectRatio = image . size . width / image . size . height
let clampedRatio : CGFloat = CGFloatClamp ( aspectRatio , 0.05 , 95.0 )
// a d d S u b v i e w W i t h S c a l e A s p e c t F i t L a y o u t ( v i e w : i m a g e V i e w , a s p e c t R a t i o : a s p e c t R a t i o )
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
// A t t a c h m e n t a p p r o v a l p r o v i d e s i t ' s o w n p l a y b u t t o n t o k e e p i t
// a t t h e p r o p e r z o o m s c a l e .
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 )
] )
// A t t a c h m e n t a p p r o v a l p r o v i d e s i t ' s o w n p l a y b u t t o n t o k e e p i t
// a t t h e p r o p e r z o o m s c a l e .
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 ( ) {
// I f l i n k p r e v i e w s a r e n ' t e n a b l e d t h e n u s e a f a l l b a c k s t a t e
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 // N o t ' i s H i d d e n ' b e c a u s e w e w a n t i t t o t a k e u p s p a c e i n t h e U I S t a c k V i e w
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 ) )
] )
// B u i l d t h e l i n k p r e v i e w
OWSLinkPreview . tryToBuildPreviewInfo ( previewUrl : linkPreviewURL )
. done { [ weak self ] draft in
// TODO: L o o k a t r e f a c t o r i n g t h i s b e h a v i o u r t o c o n s o l i d a t e a t t a c h m e n t m u t a t i o n s
self ? . attachment . linkPreviewDraft = draft
self ? . linkPreviewInfo = ( url : linkPreviewURL , draft : draft )
// U p d a t e t h e U I
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: - E v e n t H a n d l e r s
@objc func audioPlayPauseButtonPressed ( sender : UIButton ) {
audioPlayer ? . togglePlayState ( )
}
// MARK: - O W S A u d i o P l a y e r D e l e g a t e
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 ) {
// D o n o t h i n g
}
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 )
}
}