2017-09-19 16:36:23 +02:00
//
2019-01-18 16:23:07 +01:00
// 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 .
2017-09-19 16:36:23 +02:00
//
import Foundation
2020-11-11 07:45:50 +01:00
import SignalUtilitiesKit
import SignalUtilitiesKit
2017-09-19 16:36:23 +02:00
2017-10-25 20:53:54 +02:00
@objc
enum MessageMetadataViewMode : UInt {
case focusOnMessage
case focusOnMetadata
}
2019-02-20 20:44:30 +01:00
@objc
protocol MessageDetailViewDelegate : AnyObject {
func detailViewMessageWasDeleted ( _ messageDetailViewController : MessageDetailViewController )
}
@objc
2020-11-16 00:34:47 +01:00
class MessageDetailViewController : OWSViewController , MediaGalleryDataSourceDelegate , OWSMessageBubbleViewDelegate {
2017-09-19 16:36:23 +02:00
2019-02-20 20:44:30 +01:00
@objc
weak var delegate : MessageDetailViewDelegate ?
2017-09-19 16:36:23 +02:00
// MARK: P r o p e r t i e s
2018-03-12 23:04:16 +01:00
let uiDatabaseConnection : YapDatabaseConnection
2017-09-25 17:37:05 +02:00
2017-10-14 00:06:06 +02:00
var bubbleView : UIView ?
2017-09-28 21:27:17 +02:00
2017-10-25 20:53:54 +02:00
let mode : MessageMetadataViewMode
let viewItem : ConversationViewItem
2017-09-25 17:37:05 +02:00
var message : TSMessage
2018-03-22 19:10:41 +01:00
var wasDeleted : Bool = false
2017-09-19 16:36:23 +02:00
2018-04-05 21:01:02 +02:00
var messageBubbleView : OWSMessageBubbleView ?
var messageBubbleViewWidthLayoutConstraint : NSLayoutConstraint ?
var messageBubbleViewHeightLayoutConstraint : NSLayoutConstraint ?
2017-10-26 20:34:30 +02:00
2017-11-15 18:50:56 +01:00
var scrollView : UIScrollView !
2017-09-19 16:36:23 +02:00
var contentView : UIView ?
2017-09-28 03:42:58 +02:00
var attachment : TSAttachment ?
2017-09-19 16:36:23 +02:00
var dataSource : DataSource ?
var attachmentStream : TSAttachmentStream ?
var messageBody : String ?
2018-10-11 01:17:42 +02:00
lazy var shouldShowUD : Bool = {
return self . preferences . shouldShowUnidentifiedDeliveryIndicators ( )
} ( )
2018-06-25 21:20:17 +02:00
var conversationStyle : ConversationStyle
2018-06-25 21:00:45 +02:00
2018-10-11 01:17:01 +02:00
// MARK: D e p e n d e n c i e s
2018-10-11 01:17:42 +02:00
var preferences : OWSPreferences {
return Environment . shared . preferences
}
2017-09-19 16:36:23 +02:00
// MARK: I n i t i a l i z e r s
2017-10-25 20:53:54 +02:00
@ available ( * , unavailable , message : " use other constructor instead. " )
2017-09-19 16:36:23 +02:00
required init ? ( coder aDecoder : NSCoder ) {
2018-08-27 16:21:03 +02:00
notImplemented ( )
2017-09-19 16:36:23 +02:00
}
2018-05-25 23:28:36 +02:00
@objc
2018-06-22 19:48:23 +02:00
required init ( viewItem : ConversationViewItem , message : TSMessage , thread : TSThread , mode : MessageMetadataViewMode ) {
2017-10-25 20:53:54 +02:00
self . viewItem = viewItem
2017-09-19 16:36:23 +02:00
self . message = message
2017-10-25 20:53:54 +02:00
self . mode = mode
2019-02-20 20:44:30 +01:00
self . uiDatabaseConnection = OWSPrimaryStorage . shared ( ) . uiDatabaseConnection
2018-06-25 21:20:17 +02:00
self . conversationStyle = ConversationStyle ( thread : thread )
2018-06-22 19:48:23 +02:00
2017-09-19 16:36:23 +02:00
super . init ( nibName : nil , bundle : nil )
}
// MARK: V i e w L i f e c y c l e
override func viewDidLoad ( ) {
super . viewDidLoad ( )
2019-02-20 20:44:30 +01:00
do {
2019-02-27 01:19:18 +01:00
try updateMessageToLatest ( )
2019-02-20 20:44:30 +01:00
} catch DetailViewError . messageWasDeleted {
self . delegate ? . detailViewMessageWasDeleted ( self )
} catch {
owsFailDebug ( " unexpected error " )
}
2017-09-25 17:37:05 +02:00
2018-06-25 21:20:17 +02:00
self . conversationStyle . viewWidth = view . width ( )
2018-06-25 18:43:25 +02:00
2020-08-28 05:48:27 +02:00
ViewControllerUtilities . setUpDefaultSessionStyle ( for : self , title : NSLocalizedString ( " MESSAGE_METADATA_VIEW_TITLE " , comment : " Title for the 'message metadata' view. " ) , hasCustomBackButton : false )
2017-09-19 16:36:23 +02:00
createViews ( )
2017-09-25 17:37:05 +02:00
2017-10-14 00:06:06 +02:00
self . view . layoutIfNeeded ( )
2017-10-25 20:53:54 +02:00
2017-09-25 17:37:05 +02:00
NotificationCenter . default . addObserver ( self ,
2019-02-20 20:44:30 +01:00
selector : #selector ( uiDatabaseDidUpdate ) ,
name : . OWSUIDatabaseConnectionDidUpdate ,
2018-03-05 15:30:58 +01:00
object : OWSPrimaryStorage . shared ( ) . dbNotificationObject )
2017-09-19 16:36:23 +02:00
}
2018-06-25 15:42:18 +02:00
override public func viewWillTransition ( to size : CGSize , with coordinator : UIViewControllerTransitionCoordinator ) {
2018-08-23 16:37:34 +02:00
Logger . debug ( " " )
2018-06-22 19:48:23 +02:00
2018-06-25 15:42:18 +02:00
super . viewWillTransition ( to : size , with : coordinator )
2018-06-22 19:48:23 +02:00
2018-06-25 21:20:17 +02:00
self . conversationStyle . viewWidth = size . width
2018-06-22 19:48:23 +02:00
}
2017-09-19 16:36:23 +02:00
override func viewWillAppear ( _ animated : Bool ) {
super . viewWillAppear ( animated )
2018-04-05 21:01:02 +02:00
updateMessageBubbleViewLayout ( )
2018-02-14 01:53:23 +01:00
2017-11-02 17:41:24 +01:00
if mode = = . focusOnMetadata {
if let bubbleView = self . bubbleView {
// F o r c e l a y o u t .
view . setNeedsLayout ( )
view . layoutIfNeeded ( )
2017-11-15 18:50:56 +01:00
let contentHeight = scrollView . contentSize . height
let scrollViewHeight = scrollView . frame . size . height
guard contentHeight >= scrollViewHeight else {
// A l l c o n t e n t i s v i s i b l e w i t h i n t h e s c r o l l v i e w . N o n e e d t o o f f s e t .
return
}
// W e w a n t t o i n c l u d e a t l e a s t a l i t t l e p o r t i o n o f t h e m e s s a g e , b u t s c r o l l n o f a r t h e r t h a n n e c e s s a r y .
2017-11-02 17:41:24 +01:00
let showAtLeast : CGFloat = 50
2017-11-15 18:50:56 +01:00
let bubbleViewBottom = bubbleView . superview ! . convert ( bubbleView . frame , to : scrollView ) . maxY
let maxOffset = bubbleViewBottom - showAtLeast
let lastPage = contentHeight - scrollViewHeight
let offset = CGPoint ( x : 0 , y : min ( maxOffset , lastPage ) )
scrollView . setContentOffset ( offset , animated : false )
2017-11-02 17:41:24 +01:00
}
}
2017-09-19 16:36:23 +02:00
}
// MARK: - C r e a t e V i e w s
private func createViews ( ) {
2019-12-13 05:02:05 +01:00
view . backgroundColor = . clear
2017-09-19 16:36:23 +02:00
let scrollView = UIScrollView ( )
self . scrollView = scrollView
view . addSubview ( scrollView )
2017-10-17 06:05:29 +02:00
scrollView . autoPinWidthToSuperview ( withMargin : 0 )
2018-08-15 21:43:05 +02:00
2018-08-22 22:37:01 +02:00
if scrollView . applyInsetsFix ( ) {
2020-06-05 05:43:06 +02:00
scrollView . autoPinEdge ( . top , to : . top , of : view )
2018-08-15 21:43:05 +02:00
} else {
scrollView . autoPinEdge ( toSuperviewEdge : . top )
}
2017-09-19 16:36:23 +02:00
let contentView = UIView . container ( )
self . contentView = contentView
scrollView . addSubview ( contentView )
2018-04-02 21:31:32 +02:00
contentView . autoPinLeadingToSuperviewMargin ( )
contentView . autoPinTrailingToSuperviewMargin ( )
2017-10-17 06:05:29 +02:00
contentView . autoPinEdge ( toSuperviewEdge : . top )
contentView . autoPinEdge ( toSuperviewEdge : . bottom )
2017-09-27 22:16:56 +02:00
scrollView . layoutMargins = UIEdgeInsets ( top : 0 , left : 0 , bottom : 0 , right : 0 )
2018-10-12 03:40:47 +02:00
scrollView . contentInset = UIEdgeInsets ( top : 20 , left : 0 , bottom : 20 , right : 0 )
2017-09-19 16:36:23 +02:00
2017-12-20 03:23:24 +01:00
if hasMediaAttachment {
2017-09-25 17:37:05 +02:00
let footer = UIToolbar ( )
view . addSubview ( footer )
2017-10-17 06:05:29 +02:00
footer . autoPinWidthToSuperview ( withMargin : 0 )
footer . autoPinEdge ( . top , to : . bottom , of : scrollView )
2020-06-05 05:43:06 +02:00
footer . autoPinEdge ( . bottom , to : . bottom , of : view )
2017-09-25 17:37:05 +02:00
footer . items = [
UIBarButtonItem ( barButtonSystemItem : . flexibleSpace , target : nil , action : nil ) ,
UIBarButtonItem ( barButtonSystemItem : . action , target : self , action : #selector ( shareButtonPressed ) ) ,
UIBarButtonItem ( barButtonSystemItem : . flexibleSpace , target : nil , action : nil )
]
} else {
2018-04-09 16:06:46 +02:00
scrollView . autoPinEdge ( toSuperviewEdge : . bottom )
2017-09-25 17:37:05 +02:00
}
updateContent ( )
}
2018-03-12 23:04:16 +01:00
lazy var thread : TSThread = {
var thread : TSThread ?
self . uiDatabaseConnection . read { transaction in
thread = self . message . thread ( with : transaction )
}
return thread !
} ( )
2017-09-25 17:37:05 +02:00
private func updateContent ( ) {
guard let contentView = contentView else {
2018-08-27 16:27:48 +02:00
owsFailDebug ( " Missing contentView " )
2017-09-25 17:37:05 +02:00
return
}
// R e m o v e a n y e x i s t i n g c o n t e n t v i e w s .
for subview in contentView . subviews {
subview . removeFromSuperview ( )
}
2017-09-19 16:36:23 +02:00
var rows = [ UIView ] ( )
2017-09-28 03:42:58 +02:00
// C o n t e n t
2017-09-28 16:09:54 +02:00
rows += contentRows ( )
2017-09-28 03:42:58 +02:00
2017-09-19 16:36:23 +02:00
// S e n d e r ?
if let incomingMessage = message as ? TSIncomingMessage {
let senderId = incomingMessage . authorId
2020-09-15 01:48:20 +02:00
let threadID = thread . uniqueId !
var senderName : String !
2020-11-04 01:46:30 +01:00
Storage . writeSync { transaction in
2020-09-15 01:48:20 +02:00
senderName = DisplayNameUtilities2 . getDisplayName ( for : senderId , inThreadWithID : threadID , using : transaction )
}
2017-09-19 16:36:23 +02:00
rows . append ( valueRow ( name : NSLocalizedString ( " MESSAGE_METADATA_VIEW_SENDER " ,
comment : " Label for the 'sender' field of the 'message metadata' view. " ) ,
2017-10-17 06:05:29 +02:00
value : senderName ) )
2017-09-19 16:36:23 +02:00
}
// R e c i p i e n t ( s )
if let outgoingMessage = message as ? TSOutgoingMessage {
2017-09-27 22:16:56 +02:00
2020-10-02 01:03:06 +02:00
func getSeparator ( ) -> UIView {
let result = UIView ( )
result . set ( . height , to : Values . separatorThickness )
result . backgroundColor = Colors . separator
return result
}
2017-09-28 03:42:58 +02:00
2020-10-02 01:03:06 +02:00
if ! outgoingMessage . recipientIds ( ) . isEmpty {
rows += [ getSeparator ( ) ]
}
2017-09-28 03:42:58 +02:00
2020-10-02 01:03:06 +02:00
rows += outgoingMessage . recipientIds ( ) . flatMap { publicKey -> [ UIView ] in
// W e u s e C o n t a c t C e l l V i e w , n o t C o n t a c t T a b l e V i e w C e l l .
// T a b l e v i e w c e l l s d o n ' t l a y o u t p r o p e r l y o u t s i d e t h e
// c o n t e x t o f a t a b l e v i e w .
let cellView = ContactCellView ( )
cellView . configure ( withRecipientId : publicKey )
let wrapper = UIView ( )
wrapper . layoutMargins = UIEdgeInsets ( top : 8 , left : 20 , bottom : 8 , right : 20 )
wrapper . addSubview ( cellView )
cellView . autoPinEdgesToSuperviewMargins ( )
return [ wrapper , getSeparator ( ) ]
}
2017-09-28 03:42:58 +02:00
2020-10-02 01:03:06 +02:00
if ! outgoingMessage . recipientIds ( ) . isEmpty {
rows += [ UIView . vSpacer ( 10 ) ]
2017-09-19 16:36:23 +02:00
}
}
2018-10-12 03:40:47 +02:00
let sentText = DateUtil . formatPastTimestampRelativeToNow ( message . timestamp )
let sentRow : UIStackView = valueRow ( name : NSLocalizedString ( " MESSAGE_METADATA_VIEW_SENT_DATE_TIME " ,
comment : " Label for the 'sent date & time' field of the 'message metadata' view. " ) ,
value : sentText )
if let incomingMessage = message as ? TSIncomingMessage {
if self . shouldShowUD , incomingMessage . wasReceivedByUD {
let icon = # imageLiteral ( resourceName : " ic_secret_sender_indicator " ) . withRenderingMode ( . alwaysTemplate )
let iconView = UIImageView ( image : icon )
iconView . tintColor = Theme . secondaryColor
iconView . setContentHuggingHigh ( )
sentRow . addArrangedSubview ( iconView )
// k e e p t h e i c o n c l o s e t o t h e l a b e l .
let spacerView = UIView ( )
spacerView . setContentHuggingLow ( )
sentRow . addArrangedSubview ( spacerView )
}
2018-10-11 01:17:42 +02:00
}
2018-10-12 03:40:47 +02:00
2018-08-16 21:49:23 +02:00
sentRow . isUserInteractionEnabled = true
2018-08-22 22:32:44 +02:00
sentRow . addGestureRecognizer ( UILongPressGestureRecognizer ( target : self , action : #selector ( didLongPressSent ) ) )
2018-08-16 21:49:23 +02:00
rows . append ( sentRow )
2017-09-19 16:36:23 +02:00
2018-10-11 01:17:01 +02:00
if message is TSIncomingMessage {
2017-09-19 16:36:23 +02:00
rows . append ( valueRow ( name : NSLocalizedString ( " MESSAGE_METADATA_VIEW_RECEIVED_DATE_TIME " ,
comment : " Label for the 'received date & time' field of the 'message metadata' view. " ) ,
2018-10-03 23:41:43 +02:00
value : DateUtil . formatPastTimestampRelativeToNow ( message . receivedAtTimestamp ) ) )
2017-09-19 16:36:23 +02:00
}
2017-09-28 03:42:58 +02:00
rows += addAttachmentMetadataRows ( )
2017-09-19 16:36:23 +02:00
// TODO: W e c o u l d i n c l u d e t h e " d i s a p p e a r i n g m e s s a g e s " s t a t e h e r e .
2018-10-12 03:40:47 +02:00
let rowStack = UIStackView ( arrangedSubviews : rows )
rowStack . axis = . vertical
rowStack . spacing = 5
contentView . addSubview ( rowStack )
rowStack . autoPinEdgesToSuperviewMargins ( )
contentView . layoutIfNeeded ( )
2018-04-05 21:01:02 +02:00
updateMessageBubbleViewLayout ( )
2017-09-28 03:42:58 +02:00
}
2017-10-25 20:53:54 +02:00
private func displayableTextIfText ( ) -> String ? {
2018-03-29 17:25:19 +02:00
guard viewItem . hasBodyText else {
2017-10-25 20:53:54 +02:00
return nil
}
2018-09-28 00:49:01 +02:00
guard let displayableText = viewItem . displayableBodyText else {
2017-10-25 20:53:54 +02:00
return nil
}
let messageBody = displayableText . fullText
2017-12-04 16:35:47 +01:00
guard messageBody . count > 0 else {
2017-10-25 20:53:54 +02:00
return nil
}
return messageBody
}
2017-11-02 17:41:24 +01:00
let bubbleViewHMargin : CGFloat = 10
2017-09-28 16:09:54 +02:00
private func contentRows ( ) -> [ UIView ] {
2017-09-28 03:42:58 +02:00
var rows = [ UIView ] ( )
2018-04-11 17:31:34 +02:00
let messageBubbleView = OWSMessageBubbleView ( frame : CGRect . zero )
messageBubbleView . delegate = self
2018-04-17 19:28:06 +02:00
messageBubbleView . addTapGestureHandler ( )
2018-04-11 17:31:34 +02:00
self . messageBubbleView = messageBubbleView
messageBubbleView . viewItem = viewItem
messageBubbleView . cellMediaCache = NSCache ( )
2018-06-25 21:20:17 +02:00
messageBubbleView . conversationStyle = conversationStyle
2018-04-11 17:31:34 +02:00
messageBubbleView . configureViews ( )
messageBubbleView . loadContent ( )
2017-10-25 20:53:54 +02:00
2018-04-11 17:31:34 +02:00
assert ( messageBubbleView . isUserInteractionEnabled )
2017-10-25 20:53:54 +02:00
2018-04-11 17:31:34 +02:00
let row = UIView ( )
row . addSubview ( messageBubbleView )
messageBubbleView . autoPinHeightToSuperview ( )
2017-10-25 20:53:54 +02:00
2018-04-11 17:31:34 +02:00
let isIncoming = self . message as ? TSIncomingMessage != nil
messageBubbleView . autoPinEdge ( toSuperviewEdge : isIncoming ? . leading : . trailing , withInset : bubbleViewHMargin )
2017-10-25 20:53:54 +02:00
2018-04-11 17:31:34 +02:00
self . messageBubbleViewWidthLayoutConstraint = messageBubbleView . autoSetDimension ( . width , toSize : 0 )
self . messageBubbleViewHeightLayoutConstraint = messageBubbleView . autoSetDimension ( . height , toSize : 0 )
rows . append ( row )
2017-12-19 19:54:54 +01:00
2018-07-30 05:13:00 +02:00
if rows . isEmpty {
2017-10-25 20:53:54 +02:00
// N e i t h e r a t t a c h m e n t n o r b o d y .
2018-08-27 16:27:48 +02:00
owsFailDebug ( " Message has neither attachment nor body. " )
2017-10-25 20:53:54 +02:00
rows . append ( valueRow ( name : NSLocalizedString ( " MESSAGE_METADATA_VIEW_NO_ATTACHMENT_OR_BODY " ,
comment : " Label for messages without a body or attachment in the 'message metadata' view. " ) ,
value : " " ) )
2017-09-19 16:36:23 +02:00
}
2017-09-28 03:42:58 +02:00
let spacer = UIView ( )
2017-10-17 06:05:29 +02:00
spacer . autoSetDimension ( . height , toSize : 15 )
2017-09-28 03:42:58 +02:00
rows . append ( spacer )
2017-09-19 16:36:23 +02:00
2017-09-28 03:42:58 +02:00
return rows
2017-09-19 16:36:23 +02:00
}
2017-12-20 03:23:24 +01:00
private func fetchAttachment ( transaction : YapDatabaseReadTransaction ) -> TSAttachment ? {
2018-11-02 16:37:35 +01:00
// TODO: S u p p o r t m u l t i - i m a g e m e s s a g e s .
2017-12-23 05:41:03 +01:00
guard let attachmentId = message . attachmentIds . firstObject as ? String else {
2017-12-20 03:23:24 +01:00
return nil
2017-09-19 16:36:23 +02:00
}
2017-12-20 03:23:24 +01:00
guard let attachment = TSAttachment . fetch ( uniqueId : attachmentId , transaction : transaction ) else {
2018-08-23 16:37:34 +02:00
Logger . warn ( " Missing attachment. Was it deleted? " )
2017-12-20 03:23:24 +01:00
return nil
}
return attachment
}
var hasMediaAttachment : Bool {
guard let attachment = self . attachment else {
return false
}
guard attachment . contentType != OWSMimeTypeOversizeTextMessage else {
// t o t h e u s e r , o v e r s i z e d t e x t a t t a c h m e n t s s h o u l d b e h a v e
// j u s t l i k e r e g u l a r t e x t m e s s a g e s .
return false
}
return true
}
2017-09-28 03:42:58 +02:00
private func addAttachmentMetadataRows ( ) -> [ UIView ] {
2017-12-20 03:23:24 +01:00
guard hasMediaAttachment else {
return [ ]
}
2017-09-28 03:42:58 +02:00
var rows = [ UIView ] ( )
if let attachment = self . attachment {
2017-10-26 20:40:29 +02:00
// O n l y s h o w M I M E t y p e s i n D E B U G b u i l d s .
if _isDebugAssertConfiguration ( ) {
let contentType = attachment . contentType
rows . append ( valueRow ( name : NSLocalizedString ( " MESSAGE_METADATA_VIEW_ATTACHMENT_MIME_TYPE " ,
comment : " Label for the MIME type of attachments in the 'message metadata' view. " ) ,
value : contentType ) )
}
2017-09-28 03:42:58 +02:00
if let sourceFilename = attachment . sourceFilename {
rows . append ( valueRow ( name : NSLocalizedString ( " MESSAGE_METADATA_VIEW_SOURCE_FILENAME " ,
comment : " Label for the original filename of any attachment in the 'message metadata' view. " ) ,
2017-10-17 06:05:29 +02:00
value : sourceFilename ) )
2017-09-28 03:42:58 +02:00
}
}
if let dataSource = self . dataSource {
let fileSize = dataSource . dataLength ( )
rows . append ( valueRow ( name : NSLocalizedString ( " MESSAGE_METADATA_VIEW_ATTACHMENT_FILE_SIZE " ,
comment : " Label for file size of attachments in the 'message metadata' view. " ) ,
2017-12-04 16:35:47 +01:00
value : OWSFormat . formatFileSize ( UInt ( fileSize ) ) ) )
2017-09-28 03:42:58 +02:00
}
return rows
}
2018-10-12 03:40:47 +02:00
private func buildUDAccessoryView ( text : String ) -> UIView {
let label = UILabel ( )
label . textColor = Theme . secondaryColor
label . text = text
label . textAlignment = . right
label . font = UIFont . ows_mediumFont ( withSize : 13 )
let image = # imageLiteral ( resourceName : " ic_secret_sender_indicator " ) . withRenderingMode ( . alwaysTemplate )
let imageView = UIImageView ( image : image )
imageView . tintColor = Theme . middleGrayColor
let hStack = UIStackView ( arrangedSubviews : [ imageView , label ] )
hStack . axis = . horizontal
hStack . spacing = 8
return hStack
}
2017-09-19 16:36:23 +02:00
private func nameLabel ( text : String ) -> UILabel {
let label = UILabel ( )
2018-08-08 15:37:23 +02:00
label . textColor = Theme . primaryColor
2017-10-17 06:05:29 +02:00
label . font = UIFont . ows_mediumFont ( withSize : 14 )
2017-09-19 16:36:23 +02:00
label . text = text
label . setContentHuggingHorizontalHigh ( )
return label
}
private func valueLabel ( text : String ) -> UILabel {
let label = UILabel ( )
2018-08-08 15:37:23 +02:00
label . textColor = Theme . primaryColor
2017-10-17 06:05:29 +02:00
label . font = UIFont . ows_regularFont ( withSize : 14 )
2017-09-19 16:36:23 +02:00
label . text = text
label . setContentHuggingHorizontalLow ( )
return label
}
2018-10-12 03:40:47 +02:00
private func valueRow ( name : String , value : String , subtitle : String = " " ) -> UIStackView {
2017-10-17 06:05:29 +02:00
let nameLabel = self . nameLabel ( text : name )
let valueLabel = self . valueLabel ( text : value )
2018-10-12 03:40:47 +02:00
let hStackView = UIStackView ( arrangedSubviews : [ nameLabel , valueLabel ] )
hStackView . axis = . horizontal
hStackView . spacing = 10
hStackView . layoutMargins = UIEdgeInsets ( top : 0 , left : 20 , bottom : 0 , right : 20 )
hStackView . isLayoutMarginsRelativeArrangement = true
2017-09-19 16:36:23 +02:00
2017-12-04 16:35:47 +01:00
if subtitle . count > 0 {
2017-10-17 06:05:29 +02:00
let subtitleLabel = self . valueLabel ( text : subtitle )
2018-08-08 15:37:23 +02:00
subtitleLabel . textColor = Theme . secondaryColor
2018-10-12 03:40:47 +02:00
hStackView . addArrangedSubview ( subtitleLabel )
2017-09-19 16:36:23 +02:00
}
2018-10-12 03:40:47 +02:00
return hStackView
2017-09-19 16:36:23 +02:00
}
// MARK: - A c t i o n s
2018-05-25 18:54:25 +02:00
@objc func shareButtonPressed ( ) {
2017-09-19 16:36:23 +02:00
guard let attachmentStream = attachmentStream else {
2018-08-23 16:37:34 +02:00
Logger . error ( " Share button should only be shown with attachment, but no attachment found. " )
2017-09-19 16:36:23 +02:00
return
}
2017-10-17 06:05:29 +02:00
AttachmentSharing . showShareUI ( forAttachment : attachmentStream )
2017-09-19 16:36:23 +02:00
}
2017-09-25 17:37:05 +02:00
// MARK: - A c t i o n s
2019-02-20 20:44:30 +01:00
enum DetailViewError : Error {
case messageWasDeleted
}
2017-09-25 17:37:05 +02:00
// T h i s m e t h o d s h o u l d b e c a l l e d a f t e r s e l f . d a t a b a s e C o n n e c t i o n . b e g i n L o n g L i v e d R e a d T r a n s a c t i o n ( ) .
2019-02-27 01:19:18 +01:00
private func updateMessageToLatest ( ) throws {
2017-09-25 17:37:05 +02:00
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2017-09-25 17:37:05 +02:00
2019-02-20 20:44:30 +01:00
try self . uiDatabaseConnection . read { transaction in
2017-11-15 19:15:48 +01:00
guard let uniqueId = self . message . uniqueId else {
2018-08-23 16:37:34 +02:00
Logger . error ( " Message is missing uniqueId. " )
2017-11-15 19:15:48 +01:00
return
}
guard let newMessage = TSInteraction . fetch ( uniqueId : uniqueId , transaction : transaction ) as ? TSMessage else {
2019-02-20 20:44:30 +01:00
Logger . error ( " Message was deleted " )
throw DetailViewError . messageWasDeleted
2017-09-25 17:37:05 +02:00
}
self . message = newMessage
2017-12-20 03:23:24 +01:00
self . attachment = self . fetchAttachment ( transaction : transaction )
2018-10-12 17:31:26 +02:00
self . attachmentStream = self . attachment as ? TSAttachmentStream
2017-09-25 17:37:05 +02:00
}
}
2019-02-20 20:44:30 +01:00
@objc internal func uiDatabaseDidUpdate ( notification : NSNotification ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2017-09-25 17:37:05 +02:00
2018-03-22 23:47:40 +01:00
guard ! wasDeleted else {
2019-02-20 20:44:30 +01:00
// I t e m w a s d e l e t e d i n t h e t i l e v i e w g a l l e r y .
// D o n ' t b o t h e r r e - r e n d e r i n g , i t w i l l f a i l a n d w e ' l l s o o n b e d i s m i s s e d .
2018-03-22 23:47:40 +01:00
return
}
2019-02-20 20:44:30 +01:00
guard let notifications = notification . userInfo ? [ OWSUIDatabaseConnectionNotificationsKey ] as ? [ Notification ] else {
owsFailDebug ( " notifications was unexpectedly nil " )
return
}
2017-09-25 17:37:05 +02:00
2017-11-15 19:15:48 +01:00
guard let uniqueId = self . message . uniqueId else {
2018-08-23 16:37:34 +02:00
Logger . error ( " Message is missing uniqueId. " )
2017-11-15 19:15:48 +01:00
return
}
2019-02-20 20:44:30 +01:00
2018-03-12 23:04:16 +01:00
guard self . uiDatabaseConnection . hasChange ( forKey : uniqueId ,
2017-10-17 06:05:29 +02:00
inCollection : TSInteraction . collection ( ) ,
in : notifications ) else {
2018-08-23 16:37:34 +02:00
Logger . debug ( " No relevant changes. " )
2017-09-27 22:52:31 +02:00
return
}
2019-02-20 20:44:30 +01:00
do {
2019-02-27 01:19:18 +01:00
try updateMessageToLatest ( )
2019-02-20 20:44:30 +01:00
} catch DetailViewError . messageWasDeleted {
DispatchQueue . main . async {
self . delegate ? . detailViewMessageWasDeleted ( self )
}
return
} catch {
owsFailDebug ( " unexpected error: \( error ) " )
}
2017-09-25 17:37:05 +02:00
updateContent ( )
}
2017-09-28 03:42:58 +02:00
2018-04-24 22:38:35 +02:00
private func string ( for messageReceiptStatus : MessageReceiptStatus ) -> String {
switch messageReceiptStatus {
2017-09-28 03:42:58 +02:00
case . uploading :
return NSLocalizedString ( " MESSAGE_METADATA_VIEW_MESSAGE_STATUS_UPLOADING " ,
comment : " Status label for messages which are uploading. " )
case . sending :
return NSLocalizedString ( " MESSAGE_METADATA_VIEW_MESSAGE_STATUS_SENDING " ,
comment : " Status label for messages which are sending. " )
case . sent :
return NSLocalizedString ( " MESSAGE_METADATA_VIEW_MESSAGE_STATUS_SENT " ,
comment : " Status label for messages which are sent. " )
case . delivered :
return NSLocalizedString ( " MESSAGE_METADATA_VIEW_MESSAGE_STATUS_DELIVERED " ,
comment : " Status label for messages which are delivered. " )
case . read :
return NSLocalizedString ( " MESSAGE_METADATA_VIEW_MESSAGE_STATUS_READ " ,
comment : " Status label for messages which are read. " )
case . failed :
return NSLocalizedString ( " MESSAGE_METADATA_VIEW_MESSAGE_STATUS_FAILED " ,
2018-04-23 16:30:51 +02:00
comment : " Status label for messages which are failed. " )
case . skipped :
return NSLocalizedString ( " MESSAGE_METADATA_VIEW_MESSAGE_STATUS_SKIPPED " ,
comment : " Status label for messages which were skipped. " )
2017-09-28 03:42:58 +02:00
}
}
2017-10-26 20:34:30 +02:00
2018-04-05 21:01:02 +02:00
// MARK: - M e s s a g e B u b b l e L a y o u t
private func updateMessageBubbleViewLayout ( ) {
guard let messageBubbleView = messageBubbleView else {
2017-11-02 17:41:24 +01:00
return
}
2018-04-05 21:01:02 +02:00
guard let messageBubbleViewWidthLayoutConstraint = messageBubbleViewWidthLayoutConstraint else {
2017-11-02 17:41:24 +01:00
return
}
2018-04-05 21:01:02 +02:00
guard let messageBubbleViewHeightLayoutConstraint = messageBubbleViewHeightLayoutConstraint else {
2017-10-26 20:34:30 +02:00
return
}
2018-04-05 21:01:02 +02:00
2018-06-22 19:48:23 +02:00
let messageBubbleSize = messageBubbleView . measureSize ( )
2018-04-05 21:01:02 +02:00
messageBubbleViewWidthLayoutConstraint . constant = messageBubbleSize . width
messageBubbleViewHeightLayoutConstraint . constant = messageBubbleSize . height
}
2018-04-11 17:31:34 +02:00
// MARK: O W S M e s s a g e B u b b l e V i e w D e l e g a t e
func didTapImageViewItem ( _ viewItem : ConversationViewItem , attachmentStream : TSAttachmentStream , imageView : UIView ) {
2019-02-20 20:44:30 +01:00
let mediaGallery = MediaGallery ( thread : self . thread )
2018-04-11 17:31:34 +02:00
2018-11-01 01:12:56 +01:00
mediaGallery . addDataSourceDelegate ( self )
2018-11-07 18:00:34 +01:00
mediaGallery . presentDetailView ( fromViewController : self , mediaAttachment : attachmentStream , replacingView : imageView )
2018-04-11 17:31:34 +02:00
}
2018-04-05 21:01:02 +02:00
2018-04-11 17:31:34 +02:00
func didTapVideoViewItem ( _ viewItem : ConversationViewItem , attachmentStream : TSAttachmentStream , imageView : UIView ) {
2019-02-20 20:44:30 +01:00
let mediaGallery = MediaGallery ( thread : self . thread )
2018-04-11 17:31:34 +02:00
2018-11-01 01:12:56 +01:00
mediaGallery . addDataSourceDelegate ( self )
2018-11-07 18:00:34 +01:00
mediaGallery . presentDetailView ( fromViewController : self , mediaAttachment : attachmentStream , replacingView : imageView )
2018-04-11 17:31:34 +02:00
}
var audioAttachmentPlayer : OWSAudioPlayer ?
func didTapAudioViewItem ( _ viewItem : ConversationViewItem , attachmentStream : TSAttachmentStream ) {
2018-08-22 19:44:22 +02:00
AssertIsOnMainThread ( )
2018-04-11 17:31:34 +02:00
2018-09-05 03:50:38 +02:00
guard let mediaURL = attachmentStream . originalMediaURL else {
2018-08-27 16:27:48 +02:00
owsFailDebug ( " mediaURL was unexpectedly nil for attachment: \( attachmentStream ) " )
2017-10-26 20:34:30 +02:00
return
}
2018-04-11 17:31:34 +02:00
guard FileManager . default . fileExists ( atPath : mediaURL . path ) else {
2018-08-27 16:27:48 +02:00
owsFailDebug ( " audio file missing at path: \( mediaURL ) " )
2017-10-26 20:34:30 +02:00
return
}
2018-04-11 17:31:34 +02:00
if let audioAttachmentPlayer = self . audioAttachmentPlayer {
// I s t h i s p l a y e r a s s o c i a t e d w i t h t h i s m e d i a a d a p t e r ?
2018-09-28 00:49:01 +02:00
if audioAttachmentPlayer . owner = = = viewItem {
2018-04-11 17:31:34 +02:00
// T a p t o p a u s e & u n p a u s e .
2018-10-23 16:40:09 +02:00
audioAttachmentPlayer . togglePlayState ( )
2018-04-05 21:01:02 +02:00
return
}
2018-04-11 17:31:34 +02:00
audioAttachmentPlayer . stop ( )
self . audioAttachmentPlayer = nil
2017-10-26 20:34:30 +02:00
}
2018-10-23 16:40:09 +02:00
let audioAttachmentPlayer = OWSAudioPlayer ( mediaUrl : mediaURL , audioBehavior : . audioMessagePlayback , delegate : viewItem )
2018-04-11 17:31:34 +02:00
self . audioAttachmentPlayer = audioAttachmentPlayer
// A s s o c i a t e t h e p l a y e r w i t h t h i s m e d i a a d a p t e r .
audioAttachmentPlayer . owner = viewItem
2018-10-23 16:40:09 +02:00
audioAttachmentPlayer . play ( )
2018-04-11 17:31:34 +02:00
}
2020-10-19 02:34:56 +02:00
func didPanAudioViewItem ( toCurrentTime currentTime : TimeInterval ) {
2020-10-19 02:21:04 +02:00
// TODO: I m p l e m e n t
}
2018-04-11 17:31:34 +02:00
func didTapTruncatedTextMessage ( _ conversationItem : ConversationViewItem ) {
guard let navigationController = self . navigationController else {
2018-08-27 16:27:48 +02:00
owsFailDebug ( " navigationController was unexpectedly nil " )
2018-04-11 17:31:34 +02:00
return
2018-04-05 21:01:02 +02:00
}
2018-04-11 17:31:34 +02:00
let viewController = LongTextViewController ( viewItem : viewItem )
2019-02-27 01:19:18 +01:00
viewController . delegate = self
2018-04-11 17:31:34 +02:00
navigationController . pushViewController ( viewController , animated : true )
}
2018-11-07 23:49:25 +01:00
func didTapFailedIncomingAttachment ( _ viewItem : ConversationViewItem ) {
2018-04-11 17:31:34 +02:00
// n o - o p
}
func didTapFailedOutgoingMessage ( _ message : TSOutgoingMessage ) {
// n o - o p
}
2018-04-16 19:50:54 +02:00
func didTapConversationItem ( _ viewItem : ConversationViewItem , quotedReply : OWSQuotedReplyModel ) {
// n o - o p
}
func didTapConversationItem ( _ viewItem : ConversationViewItem , quotedReply : OWSQuotedReplyModel , failedThumbnailDownloadAttachmentPointer attachmentPointer : TSAttachmentPointer ) {
2018-04-11 17:31:34 +02:00
// n o - o p
2017-10-26 20:34:30 +02:00
}
2019-01-18 16:23:07 +01:00
func didTapConversationItem ( _ viewItem : ConversationViewItem , linkPreview : OWSLinkPreview ) {
2019-01-23 15:56:46 +01:00
guard let urlString = linkPreview . urlString else {
owsFailDebug ( " Missing url. " )
return
}
guard let url = URL ( string : urlString ) else {
owsFailDebug ( " Invalid url: \( urlString ) . " )
return
}
UIApplication . shared . openURL ( url )
2019-01-18 16:23:07 +01:00
}
2018-08-22 22:32:44 +02:00
@objc func didLongPressSent ( sender : UIGestureRecognizer ) {
guard sender . state = = . began else {
return
}
2018-08-16 21:49:23 +02:00
let messageTimestamp = " \( message . timestamp ) "
UIPasteboard . general . string = messageTimestamp
}
2019-02-11 17:49:26 +01:00
var lastSearchedText : String ? {
return nil
}
2018-03-22 23:47:40 +01:00
// M e d i a G a l l e r y D a t a S o u r c e D e l e g a t e
2019-02-20 20:44:30 +01:00
func mediaGalleryDataSource ( _ mediaGalleryDataSource : MediaGalleryDataSource , willDelete items : [ MediaGalleryItem ] , initiatedBy : AnyObject ) {
2018-08-23 16:37:34 +02:00
Logger . info ( " " )
2018-03-23 15:00:07 +01:00
guard ( items . map ( { $0 . message } ) = = [ self . message ] ) else {
2018-03-22 23:47:40 +01:00
// S h o u l d o n l y b e o n e m e s s a g e w e c a n d e l e t e w h e n v i e w i n g m e s s a g e d e t a i l s
2018-08-27 16:27:48 +02:00
owsFailDebug ( " Unexpectedly informed of irrelevant message deletion " )
2018-03-22 23:47:40 +01:00
return
}
self . wasDeleted = true
}
func mediaGalleryDataSource ( _ mediaGalleryDataSource : MediaGalleryDataSource , deletedSections : IndexSet , deletedItems : [ IndexPath ] ) {
self . dismiss ( animated : true ) {
self . navigationController ? . popViewController ( animated : true )
}
}
2018-05-07 21:20:34 +02:00
// MARK: - C o n t a c t S h a r e V i e w H e l p e r D e l e g a t e
public func didCreateOrEditContact ( ) {
updateContent ( )
2018-05-15 00:25:17 +02:00
self . dismiss ( animated : true )
2018-05-07 21:20:34 +02:00
}
2017-09-19 16:36:23 +02:00
}
2019-02-27 01:19:18 +01:00
extension MessageDetailViewController : LongTextViewDelegate {
func longTextViewMessageWasDeleted ( _ longTextViewController : LongTextViewController ) {
self . delegate ? . detailViewMessageWasDeleted ( self )
}
}