2014-10-29 21:58:58 +01:00
//
// MessagesViewController . m
// Signal
//
// Created by Dylan Bourgeois on 28 / 10 / 14.
// Copyright ( c ) 2014 Open Whisper Systems . All rights reserved .
//
# import "AppDelegate.h"
2015-02-17 00:14:50 +01:00
# import < AddressBookUI / AddressBookUI . h >
2015-10-31 16:53:32 +01:00
# import < ContactsUI / CNContactViewController . h >
2015-05-23 15:54:50 +02:00
2014-10-29 21:58:58 +01:00
# import "MessagesViewController.h"
# import "FullImageViewController.h"
2014-12-04 00:23:36 +01:00
# import "FingerprintViewController.h"
2014-12-17 06:44:36 +01:00
# import "NewGroupViewController.h"
2014-12-24 02:25:10 +01:00
# import "ShowGroupMembersViewController.h"
# import "SignalKeyingStorage.h"
2014-10-29 21:58:58 +01:00
2014-11-21 14:38:37 +01:00
# import "JSQCallCollectionViewCell.h"
2014-11-29 19:54:33 +01:00
2014-11-24 21:51:43 +01:00
# import "UIUtil.h"
2015-01-14 22:30:01 +01:00
# import "DJWActionSheet+OWS.h"
2014-10-29 21:58:58 +01:00
# import < MobileCoreServices / UTCoreTypes . h >
2014-11-25 16:38:33 +01:00
# import "TSDatabaseView.h"
2015-02-10 17:26:12 +01:00
# import "UIButton+OWS.h"
2014-11-25 16:38:33 +01:00
# import < YapDatabase / YapDatabaseView . h >
2014-12-11 00:05:41 +01:00
2014-11-25 16:38:33 +01:00
# import "TSMessageAdapter.h"
2014-12-11 00:05:41 +01:00
# import "TSErrorMessage.h"
2014-12-31 21:30:20 +01:00
# import "TSInvalidIdentityKeyErrorMessage.h"
2014-12-06 17:45:42 +01:00
# import "TSIncomingMessage.h"
2015-02-19 01:04:32 +01:00
# import "TSAttachmentPointer.h"
2015-01-14 22:30:01 +01:00
# import "TSVideoAttachmentAdapter.h"
2014-11-25 16:38:33 +01:00
# import "TSMessagesManager+sendMessages.h"
2014-12-22 00:40:15 +01:00
# import "TSMessagesManager+attachments.h"
2014-11-25 16:38:33 +01:00
# import "NSDate+millisecondTimeStamp.h"
2014-11-29 19:54:33 +01:00
# import "Environment.h"
# import "PhoneManager.h"
# import "ContactsManager.h"
2014-12-26 21:17:43 +01:00
# import "PreferencesUtil.h"
2014-11-29 19:54:33 +01:00
2014-12-31 13:22:40 +01:00
# import "TSAdapterCacheManager.h"
# define kYapDatabaseRangeLength 50
# define kYapDatabaseRangeMaxLength 300
# define kYapDatabaseRangeMinLength 20
2015-01-27 21:17:49 +01:00
# define JSQ_TOOLBAR _ICON _HEIGHT 22
# define JSQ_TOOLBAR _ICON _WIDTH 22
# define JSQ_IMAGE _INSET 5
2014-12-31 13:22:40 +01:00
2014-11-25 16:38:33 +01:00
static NSTimeInterval const kTSMessageSentDateShowTimeInterval = 5 * 60 ;
2014-12-24 02:25:10 +01:00
static NSString * const kUpdateGroupSegueIdentifier = @ "updateGroupSegue" ;
static NSString * const kFingerprintSegueIdentifier = @ "fingerprintSegue" ;
static NSString * const kShowGroupMembersSegue = @ "showGroupMembersSegue" ;
2014-12-17 06:44:36 +01:00
2014-10-29 21:58:58 +01:00
typedef enum : NSUInteger {
kMediaTypePicture ,
kMediaTypeVideo ,
} kMediaTypes ;
@ interface MessagesViewController ( ) {
UIImage * tappedImage ;
BOOL isGroupConversation ;
2015-05-23 15:54:50 +02:00
UIView * _unreadContainer ;
UIImageView * _unreadBackground ;
UILabel * _unreadLabel ;
NSUInteger _unreadCount ;
2014-10-29 21:58:58 +01:00
}
2015-02-17 00:14:50 +01:00
@ property ( nonatomic , weak ) UIView * navView ;
2014-11-26 16:00:10 +01:00
@ property ( nonatomic , retain ) TSThread * thread ;
2014-12-06 17:45:42 +01:00
@ property ( nonatomic , strong ) YapDatabaseConnection * editingDatabaseConnection ;
@ property ( nonatomic , strong ) YapDatabaseConnection * uiDatabaseConnection ;
2014-11-25 16:38:33 +01:00
@ property ( nonatomic , strong ) YapDatabaseViewMappings * messageMappings ;
2014-12-06 17:45:42 +01:00
@ property ( nonatomic , retain ) JSQMessagesBubbleImage * outgoingBubbleImageData ;
@ property ( nonatomic , retain ) JSQMessagesBubbleImage * incomingBubbleImageData ;
2015-06-07 19:04:24 +02:00
@ property ( nonatomic , retain ) JSQMessagesBubbleImage * currentlyOutgoingBubbleImageData ;
2014-12-06 17:45:42 +01:00
@ property ( nonatomic , retain ) JSQMessagesBubbleImage * outgoingMessageFailedImageData ;
2015-01-22 05:08:12 +01:00
@ property ( nonatomic , strong ) NSTimer * audioPlayerPoller ;
2015-01-25 00:48:40 +01:00
@ property ( nonatomic , strong ) TSVideoAttachmentAdapter * currentMediaAdapter ;
2014-12-06 17:45:42 +01:00
@ property ( nonatomic , retain ) NSTimer * readTimer ;
2015-01-27 02:20:11 +01:00
@ property ( nonatomic , retain ) UIButton * messageButton ;
@ property ( nonatomic , retain ) UIButton * attachButton ;
2014-11-25 16:38:33 +01:00
2014-12-06 23:21:15 +01:00
@ property ( nonatomic , retain ) NSIndexPath * lastDeliveredMessageIndexPath ;
2015-01-27 21:17:49 +01:00
@ property ( nonatomic , retain ) UIGestureRecognizer * showFingerprintDisplay ;
@ property ( nonatomic , retain ) UITapGestureRecognizer * toggleContactPhoneDisplay ;
@ property ( nonatomic ) BOOL displayPhoneAsTitle ;
2014-12-06 23:21:15 +01:00
2014-12-31 13:22:40 +01:00
@ property NSUInteger page ;
2015-01-31 12:00:58 +01:00
@ property BOOL isVisible ;
2015-04-14 21:49:00 +02:00
@ property ( nonatomic ) BOOL composeOnOpen ;
2015-10-31 16:53:32 +01:00
@ property ( nonatomic ) BOOL peek ;
2015-01-31 12:00:58 +01:00
2014-10-29 21:58:58 +01:00
@ end
2015-05-23 15:54:50 +02:00
@ interface UINavigationItem ( ) {
UIView * backButtonView ;
}
@ end
2014-10-29 21:58:58 +01:00
@ implementation MessagesViewController
2015-03-19 01:59:44 +01:00
- ( void ) setupWithTSIdentifier : ( NSString * ) identifier {
2014-12-06 17:45:42 +01:00
[ self . editingDatabaseConnection readWriteWithBlock : ^ ( YapDatabaseReadWriteTransaction * transaction ) {
2014-12-24 02:25:10 +01:00
self . thread = [ TSContactThread getOrCreateThreadWithContactId : identifier transaction : transaction ] ;
2014-11-26 16:00:10 +01:00
} ] ;
}
2015-01-16 15:28:41 +01:00
- ( void ) setupWithTSGroup : ( TSGroupModel * ) model {
2014-12-17 06:44:36 +01:00
[ self . editingDatabaseConnection readWriteWithBlock : ^ ( YapDatabaseReadWriteTransaction * transaction ) {
2014-12-24 02:25:10 +01:00
self . thread = [ TSGroupThread getOrCreateThreadWithGroupModel : model transaction : transaction ] ;
2014-12-17 06:44:36 +01:00
} ] ;
2015-02-17 00:14:50 +01:00
TSOutgoingMessage * message = [ [ TSOutgoingMessage alloc ] initWithTimestamp : [ NSDate ows_millisecondTimeStamp ] inThread : self . thread messageBody : @ "" attachments : [ [ NSMutableArray alloc ] init ] ] ;
message . groupMetaMessage = TSGroupMessageNew ;
2015-02-28 17:49:46 +01:00
if ( model . groupImage ! = nil ) {
2015-02-17 00:14:50 +01:00
[ [ TSMessagesManager sharedManager ] sendAttachment : UIImagePNGRepresentation ( model . groupImage ) contentType : @ "image/png" inMessage : message thread : self . thread ] ;
}
else {
2015-09-01 19:22:08 +02:00
[ [ TSMessagesManager sharedManager ] sendMessage : message inThread : self . thread success : nil failure : nil ] ;
2015-02-17 00:14:50 +01:00
}
isGroupConversation = YES ;
2014-12-17 06:44:36 +01:00
}
2015-10-31 16:53:32 +01:00
- ( void ) peekSetup {
_peek = YES ;
[ self setComposeOnOpen : NO ] ;
}
- ( void ) popped {
_peek = NO ;
[ self hideInputIfNeeded ] ;
}
2015-04-14 21:49:00 +02:00
- ( void ) setComposeOnOpen : ( BOOL ) compose {
_composeOnOpen = compose ;
}
2015-03-19 01:59:44 +01:00
- ( void ) setupWithThread : ( TSThread * ) thread {
2014-11-26 16:00:10 +01:00
self . thread = thread ;
2014-12-17 06:44:36 +01:00
isGroupConversation = [ self . thread isKindOfClass : [ TSGroupThread class ] ] ;
2014-11-26 16:00:10 +01:00
}
2015-05-23 15:54:50 +02:00
- ( TSThread * ) thread {
return _thread ;
}
2015-01-14 22:30:01 +01:00
2015-03-01 00:04:39 +01:00
- ( void ) hideInputIfNeeded {
2015-10-31 16:53:32 +01:00
if ( _peek ) {
[ self inputToolbar ] . hidden = YES ;
return ;
}
2015-01-14 22:30:01 +01:00
if ( [ _thread isKindOfClass : [ TSGroupThread class ] ] && ! [ ( ( TSGroupThread * ) _thread ) . groupModel . groupMemberIds containsObject : [ SignalKeyingStorage . localNumber toE164 ] ] ) {
[ self inputToolbar ] . hidden = YES ; // user has requested they leave the group . further sends disallowed
2015-02-10 17:26:12 +01:00
self . navigationItem . rightBarButtonItem = nil ; // further group action disallowed
2015-10-31 16:53:32 +01:00
} else if ( ! [ self isTextSecureReachable ] ) {
2015-01-27 02:20:11 +01:00
[ self inputToolbar ] . hidden = YES ; // only RedPhone
2015-03-01 00:04:39 +01:00
} else {
2015-10-31 16:53:32 +01:00
[ self inputToolbar ] . hidden = NO ;
2015-03-01 00:04:39 +01:00
[ self loadDraftInCompose ] ;
2015-01-27 02:20:11 +01:00
}
2015-01-14 22:30:01 +01:00
}
2015-01-31 12:00:58 +01:00
2014-10-29 21:58:58 +01:00
- ( void ) viewDidLoad {
2015-01-22 05:08:12 +01:00
[ super viewDidLoad ] ;
2015-01-31 12:00:58 +01:00
_isVisible = NO ;
2015-01-14 22:30:01 +01:00
[ self . navigationController . navigationBar setTranslucent : NO ] ;
2015-01-27 21:17:49 +01:00
_showFingerprintDisplay = [ [ UILongPressGestureRecognizer alloc ] initWithTarget : self action : @ selector ( showFingerprint ) ] ;
_toggleContactPhoneDisplay = [ [ UITapGestureRecognizer alloc ] initWithTarget : self action : @ selector ( toggleContactPhone ) ] ;
_toggleContactPhoneDisplay . numberOfTapsRequired = 1 ;
2015-02-17 00:14:50 +01:00
2015-02-18 23:21:03 +01:00
_messageButton = [ UIButton ows_blueButtonWithTitle : NSLocalizedString ( @ "SEND_BUTTON_TITLE" , @ "" ) ] ;
2015-03-01 00:04:39 +01:00
_messageButton . enabled = FALSE ;
2015-03-20 14:41:00 +01:00
_messageButton . titleLabel . adjustsFontSizeToFitWidth = YES ;
2015-02-17 00:14:50 +01:00
2015-01-27 02:20:11 +01:00
_attachButton = [ [ UIButton alloc ] init ] ;
2015-01-27 21:17:49 +01:00
[ _attachButton setFrame : CGRectMake ( 0 , 0 , JSQ_TOOLBAR _ICON _WIDTH + JSQ_IMAGE _INSET * 2 , JSQ_TOOLBAR _ICON _HEIGHT + JSQ_IMAGE _INSET * 2 ) ] ;
_attachButton . imageEdgeInsets = UIEdgeInsetsMake ( JSQ_IMAGE _INSET , JSQ_IMAGE _INSET , JSQ_IMAGE _INSET , JSQ_IMAGE _INSET ) ;
[ _attachButton setImage : [ UIImage imageNamed : @ "btnAttachments--blue" ] forState : UIControlStateNormal ] ;
2015-02-17 00:14:50 +01:00
2014-12-06 17:45:42 +01:00
[ self markAllMessagesAsRead ] ;
2015-02-17 00:14:50 +01:00
2014-11-29 19:54:33 +01:00
[ self initializeBubbles ] ;
2015-01-14 22:30:01 +01:00
[ self initializeTextView ] ;
2014-12-06 17:45:42 +01:00
self . messageMappings = [ [ YapDatabaseViewMappings alloc ] initWithGroups : @ [ self . thread . uniqueId ]
view : TSMessageDatabaseViewExtensionName ] ;
2015-02-17 00:14:50 +01:00
2014-12-31 13:22:40 +01:00
self . page = 0 ;
2015-02-17 00:14:50 +01:00
2014-12-31 13:22:40 +01:00
[ self updateRangeOptionsForPage : self . page ] ;
2015-02-17 00:14:50 +01:00
2015-01-31 12:00:58 +01:00
[ self . uiDatabaseConnection beginLongLivedReadTransaction ] ;
2015-03-21 19:15:43 +01:00
[ self . uiDatabaseConnection asyncReadWithBlock : ^ ( YapDatabaseReadTransaction * transaction ) {
2014-11-25 16:38:33 +01:00
[ self . messageMappings updateWithTransaction : transaction ] ;
2015-03-21 19:15:43 +01:00
[ self . collectionView reloadData ] ;
2014-11-25 16:38:33 +01:00
} ] ;
2015-02-17 00:14:50 +01:00
2014-11-29 19:54:33 +01:00
[ self initializeCollectionViewLayout ] ;
2015-02-17 00:14:50 +01:00
2014-12-09 20:11:14 +01:00
self . senderId = ME_MESSAGE _IDENTIFIER
2014-11-29 19:54:33 +01:00
self . senderDisplayName = ME_MESSAGE _IDENTIFIER
2015-02-17 00:14:50 +01:00
2014-12-09 20:11:14 +01:00
[ [ NSNotificationCenter defaultCenter ] addObserver : self selector : @ selector ( startReadTimer )
name : UIApplicationWillEnterForegroundNotification object : nil ] ;
[ [ NSNotificationCenter defaultCenter ] addObserver : self selector : @ selector ( cancelReadTimer )
name : UIApplicationDidEnterBackgroundNotification object : nil ] ;
2015-02-06 10:45:32 +01:00
self . navigationController . interactivePopGestureRecognizer . delegate = self ; // Swipe back to inbox fix . See http : // stackoverflow . com / questions / 19054625 / changing - back - button - in - ios -7 - disables - swipe - to - navigate - back
2014-12-09 20:11:14 +01:00
}
2015-03-01 00:04:39 +01:00
- ( void ) initializeTextView {
2015-01-14 22:30:01 +01:00
[ self . inputToolbar . contentView . textView setFont : [ UIFont ows_regularFontWithSize : 17. f ] ] ;
2015-03-01 00:04:39 +01:00
self . inputToolbar . contentView . leftBarButtonItem = _attachButton ;
2015-02-17 00:14:50 +01:00
2015-02-10 17:26:12 +01:00
self . inputToolbar . contentView . rightBarButtonItem = _messageButton ;
2015-01-14 22:30:01 +01:00
}
2015-03-19 01:59:44 +01:00
- ( void ) viewWillAppear : ( BOOL ) animated {
2014-12-24 11:50:07 +01:00
[ super viewWillAppear : animated ] ;
2015-02-10 12:02:58 +01:00
[ self initializeToolbars ] ;
2015-01-31 12:00:58 +01:00
[ self . collectionView reloadData ] ;
2015-01-05 03:15:18 +01:00
NSInteger numberOfMessages = ( NSInteger ) [ self . messageMappings numberOfItemsInGroup : self . thread . uniqueId ] ;
2015-02-17 00:14:50 +01:00
2015-01-05 03:15:18 +01:00
if ( numberOfMessages > 0 ) {
NSIndexPath * lastCellIndexPath = [ NSIndexPath indexPathForRow : numberOfMessages -1 inSection : 0 ] ;
[ self . collectionView scrollToItemAtIndexPath : lastCellIndexPath atScrollPosition : UICollectionViewScrollPositionBottom animated : NO ] ;
}
2014-12-24 11:50:07 +01:00
}
2015-03-19 01:59:44 +01:00
- ( void ) startReadTimer {
2015-05-23 15:54:50 +02:00
self . readTimer = [ NSTimer scheduledTimerWithTimeInterval : 1 target : self selector : @ selector ( markAllMessagesAsRead ) userInfo : nil repeats : YES ] ;
2014-12-09 20:11:14 +01:00
}
2015-03-19 01:59:44 +01:00
- ( void ) cancelReadTimer {
2014-12-09 20:11:14 +01:00
[ self . readTimer invalidate ] ;
}
2015-03-19 01:59:44 +01:00
- ( void ) viewDidAppear : ( BOOL ) animated {
2014-12-09 20:11:14 +01:00
[ super viewDidAppear : animated ] ;
2015-04-14 21:49:00 +02:00
[ self markAllMessagesAsRead ] ;
2014-12-09 20:11:14 +01:00
[ self startReadTimer ] ;
2015-01-31 12:00:58 +01:00
_isVisible = YES ;
2015-02-10 12:02:58 +01:00
[ self initializeTitleLabelGestureRecognizer ] ;
2015-04-14 21:49:00 +02:00
2015-10-31 16:53:32 +01:00
[ self updateBackButtonAsync ] ;
2015-05-23 15:54:50 +02:00
2015-04-14 21:49:00 +02:00
if ( _composeOnOpen ) {
[ self popKeyBoard ] ;
}
2014-12-09 20:11:14 +01:00
}
2015-10-31 16:53:32 +01:00
- ( void ) updateBackButtonAsync {
dispatch_async ( dispatch_get _global _queue ( DISPATCH_QUEUE _PRIORITY _DEFAULT , 0 ) , ^ {
NSUInteger count = [ [ TSMessagesManager sharedManager ] unreadMessagesCountExcept : self . thread ] ;
dispatch_async ( dispatch_get _main _queue ( ) , ^ {
if ( self ) {
[ self setUnreadCount : count ] ;
}
} ) ;
} ) ;
2015-05-23 15:54:50 +02:00
}
2015-03-19 01:59:44 +01:00
- ( void ) viewWillDisappear : ( BOOL ) animated {
2015-01-14 22:30:01 +01:00
if ( [ self . navigationController . viewControllers indexOfObject : self ] = = NSNotFound ) {
// back button was pressed .
[ self . navController hideDropDown : self ] ;
}
[ super viewWillDisappear : animated ] ;
2015-05-23 15:54:50 +02:00
[ _unreadContainer removeFromSuperview ] ;
_unreadContainer = nil ;
2015-01-14 22:30:01 +01:00
[ _audioPlayerPoller invalidate ] ;
[ _audioPlayer stop ] ;
// reset all audio bars to 0
JSQMessagesCollectionView * collectionView = self . collectionView ;
NSInteger num_bubbles = [ self collectionView : collectionView numberOfItemsInSection : 0 ] ;
for ( NSInteger i = 0 ; i < num_bubbles ; i + + ) {
NSIndexPath * index_path = [ NSIndexPath indexPathForRow : i inSection : 0 ] ;
TSMessageAdapter * msgAdapter = [ collectionView . dataSource collectionView : collectionView messageDataForItemAtIndexPath : index_path ] ;
if ( msgAdapter . messageType = = TSIncomingMessageAdapter && msgAdapter . isMediaMessage && [ msgAdapter isKindOfClass : [ TSVideoAttachmentAdapter class ] ] ) {
TSVideoAttachmentAdapter * msgMedia = ( TSVideoAttachmentAdapter * ) [ msgAdapter media ] ;
if ( [ msgMedia isAudio ] ) {
msgMedia . isPaused = NO ;
msgMedia . isAudioPlaying = NO ;
[ msgMedia setAudioProgressFromFloat : 0 ] ;
[ msgMedia setAudioIconToPlay ] ;
}
}
}
2015-01-22 05:08:12 +01:00
2014-12-09 20:11:14 +01:00
[ self cancelReadTimer ] ;
2015-02-17 00:14:50 +01:00
[ self removeTitleLabelGestureRecognizer ] ;
2015-03-01 00:04:39 +01:00
[ self saveDraft ] ;
2014-10-29 21:58:58 +01:00
}
2015-03-19 01:59:44 +01:00
- ( void ) viewDidDisappear : ( BOOL ) animated {
2015-01-31 12:00:58 +01:00
_isVisible = NO ;
}
2014-10-29 21:58:58 +01:00
- ( void ) didReceiveMemoryWarning {
[ super didReceiveMemoryWarning ] ;
}
2014-11-29 19:54:33 +01:00
# pragma mark - Initiliazers
2015-01-14 22:30:01 +01:00
- ( IBAction ) didSelectShow : ( id ) sender {
2015-01-27 02:20:11 +01:00
if ( isGroupConversation ) {
UIBarButtonItem * spaceEdge = [ [ UIBarButtonItem alloc ] initWithBarButtonSystemItem : UIBarButtonSystemItemFixedSpace target : nil action : nil ] ;
2015-02-17 00:14:50 +01:00
2015-01-27 02:20:11 +01:00
spaceEdge . width = 40 ;
2015-02-17 00:14:50 +01:00
2015-01-27 02:20:11 +01:00
UIBarButtonItem * spaceMiddleIcons = [ [ UIBarButtonItem alloc ] initWithBarButtonSystemItem : UIBarButtonSystemItemFixedSpace target : nil action : nil ] ;
spaceMiddleIcons . width = 61 ;
2015-02-17 00:14:50 +01:00
2015-01-27 02:20:11 +01:00
UIBarButtonItem * spaceMiddleWords = [ [ UIBarButtonItem alloc ] initWithBarButtonSystemItem : UIBarButtonSystemItemFlexibleSpace target : nil action : nil ] ;
2015-02-17 00:14:50 +01:00
2015-01-30 04:31:35 +01:00
NSDictionary * buttonTextAttributes = @ { NSFontAttributeName : [ UIFont ows_regularFontWithSize : 15.0 f ] ,
NSForegroundColorAttributeName : [ UIColor ows_materialBlueColor ] } ;
2015-02-17 00:14:50 +01:00
2015-01-30 04:31:35 +01:00
UIButton * groupUpdateButton = [ [ UIButton alloc ] initWithFrame : CGRectMake ( 0 , 0 , 65 , 24 ) ] ;
2015-02-18 23:21:03 +01:00
NSMutableAttributedString * updateTitle = [ [ NSMutableAttributedString alloc ] initWithString : NSLocalizedString ( @ "UPDATE_BUTTON_TITLE" , @ "" ) ] ;
2015-01-30 04:31:35 +01:00
[ updateTitle setAttributes : buttonTextAttributes range : NSMakeRange ( 0 , [ updateTitle length ] ) ] ;
[ groupUpdateButton setAttributedTitle : updateTitle forState : UIControlStateNormal ] ;
[ groupUpdateButton addTarget : self action : @ selector ( updateGroup ) forControlEvents : UIControlEventTouchUpInside ] ;
[ groupUpdateButton . titleLabel setTextAlignment : NSTextAlignmentCenter ] ;
2015-03-21 15:19:42 +01:00
[ groupUpdateButton . titleLabel setAdjustsFontSizeToFitWidth : YES ] ;
2015-01-30 04:31:35 +01:00
UIBarButtonItem * groupUpdateBarButton = [ [ UIBarButtonItem alloc ] initWithTitle : @ "" style : UIBarButtonItemStylePlain target : self action : nil ] ;
groupUpdateBarButton . customView = groupUpdateButton ;
groupUpdateBarButton . customView . userInteractionEnabled = YES ;
UIButton * groupLeaveButton = [ [ UIButton alloc ] initWithFrame : CGRectMake ( 0 , 0 , 50 , 24 ) ] ;
2015-02-18 23:21:03 +01:00
NSMutableAttributedString * leaveTitle = [ [ NSMutableAttributedString alloc ] initWithString : NSLocalizedString ( @ "LEAVE_BUTTON_TITLE" , @ "" ) ] ;
2015-01-30 04:31:35 +01:00
[ leaveTitle setAttributes : buttonTextAttributes range : NSMakeRange ( 0 , [ leaveTitle length ] ) ] ;
[ groupLeaveButton setAttributedTitle : leaveTitle forState : UIControlStateNormal ] ;
[ groupLeaveButton addTarget : self action : @ selector ( leaveGroup ) forControlEvents : UIControlEventTouchUpInside ] ;
[ groupLeaveButton . titleLabel setTextAlignment : NSTextAlignmentCenter ] ;
UIBarButtonItem * groupLeaveBarButton = [ [ UIBarButtonItem alloc ] initWithTitle : @ "" style : UIBarButtonItemStylePlain target : self action : nil ] ;
groupLeaveBarButton . customView = groupLeaveButton ;
groupLeaveBarButton . customView . userInteractionEnabled = YES ;
2015-03-21 15:19:42 +01:00
[ groupLeaveButton . titleLabel setAdjustsFontSizeToFitWidth : YES ] ;
2015-01-30 04:31:35 +01:00
UIButton * groupMembersButton = [ [ UIButton alloc ] initWithFrame : CGRectMake ( 0 , 0 , 65 , 24 ) ] ;
2015-02-18 23:21:03 +01:00
NSMutableAttributedString * membersTitle = [ [ NSMutableAttributedString alloc ] initWithString : NSLocalizedString ( @ "MEMBERS_BUTTON_TITLE" , @ "" ) ] ;
2015-01-30 04:31:35 +01:00
[ membersTitle setAttributes : buttonTextAttributes range : NSMakeRange ( 0 , [ membersTitle length ] ) ] ;
[ groupMembersButton setAttributedTitle : membersTitle forState : UIControlStateNormal ] ;
[ groupMembersButton addTarget : self action : @ selector ( showGroupMembers ) forControlEvents : UIControlEventTouchUpInside ] ;
[ groupMembersButton . titleLabel setTextAlignment : NSTextAlignmentCenter ] ;
UIBarButtonItem * groupMembersBarButton = [ [ UIBarButtonItem alloc ] initWithTitle : @ "" style : UIBarButtonItemStylePlain target : self action : nil ] ;
groupMembersBarButton . customView = groupMembersButton ;
groupMembersBarButton . customView . userInteractionEnabled = YES ;
2015-03-21 15:19:42 +01:00
[ groupMembersButton . titleLabel setAdjustsFontSizeToFitWidth : YES ] ;
2015-01-30 04:31:35 +01:00
2015-02-17 00:14:50 +01:00
2015-01-30 04:31:35 +01:00
self . navController . dropDownToolbar . items = @ [ spaceEdge , groupUpdateBarButton , spaceMiddleWords , groupLeaveBarButton , spaceMiddleWords , groupMembersBarButton , spaceEdge ] ;
2015-02-17 00:14:50 +01:00
2015-01-27 02:20:11 +01:00
for ( UIButton * button in self . navController . dropDownToolbar . items ) {
[ button setTintColor : [ UIColor ows_materialBlueColor ] ] ;
}
if ( self . navController . isDropDownVisible ) {
[ self . navController hideDropDown : sender ] ;
}
else {
[ self . navController showDropDown : sender ] ;
}
// Can also toggle toolbar from current state
// [ self . navController toggleToolbar : sender ] ;
[ self setNavigationTitle ] ;
2015-01-14 22:30:01 +01:00
}
}
- ( void ) setNavigationTitle {
2015-02-17 00:14:50 +01:00
NSString * navTitle = self . thread . name ;
2015-01-27 02:20:11 +01:00
if ( isGroupConversation && [ navTitle length ] = = 0 ) {
2015-02-18 23:21:03 +01:00
navTitle = NSLocalizedString ( @ "NEW_GROUP_DEFAULT_TITLE" , @ "" ) ;
2015-01-27 02:20:11 +01:00
}
2015-01-14 22:30:01 +01:00
self . navController . activeNavigationBarTitle = nil ;
self . title = navTitle ;
}
- ( void ) initializeToolbars {
2015-01-27 21:17:49 +01:00
2015-01-14 22:30:01 +01:00
self . navController = ( APNavigationController * ) self . navigationController ;
2015-02-17 00:14:50 +01:00
2015-02-21 12:46:44 +01:00
if ( [ self canCall ] ) {
2015-02-10 12:02:58 +01:00
self . navigationItem . rightBarButtonItem = [ [ UIBarButtonItem alloc ] initWithImage : [ [ UIImage imageNamed : @ "btnPhone--white" ] imageWithRenderingMode : UIImageRenderingModeAlwaysOriginal ] style : UIBarButtonItemStylePlain target : self action : @ selector ( callAction ) ] ;
2015-02-17 00:14:50 +01:00
self . navigationItem . rightBarButtonItem . imageInsets = UIEdgeInsetsMake ( 0 , -10 , 0 , 10 ) ;
2015-02-21 12:46:44 +01:00
} else if ( ! _thread . isGroupThread ) {
self . navigationItem . rightBarButtonItem = nil ;
2015-01-27 02:20:11 +01:00
}
2015-02-10 12:02:58 +01:00
[ self hideInputIfNeeded ] ;
[ self setNavigationTitle ] ;
}
- ( void ) initializeTitleLabelGestureRecognizer {
if ( isGroupConversation ) {
return ;
}
2015-01-27 21:17:49 +01:00
2015-02-10 12:02:58 +01:00
for ( UIView * view in self . navigationController . navigationBar . subviews ) {
if ( [ view isKindOfClass : NSClassFromString ( @ "UINavigationItemView" ) ] ) {
2015-02-17 00:14:50 +01:00
self . navView = view ;
for ( UIView * aView in self . navView . subviews ) {
2015-02-10 12:02:58 +01:00
if ( [ aView isKindOfClass : [ UILabel class ] ] ) {
2015-02-17 00:14:50 +01:00
UILabel * label = ( UILabel * ) aView ;
if ( [ label . text isEqualToString : self . title ] ) {
[ self . navView setUserInteractionEnabled : YES ] ;
[ aView setUserInteractionEnabled : YES ] ;
[ aView addGestureRecognizer : _showFingerprintDisplay ] ;
[ aView addGestureRecognizer : _toggleContactPhoneDisplay ] ;
return ;
}
2015-02-10 12:02:58 +01:00
}
2015-02-17 00:14:50 +01:00
}
}
}
}
- ( void ) removeTitleLabelGestureRecognizer {
if ( isGroupConversation ) {
return ;
}
for ( UIView * aView in self . navView . subviews ) {
if ( [ aView isKindOfClass : [ UILabel class ] ] ) {
UILabel * label = ( UILabel * ) aView ;
if ( [ label . text isEqualToString : self . title ] ) {
[ self . navView setUserInteractionEnabled : NO ] ;
[ aView setUserInteractionEnabled : NO ] ;
[ aView removeGestureRecognizer : _showFingerprintDisplay ] ;
[ aView removeGestureRecognizer : _toggleContactPhoneDisplay ] ;
2015-02-10 12:02:58 +01:00
return ;
}
}
}
2014-11-29 19:54:33 +01:00
}
- ( void ) initializeBubbles
{
JSQMessagesBubbleImageFactory * bubbleFactory = [ [ JSQMessagesBubbleImageFactory alloc ] init ] ;
2015-06-07 19:04:24 +02:00
self . outgoingBubbleImageData = [ bubbleFactory outgoingMessagesBubbleImageWithColor : [ UIColor ows_materialBlueColor ] ] ;
self . incomingBubbleImageData = [ bubbleFactory incomingMessagesBubbleImageWithColor : [ UIColor jsq_messageBubbleLightGrayColor ] ] ;
self . currentlyOutgoingBubbleImageData = [ bubbleFactory outgoingMessageFailedBubbleImageWithColor : [ UIColor ows_fadedBlueColor ] ] ;
2015-02-17 00:14:50 +01:00
2015-06-07 19:04:24 +02:00
self . outgoingMessageFailedImageData = [ bubbleFactory outgoingMessageFailedBubbleImageWithColor : [ UIColor grayColor ] ] ;
2014-11-29 19:54:33 +01:00
}
- ( void ) initializeCollectionViewLayout
{
2014-12-09 22:18:39 +01:00
if ( self . collectionView ) {
2015-01-14 22:30:01 +01:00
[ self . collectionView . collectionViewLayout setMessageBubbleFont : [ UIFont ows_regularFontWithSize : 15.0 f ] ] ;
2015-02-17 00:14:50 +01:00
2014-12-09 22:18:39 +01:00
self . collectionView . showsVerticalScrollIndicator = NO ;
self . collectionView . showsHorizontalScrollIndicator = NO ;
2015-02-17 00:14:50 +01:00
2014-12-31 13:22:40 +01:00
[ self updateLoadEarlierVisible ] ;
2015-02-17 00:14:50 +01:00
2014-12-09 22:18:39 +01:00
self . collectionView . collectionViewLayout . incomingAvatarViewSize = CGSizeZero ;
self . collectionView . collectionViewLayout . outgoingAvatarViewSize = CGSizeZero ;
}
2014-11-29 19:54:33 +01:00
}
2014-10-29 21:58:58 +01:00
# pragma mark - Fingerprints
- ( void ) showFingerprint
{
2014-12-06 17:45:42 +01:00
[ self markAllMessagesAsRead ] ;
2014-12-17 06:44:36 +01:00
[ self performSegueWithIdentifier : kFingerprintSegueIdentifier sender : self ] ;
2014-10-29 21:58:58 +01:00
}
2014-12-04 00:23:36 +01:00
2015-01-27 21:17:49 +01:00
- ( void ) toggleContactPhone {
_displayPhoneAsTitle = ! _displayPhoneAsTitle ;
2015-02-17 00:14:50 +01:00
if ( ! _thread . isGroupThread ) {
Contact * contact = [ [ [ Environment getCurrent ] contactsManager ] latestContactForPhoneNumber : [ self phoneNumberForThread ] ] ;
if ( ! contact ) {
2015-10-31 16:53:32 +01:00
if ( ! ( SYSTEM_VERSION _GREATER _THAN _OR _EQUAL _TO ( NSFoundationVersionNumber_iOS _9 ) ) ) {
ABUnknownPersonViewController * view = [ [ ABUnknownPersonViewController alloc ] init ] ;
ABRecordRef aContact = ABPersonCreate ( ) ;
CFErrorRef anError = NULL ;
ABMultiValueRef phone = ABMultiValueCreateMutable ( kABMultiStringPropertyType ) ;
ABMultiValueAddValueAndLabel ( phone , ( __bridge CFTypeRef ) [ self phoneNumberForThread ] . toE164 , kABPersonPhoneMainLabel , NULL ) ;
ABRecordSetValue ( aContact , kABPersonPhoneProperty , phone , & anError ) ;
CFRelease ( phone ) ;
if ( ! anError && aContact ) {
view . displayedPerson = aContact ; // Assume person is already defined .
view . allowsAddingToAddressBook = YES ;
[ self . navigationController pushViewController : view animated : YES ] ;
}
} else {
CNContactStore * contactStore = [ Environment getCurrent ] . contactsManager . contactStore ;
CNMutableContact * cncontact = [ [ CNMutableContact alloc ] init ] ;
cncontact . phoneNumbers = @ [ [ CNLabeledValue labeledValueWithLabel : nil value : [ CNPhoneNumber phoneNumberWithStringValue : [ self phoneNumberForThread ] . toE164 ] ] ] ;
CNContactViewController * controller = [ CNContactViewController viewControllerForUnknownContact : cncontact ] ;
controller . allowsActions = NO ;
controller . allowsEditing = YES ;
controller . contactStore = contactStore ;
[ self . navigationController pushViewController : controller animated : YES ] ;
// The "Add to existing contacts" is known to be destroying the view controller stack on iOS 9 http : // stackoverflow . com / questions / 32973254 / cncontactviewcontroller - forunknowncontact - unusable - destroys - interface
// Warning the user
UIAlertController * alertController = [ UIAlertController alertControllerWithTitle : @ "iOS 9 Bug" message : @ "iOS 9 introduced a bug that prevents us from adding this number to an existing contact from the app. You can still create a new contact for this number or copy-paste it into an existing contact sheet." preferredStyle : UIAlertControllerStyleAlert ] ;
[ alertController addAction : [ UIAlertAction actionWithTitle : @ "Continue" style : UIAlertActionStyleCancel handler : nil ] ] ;
[ alertController addAction : [ UIAlertAction actionWithTitle : @ "Copy number" style : UIAlertActionStyleDefault handler : ^ ( UIAlertAction * _Nonnull action ) {
UIPasteboard * pasteboard = [ UIPasteboard generalPasteboard ] ;
pasteboard . string = [ self phoneNumberForThread ] . toE164 ;
[ controller . navigationController popViewControllerAnimated : YES ] ;
} ] ] ;
[ controller presentViewController : alertController animated : YES completion : nil ] ;
2015-02-17 00:14:50 +01:00
}
}
}
2015-01-27 21:17:49 +01:00
if ( _displayPhoneAsTitle ) {
self . title = [ PhoneNumber bestEffortFormatPartialUserSpecifiedTextToLookLikeAPhoneNumber : [ [ self phoneNumberForThread ] toE164 ] ] ;
}
else {
[ self setNavigationTitle ] ;
}
}
2014-12-24 02:25:10 +01:00
- ( void ) showGroupMembers {
2015-01-14 22:30:01 +01:00
[ self . navController hideDropDown : self ] ;
2014-12-24 02:25:10 +01:00
[ self performSegueWithIdentifier : kShowGroupMembersSegue sender : self ] ;
}
2014-11-29 19:54:33 +01:00
# pragma mark - Calls
2015-11-29 01:14:49 +01:00
- ( SignalRecipient * ) signalRecipient {
__block SignalRecipient * recipient ;
[ self . editingDatabaseConnection readWithBlock : ^ ( YapDatabaseReadTransaction * transaction ) {
recipient = [ SignalRecipient recipientWithTextSecureIdentifier : [ self phoneNumberForThread ] . toE164 withTransaction : transaction ] ;
} ] ;
return recipient ;
}
2014-11-29 19:54:33 +01:00
- ( BOOL ) isRedPhoneReachable
{
2015-11-29 01:14:49 +01:00
return [ self signalRecipient ] . supportsVoice ;
2014-11-29 19:54:33 +01:00
}
2015-01-27 02:20:11 +01:00
- ( BOOL ) isTextSecureReachable {
if ( isGroupConversation ) {
return YES ;
}
else {
2015-11-29 01:14:49 +01:00
return [ self signalRecipient ] ;
2015-01-27 02:20:11 +01:00
}
}
2014-11-29 19:54:33 +01:00
- ( PhoneNumber * ) phoneNumberForThread
{
NSString * contactId = [ ( TSContactThread * ) self . thread contactIdentifier ] ;
2014-12-29 11:43:11 +01:00
return [ PhoneNumber tryParsePhoneNumberFromUserSpecifiedText : contactId ] ;
2014-11-29 19:54:33 +01:00
}
- ( void ) callAction
{
if ( [ self isRedPhoneReachable ] ) {
2014-12-29 11:43:11 +01:00
PhoneNumber * number = [ self phoneNumberForThread ] ;
Contact * contact = [ [ Environment . getCurrent contactsManager ] latestContactForPhoneNumber : number ] ;
[ Environment . phoneManager initiateOutgoingCallToContact : contact atRemoteNumber : number ] ;
2014-11-29 19:54:33 +01:00
} else {
DDLogWarn ( @ "Tried to initiate a call but contact has no RedPhone identifier" ) ;
}
}
2015-01-27 21:17:49 +01:00
- ( BOOL ) canCall {
return ! isGroupConversation && [ self isRedPhoneReachable ] && ! [ ( ( TSContactThread * ) _thread ) . contactIdentifier isEqualToString : [ SignalKeyingStorage . localNumber toE164 ] ] ;
}
2015-02-17 00:14:50 +01:00
2015-01-27 02:20:11 +01:00
- ( void ) textViewDidChange : ( UITextView * ) textView {
if ( [ textView . text length ] > 0 ) {
self . inputToolbar . contentView . rightBarButtonItem . enabled = YES ;
}
else {
self . inputToolbar . contentView . rightBarButtonItem . enabled = NO ;
}
2015-02-17 00:14:50 +01:00
2015-01-27 02:20:11 +01:00
}
2014-10-29 21:58:58 +01:00
# pragma mark - JSQMessagesViewController method overrides
- ( void ) didPressSendButton : ( UIButton * ) button
withMessageText : ( NSString * ) text
senderId : ( NSString * ) senderId
senderDisplayName : ( NSString * ) senderDisplayName
date : ( NSDate * ) date
{
2014-12-04 11:27:45 +01:00
if ( text . length > 0 ) {
2014-10-29 21:58:58 +01:00
[ JSQSystemSoundPlayer jsq_playMessageSentSound ] ;
2015-02-17 00:14:50 +01:00
2014-12-21 12:52:42 +01:00
TSOutgoingMessage * message = [ [ TSOutgoingMessage alloc ] initWithTimestamp : [ NSDate ows_millisecondTimeStamp ] inThread : self . thread messageBody : text attachments : nil ] ;
2015-02-17 00:14:50 +01:00
2015-09-01 19:22:08 +02:00
[ [ TSMessagesManager sharedManager ] sendMessage : message inThread : self . thread success : nil failure : nil ] ;
2014-10-29 21:58:58 +01:00
[ self finishSendingMessage ] ;
}
}
# pragma mark - JSQMessages CollectionView DataSource
- ( id < JSQMessageData > ) collectionView : ( JSQMessagesCollectionView * ) collectionView messageDataForItemAtIndexPath : ( NSIndexPath * ) indexPath
{
2014-11-25 21:33:29 +01:00
return [ self messageAtIndexPath : indexPath ] ;
2014-10-29 21:58:58 +01:00
}
- ( id < JSQMessageBubbleImageDataSource > ) collectionView : ( JSQMessagesCollectionView * ) collectionView messageBubbleImageDataForItemAtIndexPath : ( NSIndexPath * ) indexPath
{
2014-11-25 21:33:29 +01:00
id < JSQMessageData > message = [ self messageAtIndexPath : indexPath ] ;
2015-02-17 00:14:50 +01:00
2014-10-29 21:58:58 +01:00
if ( [ message . senderId isEqualToString : self . senderId ] ) {
2015-06-07 19:04:24 +02:00
switch ( message . messageState ) {
case TSOutgoingMessageStateUnsent :
return self . outgoingMessageFailedImageData ;
case TSOutgoingMessageStateAttemptingOut :
return self . currentlyOutgoingBubbleImageData ;
default :
return self . outgoingBubbleImageData ;
2014-12-09 22:18:39 +01:00
}
2014-10-29 21:58:58 +01:00
}
2015-02-17 00:14:50 +01:00
2014-11-25 16:38:33 +01:00
return self . incomingBubbleImageData ;
2014-10-29 21:58:58 +01:00
}
- ( id < JSQMessageAvatarImageDataSource > ) collectionView : ( JSQMessagesCollectionView * ) collectionView avatarImageDataForItemAtIndexPath : ( NSIndexPath * ) indexPath
{
return nil ;
}
# pragma mark - UICollectionView DataSource
- ( UICollectionViewCell * ) collectionView : ( JSQMessagesCollectionView * ) collectionView cellForItemAtIndexPath : ( NSIndexPath * ) indexPath
{
2014-11-29 19:54:33 +01:00
TSMessageAdapter * msg = [ self messageAtIndexPath : indexPath ] ;
2015-02-17 00:14:50 +01:00
2014-11-29 19:54:33 +01:00
switch ( msg . messageType ) {
case TSIncomingMessageAdapter :
return [ self loadIncomingMessageCellForMessage : msg atIndexPath : indexPath ] ;
case TSOutgoingMessageAdapter :
return [ self loadOutgoingCellForMessage : msg atIndexPath : indexPath ] ;
case TSCallAdapter :
return [ self loadCallCellForCall : msg atIndexPath : indexPath ] ;
case TSInfoMessageAdapter :
return [ self loadInfoMessageCellForMessage : msg atIndexPath : indexPath ] ;
case TSErrorMessageAdapter :
return [ self loadErrorMessageCellForMessage : msg atIndexPath : indexPath ] ;
2015-02-17 00:14:50 +01:00
2014-11-29 19:54:33 +01:00
default :
2015-08-14 00:19:29 +02:00
DDLogError ( @ "Something went wrong" ) ;
2014-11-29 19:54:33 +01:00
return nil ;
}
}
# pragma mark - Loading message cells
- ( JSQMessagesCollectionViewCell * ) loadIncomingMessageCellForMessage : ( id < JSQMessageData > ) message atIndexPath : ( NSIndexPath * ) indexPath
{
JSQMessagesCollectionViewCell * cell = ( JSQMessagesCollectionViewCell * ) [ super collectionView : self . collectionView cellForItemAtIndexPath : indexPath ] ;
2014-12-06 17:45:42 +01:00
if ( ! message . isMediaMessage ) {
2015-01-14 22:30:01 +01:00
cell . textView . textColor = [ UIColor ows_blackColor ] ;
2014-11-25 16:38:33 +01:00
cell . textView . linkTextAttributes = @ { NSForegroundColorAttributeName : cell . textView . textColor ,
2015-06-07 17:43:47 +02:00
NSUnderlineStyleAttributeName : @ ( NSUnderlineStyleSingle | NSUnderlinePatternSolid ) } ;
2014-10-29 21:58:58 +01:00
}
2015-02-17 00:14:50 +01:00
2014-11-25 16:38:33 +01:00
return cell ;
2014-11-29 19:54:33 +01:00
}
- ( JSQMessagesCollectionViewCell * ) loadOutgoingCellForMessage : ( id < JSQMessageData > ) message atIndexPath : ( NSIndexPath * ) indexPath
{
JSQMessagesCollectionViewCell * cell = ( JSQMessagesCollectionViewCell * ) [ super collectionView : self . collectionView cellForItemAtIndexPath : indexPath ] ;
if ( ! message . isMediaMessage )
{
2014-12-26 23:18:54 +01:00
cell . textView . textColor = [ UIColor whiteColor ] ;
2014-11-29 19:54:33 +01:00
cell . textView . linkTextAttributes = @ { NSForegroundColorAttributeName : cell . textView . textColor ,
NSUnderlineStyleAttributeName : @ ( NSUnderlineStyleSingle | NSUnderlinePatternSolid ) } ;
}
2015-02-17 00:14:50 +01:00
2014-11-29 19:54:33 +01:00
return cell ;
}
- ( JSQCallCollectionViewCell * ) loadCallCellForCall : ( id < JSQMessageData > ) call atIndexPath : ( NSIndexPath * ) indexPath
{
JSQCallCollectionViewCell * cell = ( JSQCallCollectionViewCell * ) [ super collectionView : self . collectionView cellForItemAtIndexPath : indexPath ] ;
return cell ;
}
- ( JSQDisplayedMessageCollectionViewCell * ) loadInfoMessageCellForMessage : ( id < JSQMessageData > ) message atIndexPath : ( NSIndexPath * ) indexPath
{
JSQDisplayedMessageCollectionViewCell * cell = ( JSQDisplayedMessageCollectionViewCell * ) [ super collectionView : self . collectionView cellForItemAtIndexPath : indexPath ] ;
return cell ;
}
- ( JSQDisplayedMessageCollectionViewCell * ) loadErrorMessageCellForMessage : ( id < JSQMessageData > ) message atIndexPath : ( NSIndexPath * ) indexPath
{
JSQDisplayedMessageCollectionViewCell * cell = ( JSQDisplayedMessageCollectionViewCell * ) [ super collectionView : self . collectionView cellForItemAtIndexPath : indexPath ] ;
return cell ;
2014-10-29 21:58:58 +01:00
}
# pragma mark - Adjusting cell label heights
- ( CGFloat ) collectionView : ( JSQMessagesCollectionView * ) collectionView
layout : ( JSQMessagesCollectionViewFlowLayout * ) collectionViewLayout heightForCellTopLabelAtIndexPath : ( NSIndexPath * ) indexPath
{
2014-11-25 16:38:33 +01:00
if ( [ self showDateAtIndexPath : indexPath ] ) {
2014-10-29 21:58:58 +01:00
return kJSQMessagesCollectionViewCellLabelHeightDefault ;
}
2015-02-17 00:14:50 +01:00
2014-10-29 21:58:58 +01:00
return 0.0 f ;
}
2014-11-25 16:38:33 +01:00
- ( BOOL ) showDateAtIndexPath : ( NSIndexPath * ) indexPath
2014-10-29 21:58:58 +01:00
{
2014-11-25 16:38:33 +01:00
BOOL showDate = NO ;
if ( indexPath . row = = 0 ) {
showDate = YES ;
2014-10-29 21:58:58 +01:00
}
2014-11-25 16:38:33 +01:00
else {
2014-11-25 21:33:29 +01:00
TSMessageAdapter * currentMessage = [ self messageAtIndexPath : indexPath ] ;
2015-02-17 00:14:50 +01:00
2014-11-25 21:33:29 +01:00
TSMessageAdapter * previousMessage = [ self messageAtIndexPath : [ NSIndexPath indexPathForItem : indexPath . row -1 inSection : indexPath . section ] ] ;
2015-02-17 00:14:50 +01:00
2014-11-25 16:38:33 +01:00
NSTimeInterval timeDifference = [ currentMessage . date timeIntervalSinceDate : previousMessage . date ] ;
if ( timeDifference > kTSMessageSentDateShowTimeInterval ) {
showDate = YES ;
2014-10-29 21:58:58 +01:00
}
}
2014-11-25 16:38:33 +01:00
return showDate ;
2014-10-29 21:58:58 +01:00
}
2014-11-29 19:54:33 +01:00
- ( NSAttributedString * ) collectionView : ( JSQMessagesCollectionView * ) collectionView attributedTextForCellTopLabelAtIndexPath : ( NSIndexPath * ) indexPath
{
2015-02-17 00:14:50 +01:00
2014-12-17 06:44:36 +01:00
if ( [ self showDateAtIndexPath : indexPath ] ) {
TSMessageAdapter * currentMessage = [ self messageAtIndexPath : indexPath ] ;
2015-02-17 00:14:50 +01:00
2014-12-17 06:44:36 +01:00
return [ [ JSQMessagesTimestampFormatter sharedFormatter ] attributedTimestampForDate : currentMessage . date ] ;
2014-11-29 19:54:33 +01:00
}
2015-02-17 00:14:50 +01:00
2014-11-29 19:54:33 +01:00
return nil ;
}
- ( BOOL ) shouldShowMessageStatusAtIndexPath : ( NSIndexPath * ) indexPath
{
2014-12-17 06:44:36 +01:00
TSMessageAdapter * currentMessage = [ self messageAtIndexPath : indexPath ] ;
2015-06-07 19:04:24 +02:00
// If message failed , say that message should be tapped to retry ;
if ( currentMessage . messageType = = TSOutgoingMessageAdapter
&& currentMessage . messageState = = TSOutgoingMessageStateUnsent ) {
return YES ;
}
2014-12-17 06:44:36 +01:00
if ( [ self . thread isKindOfClass : [ TSGroupThread class ] ] ) {
return currentMessage . messageType = = TSIncomingMessageAdapter ;
2015-06-07 19:04:24 +02:00
} else {
2014-12-17 06:44:36 +01:00
if ( indexPath . item = = [ self . collectionView numberOfItemsInSection : indexPath . section ] -1 ) {
return [ self isMessageOutgoingAndDelivered : currentMessage ] ;
}
2015-02-17 00:14:50 +01:00
2014-12-17 06:44:36 +01:00
if ( ! [ self isMessageOutgoingAndDelivered : currentMessage ] ) {
return NO ;
}
2015-02-17 00:14:50 +01:00
2014-12-17 06:44:36 +01:00
TSMessageAdapter * nextMessage = [ self nextOutgoingMessage : indexPath ] ;
return ! [ self isMessageOutgoingAndDelivered : nextMessage ] ;
2014-11-29 19:54:33 +01:00
}
2014-12-04 15:01:05 +01:00
}
- ( TSMessageAdapter * ) nextOutgoingMessage : ( NSIndexPath * ) indexPath
{
2014-11-29 19:54:33 +01:00
TSMessageAdapter * nextMessage = [ self messageAtIndexPath : [ NSIndexPath indexPathForRow : indexPath . row + 1 inSection : indexPath . section ] ] ;
2014-12-04 15:01:05 +01:00
int i = 1 ;
2015-02-17 00:14:50 +01:00
2014-12-04 15:01:05 +01:00
while ( indexPath . item + i < [ self . collectionView numberOfItemsInSection : indexPath . section ] -1 && ! [ self isMessageOutgoingAndDelivered : nextMessage ] ) {
i + + ;
nextMessage = [ self messageAtIndexPath : [ NSIndexPath indexPathForRow : indexPath . row + i inSection : indexPath . section ] ] ;
}
2015-02-17 00:14:50 +01:00
2014-12-04 15:01:05 +01:00
return nextMessage ;
2014-11-29 19:54:33 +01:00
}
- ( BOOL ) isMessageOutgoingAndDelivered : ( TSMessageAdapter * ) message
{
return message . messageType = = TSOutgoingMessageAdapter && message . messageState = = TSOutgoingMessageStateDelivered ;
}
2014-12-17 06:44:36 +01:00
- ( NSAttributedString * ) collectionView : ( JSQMessagesCollectionView * ) collectionView attributedTextForCellBottomLabelAtIndexPath : ( NSIndexPath * ) indexPath {
TSMessageAdapter * msg = [ self messageAtIndexPath : indexPath ] ;
2015-06-07 19:04:24 +02:00
NSTextAttachment * textAttachment = [ [ NSTextAttachment alloc ] init ] ;
textAttachment . bounds = CGRectMake ( 0 , 0 , 11.0 f , 10.0 f ) ;
2014-12-17 06:44:36 +01:00
if ( [ self shouldShowMessageStatusAtIndexPath : indexPath ] ) {
2015-06-07 19:04:24 +02:00
if ( msg . messageType = = TSOutgoingMessageAdapter
&& msg . messageState = = TSOutgoingMessageStateUnsent ) {
NSMutableAttributedString * attrStr = [ [ NSMutableAttributedString alloc ] initWithString : NSLocalizedString ( @ "FAILED_SENDING_TEXT" , nil ) ] ;
[ attrStr appendAttributedString : [ NSAttributedString attributedStringWithAttachment : textAttachment ] ] ;
return attrStr ;
}
2014-12-17 06:44:36 +01:00
if ( [ self . thread isKindOfClass : [ TSGroupThread class ] ] ) {
NSString * name = [ [ Environment getCurrent ] . contactsManager nameStringForPhoneIdentifier : msg . senderId ] ;
name = name ? name : msg . senderId ;
2015-06-19 14:31:50 +02:00
if ( ! name ) {
name = @ "" ;
}
2015-06-07 19:04:24 +02:00
NSMutableAttributedString * attrStr = [ [ NSMutableAttributedString alloc ] initWithString : name ] ;
2014-12-17 06:44:36 +01:00
[ attrStr appendAttributedString : [ NSAttributedString attributedStringWithAttachment : textAttachment ] ] ;
2015-02-17 00:14:50 +01:00
2015-06-07 19:04:24 +02:00
return attrStr ;
2014-12-17 06:44:36 +01:00
}
else {
_lastDeliveredMessageIndexPath = indexPath ;
2015-06-07 19:04:24 +02:00
NSMutableAttributedString * attrStr = [ [ NSMutableAttributedString alloc ] initWithString : NSLocalizedString ( @ "DELIVERED_MESSAGE_TEXT" , @ "" ) ] ;
2014-12-17 06:44:36 +01:00
[ attrStr appendAttributedString : [ NSAttributedString attributedStringWithAttachment : textAttachment ] ] ;
2015-02-17 00:14:50 +01:00
2015-06-07 19:04:24 +02:00
return attrStr ;
2014-12-17 06:44:36 +01:00
}
2014-11-29 19:54:33 +01:00
}
return nil ;
}
2014-10-29 21:58:58 +01:00
- ( CGFloat ) collectionView : ( JSQMessagesCollectionView * ) collectionView
layout : ( JSQMessagesCollectionViewFlowLayout * ) collectionViewLayout heightForCellBottomLabelAtIndexPath : ( NSIndexPath * ) indexPath
{
2015-03-19 01:59:44 +01:00
if ( [ self shouldShowMessageStatusAtIndexPath : indexPath ] ) {
2014-11-29 19:54:33 +01:00
return 16.0 f ;
}
2015-02-17 00:14:50 +01:00
2014-11-29 19:54:33 +01:00
return 0.0 f ;
2014-10-29 21:58:58 +01:00
}
# pragma mark - Actions
- ( void ) collectionView : ( JSQMessagesCollectionView * ) collectionView didTapMessageBubbleAtIndexPath : ( NSIndexPath * ) indexPath
{
2014-12-11 00:05:41 +01:00
TSMessageAdapter * messageItem = [ collectionView . dataSource collectionView : collectionView messageDataForItemAtIndexPath : indexPath ] ;
TSInteraction * interaction = [ self interactionAtIndexPath : indexPath ] ;
2015-02-17 00:14:50 +01:00
2014-12-11 00:05:41 +01:00
switch ( messageItem . messageType ) {
case TSOutgoingMessageAdapter :
if ( messageItem . messageState = = TSOutgoingMessageStateUnsent ) {
[ self handleUnsentMessageTap : ( TSOutgoingMessage * ) interaction ] ;
}
case TSIncomingMessageAdapter : {
2015-02-17 00:14:50 +01:00
2014-12-11 00:05:41 +01:00
BOOL isMediaMessage = [ messageItem isMediaMessage ] ;
2015-02-17 00:14:50 +01:00
2014-12-11 00:05:41 +01:00
if ( isMediaMessage ) {
2015-06-07 17:43:47 +02:00
if ( [ [ messageItem media ] isKindOfClass : [ TSPhotoAdapter class ] ] ) {
TSPhotoAdapter * messageMedia = ( TSPhotoAdapter * ) [ messageItem media ] ;
2015-02-17 00:14:50 +01:00
2015-01-14 22:30:01 +01:00
if ( [ messageMedia isImage ] ) {
tappedImage = ( ( UIImageView * ) [ messageMedia mediaView ] ) . image ;
CGRect convertedRect = [ self . collectionView convertRect : [ collectionView cellForItemAtIndexPath : indexPath ] . frame toView : nil ] ;
__block TSAttachment * attachment = nil ;
[ self . uiDatabaseConnection readWithBlock : ^ ( YapDatabaseReadTransaction * transaction ) {
attachment = [ TSAttachment fetchObjectWithUniqueID : messageMedia . attachmentId transaction : transaction ] ;
} ] ;
2015-02-17 00:14:50 +01:00
2015-01-14 22:30:01 +01:00
if ( [ attachment isKindOfClass : [ TSAttachmentStream class ] ] ) {
TSAttachmentStream * attStream = ( TSAttachmentStream * ) attachment ;
FullImageViewController * vc = [ [ FullImageViewController alloc ] initWithAttachment : attStream fromRect : convertedRect forInteraction : [ self interactionAtIndexPath : indexPath ] ] ;
2015-02-17 00:14:50 +01:00
2015-01-25 02:40:51 +01:00
[ vc presentFromViewController : self . navigationController ] ;
2015-01-14 22:30:01 +01:00
}
} else {
DDLogWarn ( @ "Currently unsupported" ) ;
}
}
else if ( [ [ messageItem media ] isKindOfClass : [ TSVideoAttachmentAdapter class ] ] ) {
// fileurl disappeared should look up in db as before . will do refactor
// full screen , check this setup with a . mov
TSVideoAttachmentAdapter * messageMedia = ( TSVideoAttachmentAdapter * ) [ messageItem media ] ;
2015-01-25 00:48:40 +01:00
_currentMediaAdapter = messageMedia ;
2014-12-26 23:18:54 +01:00
__block TSAttachment * attachment = nil ;
[ self . uiDatabaseConnection readWithBlock : ^ ( YapDatabaseReadTransaction * transaction ) {
attachment = [ TSAttachment fetchObjectWithUniqueID : messageMedia . attachmentId transaction : transaction ] ;
} ] ;
2015-02-17 00:14:50 +01:00
2014-12-26 23:18:54 +01:00
if ( [ attachment isKindOfClass : [ TSAttachmentStream class ] ] ) {
TSAttachmentStream * attStream = ( TSAttachmentStream * ) attachment ;
2015-01-14 22:30:01 +01:00
NSFileManager * fileManager = [ NSFileManager defaultManager ] ;
if ( [ messageMedia isVideo ] ) {
2015-01-22 05:08:12 +01:00
if ( [ fileManager fileExistsAtPath : [ attStream . mediaURL path ] ] ) {
2015-01-30 23:28:05 +01:00
[ self dismissKeyBoard ] ;
2015-01-22 05:08:12 +01:00
_videoPlayer = [ [ MPMoviePlayerController alloc ] initWithContentURL : attStream . mediaURL ] ;
2015-01-14 22:30:01 +01:00
[ _videoPlayer prepareToPlay ] ;
2015-02-17 00:14:50 +01:00
2015-01-14 22:30:01 +01:00
[ [ NSNotificationCenter defaultCenter ] addObserver : self
selector : @ selector ( moviePlayBackDidFinish : )
name : MPMoviePlayerPlaybackDidFinishNotification
object : _videoPlayer ] ;
2015-02-17 00:14:50 +01:00
_videoPlayer . controlStyle = MPMovieControlStyleDefault ;
_videoPlayer . shouldAutoplay = YES ;
2015-01-14 22:30:01 +01:00
[ self . view addSubview : _videoPlayer . view ] ;
[ _videoPlayer setFullscreen : YES animated : YES ] ;
}
2015-01-22 05:08:12 +01:00
} else if ( [ messageMedia isAudio ] ) {
if ( messageMedia . isAudioPlaying ) {
// if you had started playing an audio msg and now you ' re tapping it to pause
messageMedia . isAudioPlaying = NO ;
[ _audioPlayer pause ] ;
messageMedia . isPaused = YES ;
[ _audioPlayerPoller invalidate ] ;
double current = [ _audioPlayer currentTime ] / [ _audioPlayer duration ] ;
[ messageMedia setAudioProgressFromFloat : ( float ) current ] ;
[ messageMedia setAudioIconToPlay ] ;
} else {
BOOL isResuming = NO ;
[ _audioPlayerPoller invalidate ] ;
2015-01-25 00:48:40 +01:00
2015-01-22 05:08:12 +01:00
// loop through all the other bubbles and set their isPlaying to false
NSInteger num_bubbles = [ self collectionView : collectionView numberOfItemsInSection : 0 ] ;
for ( NSInteger i = 0 ; i < num_bubbles ; i + + ) {
NSIndexPath * index_path = [ NSIndexPath indexPathForRow : i inSection : 0 ] ;
TSMessageAdapter * msgAdapter = [ collectionView . dataSource collectionView : collectionView messageDataForItemAtIndexPath : index_path ] ;
if ( msgAdapter . messageType = = TSIncomingMessageAdapter && msgAdapter . isMediaMessage ) {
TSVideoAttachmentAdapter * msgMedia = ( TSVideoAttachmentAdapter * ) [ msgAdapter media ] ;
if ( [ msgMedia isAudio ] ) {
if ( msgMedia = = messageMedia && messageMedia . isPaused ) {
isResuming = YES ;
} else {
msgMedia . isAudioPlaying = NO ;
msgMedia . isPaused = NO ;
[ msgMedia setAudioIconToPlay ] ;
[ msgMedia setAudioProgressFromFloat : 0 ] ;
2015-01-25 00:48:40 +01:00
[ msgMedia resetAudioDuration ] ;
2015-01-22 05:08:12 +01:00
}
}
}
}
2015-02-17 00:14:50 +01:00
2015-01-22 05:08:12 +01:00
if ( isResuming ) {
// if you had paused an audio msg and now you ' re tapping to resume
[ _audioPlayer prepareToPlay ] ;
[ _audioPlayer play ] ;
[ messageMedia setAudioIconToPause ] ;
messageMedia . isAudioPlaying = YES ;
messageMedia . isPaused = NO ;
2015-01-25 00:48:40 +01:00
_audioPlayerPoller = [ NSTimer scheduledTimerWithTimeInterval : .05 target : self selector : @ selector ( audioPlayerUpdated : ) userInfo : @ { @ "adapter" : messageMedia } repeats : YES ] ;
2015-01-22 05:08:12 +01:00
} else {
// if you are tapping an audio msg for the first time to play
messageMedia . isAudioPlaying = YES ;
NSError * error ;
_audioPlayer = [ [ AVAudioPlayer alloc ] initWithContentsOfURL : attStream . mediaURL error : & error ] ;
2015-01-25 00:48:40 +01:00
if ( error ) {
2015-08-14 00:19:29 +02:00
DDLogError ( @ "error: %@" , error ) ;
2015-01-25 00:48:40 +01:00
}
2015-01-22 05:08:12 +01:00
[ _audioPlayer prepareToPlay ] ;
[ _audioPlayer play ] ;
[ messageMedia setAudioIconToPause ] ;
_audioPlayer . delegate = self ;
2015-01-25 00:48:40 +01:00
_audioPlayerPoller = [ NSTimer scheduledTimerWithTimeInterval : .05 target : self selector : @ selector ( audioPlayerUpdated : ) userInfo : @ { @ "adapter" : messageMedia } repeats : YES ] ;
2015-01-22 05:08:12 +01:00
}
}
2015-01-14 22:30:01 +01:00
}
2014-12-26 23:18:54 +01:00
}
2014-12-11 00:05:41 +01:00
}
}
2014-12-31 21:30:20 +01:00
}
break ;
2014-12-11 00:05:41 +01:00
case TSErrorMessageAdapter :
[ self handleErrorMessageTap : ( TSErrorMessage * ) interaction ] ;
break ;
case TSInfoMessageAdapter :
2015-02-19 01:04:32 +01:00
[ self handleWarningTap : interaction ] ;
2014-12-11 00:05:41 +01:00
break ;
2014-12-31 21:30:20 +01:00
case TSCallAdapter :
break ;
2014-12-11 00:05:41 +01:00
default :
break ;
2014-11-29 19:54:33 +01:00
}
2014-12-11 00:05:41 +01:00
}
2015-02-19 01:04:32 +01:00
- ( void ) handleWarningTap : ( TSInteraction * ) interaction {
if ( [ interaction isKindOfClass : [ TSIncomingMessage class ] ] ) {
TSIncomingMessage * message = ( TSIncomingMessage * ) interaction ;
for ( NSString * attachmentId in message . attachments ) {
__block TSAttachment * attachment ;
[ self . editingDatabaseConnection readWithBlock : ^ ( YapDatabaseReadTransaction * transaction ) {
attachment = [ TSAttachment fetchObjectWithUniqueID : attachmentId transaction : transaction ] ;
} ] ;
if ( [ attachment isKindOfClass : [ TSAttachmentPointer class ] ] ) {
TSAttachmentPointer * pointer = ( TSAttachmentPointer * ) attachment ;
if ( ! pointer . isDownloading ) {
[ [ TSMessagesManager sharedManager ] retrieveAttachment : pointer messageId : message . uniqueId ] ;
}
}
}
}
}
2015-01-29 04:50:41 +01:00
2015-01-14 22:30:01 +01:00
- ( void ) moviePlayBackDidFinish : ( id ) sender {
DDLogDebug ( @ "playback finished" ) ;
}
2014-12-31 13:22:40 +01:00
- ( void ) collectionView : ( JSQMessagesCollectionView * ) collectionView header : ( JSQMessagesLoadEarlierHeaderView * ) headerView didTapLoadEarlierMessagesButton : ( UIButton * ) sender
{
if ( [ self shouldShowLoadEarlierMessages ] ) {
self . page + + ;
}
2015-02-17 00:14:50 +01:00
2014-12-31 13:22:40 +01:00
NSInteger item = ( NSInteger ) [ self scrollToItem ] ;
2015-02-17 00:14:50 +01:00
2014-12-31 13:22:40 +01:00
[ self updateRangeOptionsForPage : self . page ] ;
2015-02-17 00:14:50 +01:00
2014-12-31 13:22:40 +01:00
[ self . uiDatabaseConnection readWithBlock : ^ ( YapDatabaseReadTransaction * transaction ) {
[ self . messageMappings updateWithTransaction : transaction ] ;
} ] ;
2015-02-17 00:14:50 +01:00
2014-12-31 13:22:40 +01:00
[ self updateLayoutForEarlierMessagesWithOffset : item ] ;
2015-02-17 00:14:50 +01:00
2014-12-31 13:22:40 +01:00
}
- ( BOOL ) shouldShowLoadEarlierMessages
{
__block BOOL show = YES ;
2015-02-17 00:14:50 +01:00
2014-12-31 13:22:40 +01:00
[ self . uiDatabaseConnection readWithBlock : ^ ( YapDatabaseReadTransaction * transaction ) {
show = [ self . messageMappings numberOfItemsInGroup : self . thread . uniqueId ] < [ [ transaction ext : TSMessageDatabaseViewExtensionName ] numberOfItemsInGroup : self . thread . uniqueId ] ;
} ] ;
2015-02-17 00:14:50 +01:00
2014-12-31 13:22:40 +01:00
return show ;
}
- ( NSUInteger ) scrollToItem
{
__block NSUInteger item = kYapDatabaseRangeLength * ( self . page + 1 ) - [ self . messageMappings numberOfItemsInGroup : self . thread . uniqueId ] ;
2015-02-17 00:14:50 +01:00
2014-12-31 13:22:40 +01:00
[ self . uiDatabaseConnection readWithBlock : ^ ( YapDatabaseReadTransaction * transaction ) {
2015-02-17 00:14:50 +01:00
2014-12-31 13:22:40 +01:00
NSUInteger numberOfVisibleMessages = [ self . messageMappings numberOfItemsInGroup : self . thread . uniqueId ] ;
NSUInteger numberOfTotalMessages = [ [ transaction ext : TSMessageDatabaseViewExtensionName ] numberOfItemsInGroup : self . thread . uniqueId ] ;
NSUInteger numberOfMessagesToLoad = numberOfTotalMessages - numberOfVisibleMessages ;
2015-02-17 00:14:50 +01:00
2014-12-31 13:22:40 +01:00
BOOL canLoadFullRange = numberOfMessagesToLoad >= kYapDatabaseRangeLength ;
2015-02-17 00:14:50 +01:00
2014-12-31 13:22:40 +01:00
if ( ! canLoadFullRange ) {
item = numberOfMessagesToLoad ;
}
} ] ;
2015-02-17 00:14:50 +01:00
2014-12-31 13:22:40 +01:00
return item = = 0 ? item : item - 1 ;
}
- ( void ) updateLoadEarlierVisible
{
[ self setShowLoadEarlierMessagesHeader : [ self shouldShowLoadEarlierMessages ] ] ;
}
- ( void ) updateLayoutForEarlierMessagesWithOffset : ( NSInteger ) offset
{
[ self . collectionView . collectionViewLayout invalidateLayoutWithContext : [ JSQMessagesCollectionViewFlowLayoutInvalidationContext context ] ] ;
[ self . collectionView reloadData ] ;
2015-02-17 00:14:50 +01:00
2014-12-31 13:22:40 +01:00
[ self . collectionView scrollToItemAtIndexPath : [ NSIndexPath indexPathForItem : offset inSection : 0 ] atScrollPosition : UICollectionViewScrollPositionTop animated : NO ] ;
2015-02-17 00:14:50 +01:00
2014-12-31 13:22:40 +01:00
[ self updateLoadEarlierVisible ] ;
}
- ( void ) updateRangeOptionsForPage : ( NSUInteger ) page
{
YapDatabaseViewRangeOptions * rangeOptions = [ YapDatabaseViewRangeOptions flexibleRangeWithLength : kYapDatabaseRangeLength * ( page + 1 ) offset : 0 from : YapDatabaseViewEnd ] ;
2015-02-17 00:14:50 +01:00
2014-12-31 13:22:40 +01:00
rangeOptions . maxLength = kYapDatabaseRangeMaxLength ;
rangeOptions . minLength = kYapDatabaseRangeMinLength ;
2015-02-17 00:14:50 +01:00
2014-12-31 13:22:40 +01:00
[ self . messageMappings setRangeOptions : rangeOptions forGroup : self . thread . uniqueId ] ;
2015-02-17 00:14:50 +01:00
2014-12-31 13:22:40 +01:00
}
2014-12-11 00:05:41 +01:00
# pragma mark Bubble User Actions
- ( void ) handleUnsentMessageTap : ( TSOutgoingMessage * ) message {
2015-01-30 23:28:05 +01:00
[ self dismissKeyBoard ] ;
2015-02-18 23:21:03 +01:00
[ DJWActionSheet showInView : self . parentViewController . view withTitle : nil cancelButtonTitle : NSLocalizedString ( @ "TXT_CANCEL_TITLE" , @ "" ) destructiveButtonTitle : NSLocalizedString ( @ "TXT_DELETE_TITLE" , @ "" ) otherButtonTitles : @ [ NSLocalizedString ( @ "SEND_AGAIN_BUTTON" , @ "" ) ] tapBlock : ^ ( DJWActionSheet * actionSheet , NSInteger tappedButtonIndex ) {
2014-12-11 00:05:41 +01:00
if ( tappedButtonIndex = = actionSheet . cancelButtonIndex ) {
2015-08-14 00:19:29 +02:00
DDLogDebug ( @ "User Cancelled" ) ;
2014-12-11 00:05:41 +01:00
} else if ( tappedButtonIndex = = actionSheet . destructiveButtonIndex ) {
[ self . editingDatabaseConnection readWriteWithBlock : ^ ( YapDatabaseReadWriteTransaction * transaction ) {
[ message removeWithTransaction : transaction ] ;
} ] ;
} else {
2015-09-01 19:22:08 +02:00
[ [ TSMessagesManager sharedManager ] sendMessage : message inThread : self . thread success : nil failure : nil ] ;
2014-12-11 00:05:41 +01:00
[ self finishSendingMessage ] ;
}
} ] ;
}
2014-12-26 23:18:54 +01:00
- ( void ) deleteMessageAtIndexPath : ( NSIndexPath * ) indexPath {
[ self . editingDatabaseConnection readWriteWithBlock : ^ ( YapDatabaseReadWriteTransaction * transaction ) {
TSInteraction * interaction = [ self interactionAtIndexPath : indexPath ] ;
2015-01-22 10:49:19 +01:00
[ [ TSAdapterCacheManager sharedManager ] clearCacheEntryForInteractionId : interaction . uniqueId ] ;
2014-12-26 23:18:54 +01:00
[ interaction removeWithTransaction : transaction ] ;
} ] ;
}
2015-01-14 22:30:01 +01:00
- ( void ) handleErrorMessageTap : ( TSErrorMessage * ) message {
2014-12-31 21:30:20 +01:00
if ( [ message isKindOfClass : [ TSInvalidIdentityKeyErrorMessage class ] ] ) {
TSInvalidIdentityKeyErrorMessage * errorMessage = ( TSInvalidIdentityKeyErrorMessage * ) message ;
NSString * newKeyFingerprint = [ errorMessage newIdentityKey ] ;
2015-06-07 22:37:19 +02:00
NSString * keyOwner ;
if ( [ message isKindOfClass : [ TSInvalidIdentityKeySendingErrorMessage class ] ] ) {
TSInvalidIdentityKeySendingErrorMessage * m = ( TSInvalidIdentityKeySendingErrorMessage * ) message ;
keyOwner = [ [ [ Environment getCurrent ] contactsManager ] nameStringForPhoneIdentifier : m . recipientId ] ;
} else {
keyOwner = [ self . thread name ] ;
}
NSString * messageString = [ NSString stringWithFormat : NSLocalizedString ( @ "ACCEPT_IDENTITYKEY_QUESTION" , @ "" ) , keyOwner , newKeyFingerprint ] ;
2015-02-18 23:21:03 +01:00
NSArray * actions = @ [ NSLocalizedString ( @ "ACCEPT_IDENTITYKEY_BUTTON" , @ "" ) , NSLocalizedString ( @ "COPY_IDENTITYKEY_BUTTON" , @ "" ) ] ;
2015-02-17 00:14:50 +01:00
2015-01-30 23:28:05 +01:00
[ self dismissKeyBoard ] ;
2015-01-05 03:15:18 +01:00
2015-02-18 23:21:03 +01:00
[ DJWActionSheet showInView : self . parentViewController . view withTitle : messageString cancelButtonTitle : NSLocalizedString ( @ "TXT_CANCEL_TITLE" , @ "" ) destructiveButtonTitle : NSLocalizedString ( @ "TXT_DELETE_TITLE" , @ "" ) otherButtonTitles : actions tapBlock : ^ ( DJWActionSheet * actionSheet , NSInteger tappedButtonIndex ) {
2014-11-29 19:54:33 +01:00
if ( tappedButtonIndex = = actionSheet . cancelButtonIndex ) {
2015-08-14 00:19:29 +02:00
DDLogDebug ( @ "User Cancelled" ) ;
2014-11-29 19:54:33 +01:00
} else if ( tappedButtonIndex = = actionSheet . destructiveButtonIndex ) {
2014-12-11 00:05:41 +01:00
[ self . editingDatabaseConnection readWriteWithBlock : ^ ( YapDatabaseReadWriteTransaction * transaction ) {
2014-11-29 19:54:33 +01:00
[ message removeWithTransaction : transaction ] ;
} ] ;
2014-12-11 00:05:41 +01:00
} else {
2014-11-29 19:54:33 +01:00
switch ( tappedButtonIndex ) {
case 0 :
2014-12-31 21:30:20 +01:00
[ errorMessage acceptNewIdentityKey ] ;
2014-12-11 00:05:41 +01:00
break ;
case 1 :
[ [ UIPasteboard generalPasteboard ] setString : newKeyFingerprint ] ;
2014-11-29 19:54:33 +01:00
break ;
default :
break ;
}
}
} ] ;
2014-10-29 21:58:58 +01:00
}
}
# pragma mark - Navigation
- ( void ) prepareForSegue : ( UIStoryboardSegue * ) segue sender : ( id ) sender {
2015-02-17 00:14:50 +01:00
2014-12-17 06:44:36 +01:00
if ( [ segue . identifier isEqualToString : kFingerprintSegueIdentifier ] ) {
2014-12-04 00:23:36 +01:00
FingerprintViewController * vc = [ segue destinationViewController ] ;
[ self . uiDatabaseConnection readWithBlock : ^ ( YapDatabaseReadTransaction * transaction ) {
[ vc configWithThread : self . thread ] ;
} ] ;
2014-10-29 21:58:58 +01:00
}
2014-12-17 06:44:36 +01:00
else if ( [ segue . identifier isEqualToString : kUpdateGroupSegueIdentifier ] ) {
NewGroupViewController * vc = [ segue destinationViewController ] ;
[ self . uiDatabaseConnection readWithBlock : ^ ( YapDatabaseReadTransaction * transaction ) {
[ vc configWithThread : ( TSGroupThread * ) self . thread ] ;
} ] ;
}
2014-12-24 02:25:10 +01:00
else if ( [ segue . identifier isEqualToString : kShowGroupMembersSegue ] ) {
ShowGroupMembersViewController * vc = [ segue destinationViewController ] ;
[ self . uiDatabaseConnection readWithBlock : ^ ( YapDatabaseReadTransaction * transaction ) {
[ vc configWithThread : ( TSGroupThread * ) self . thread ] ;
} ] ;
}
2014-10-29 21:58:58 +01:00
}
# pragma mark - UIImagePickerController
/ *
* Presenting UIImagePickerController
* /
2015-03-19 01:59:44 +01:00
- ( void ) takePictureOrVideo {
2014-10-29 21:58:58 +01:00
UIImagePickerController * picker = [ [ UIImagePickerController alloc ] init ] ;
picker . delegate = self ;
picker . allowsEditing = NO ;
picker . sourceType = UIImagePickerControllerSourceTypeCamera ;
2015-02-17 00:14:50 +01:00
2014-10-29 21:58:58 +01:00
if ( [ UIImagePickerController isSourceTypeAvailable :
2014-12-26 23:18:54 +01:00
UIImagePickerControllerSourceTypeCamera ] ) {
2015-01-14 22:30:01 +01:00
picker . mediaTypes = @ [ ( NSString * ) kUTTypeImage , ( NSString * ) kUTTypeMovie ] ;
2015-01-30 09:49:40 +01:00
[ self presentViewController : picker animated : YES completion : [ UIUtil modalCompletionBlock ] ] ;
2014-10-29 21:58:58 +01:00
}
2015-02-17 00:14:50 +01:00
2014-10-29 21:58:58 +01:00
}
2015-03-19 01:59:44 +01:00
- ( void ) chooseFromLibrary {
2014-10-29 21:58:58 +01:00
UIImagePickerController * picker = [ [ UIImagePickerController alloc ] init ] ;
picker . delegate = self ;
2015-01-06 07:06:19 +01:00
picker . sourceType = UIImagePickerControllerSourceTypePhotoLibrary ;
2015-02-17 00:14:50 +01:00
2015-02-10 17:26:12 +01:00
if ( [ UIImagePickerController isSourceTypeAvailable : UIImagePickerControllerSourceTypePhotoLibrary ] ) {
2015-02-17 00:14:50 +01:00
2015-02-10 17:26:12 +01:00
NSArray * photoOrVideoTypeArray = [ [ NSArray alloc ] initWithObjects : ( NSString * ) kUTTypeImage , ( NSString * ) kUTTypeMovie , ( NSString * ) kUTTypeVideo , nil ] ;
2015-02-17 00:14:50 +01:00
2015-02-10 17:26:12 +01:00
picker . mediaTypes = photoOrVideoTypeArray ;
2015-01-30 09:49:40 +01:00
[ self presentViewController : picker animated : YES completion : [ UIUtil modalCompletionBlock ] ] ;
2014-10-29 21:58:58 +01:00
}
}
/ *
* Dismissing UIImagePickerController
* /
- ( void ) imagePickerControllerDidCancel : ( UIImagePickerController * ) picker {
2015-01-31 09:38:52 +01:00
[ UIUtil modalCompletionBlock ] ( ) ;
2014-10-29 21:58:58 +01:00
[ self dismissViewControllerAnimated : YES completion : nil ] ;
}
2015-03-19 01:59:44 +01:00
- ( void ) resetFrame {
2015-01-31 09:38:52 +01:00
// fixes bug on frame being off after this selection
CGRect frame = [ UIScreen mainScreen ] . applicationFrame ;
self . view . frame = frame ;
}
2014-10-29 21:58:58 +01:00
/ *
2014-11-29 19:54:33 +01:00
* Fetching data from UIImagePickerController
2014-10-29 21:58:58 +01:00
* /
2015-03-19 01:59:44 +01:00
- ( void ) imagePickerController : ( UIImagePickerController * ) picker didFinishPickingMediaWithInfo : ( NSDictionary * ) info {
2015-01-31 09:38:52 +01:00
[ UIUtil modalCompletionBlock ] ( ) ;
[ self resetFrame ] ;
2014-10-29 21:58:58 +01:00
NSString * mediaType = [ info objectForKey : UIImagePickerControllerMediaType ] ;
if ( CFStringCompare ( ( __bridge _retained CFStringRef ) mediaType , kUTTypeMovie , 0 ) = = kCFCompareEqualTo ) {
2015-01-14 22:30:01 +01:00
NSURL * videoURL = [ info objectForKey : UIImagePickerControllerMediaURL ] ;
[ self sendQualityAdjustedAttachment : videoURL ] ;
}
else {
UIImage * picture_camera = [ [ info objectForKey : UIImagePickerControllerOriginalImage ] normalizedImage ] ;
if ( picture_camera ) {
DDLogVerbose ( @ "Sending picture attachement ..." ) ;
[ self sendMessageAttachment : [ self qualityAdjustedAttachmentForImage : picture_camera ] ofType : @ "image/jpeg" ] ;
}
2014-10-29 21:58:58 +01:00
}
2015-02-17 00:14:50 +01:00
2015-01-14 22:30:01 +01:00
}
2015-03-19 01:59:44 +01:00
- ( void ) sendMessageAttachment : ( NSData * ) attachmentData ofType : ( NSString * ) attachmentType {
2015-01-14 22:30:01 +01:00
TSOutgoingMessage * message = [ [ TSOutgoingMessage alloc ] initWithTimestamp : [ NSDate ows_millisecondTimeStamp ] inThread : self . thread messageBody : nil attachments : [ NSMutableArray array ] ] ;
2015-02-17 00:14:50 +01:00
[ self dismissViewControllerAnimated : YES completion : ^ {
[ [ TSMessagesManager sharedManager ] sendAttachment : attachmentData contentType : attachmentType inMessage : message thread : self . thread ] ;
} ] ;
2015-01-14 22:30:01 +01:00
}
2015-04-25 16:59:32 +02:00
- ( NSURL * ) videoTempFolder {
NSArray * paths = NSSearchPathForDirectoriesInDomains ( NSCachesDirectory , NSUserDomainMask , YES ) ;
2015-01-14 22:30:01 +01:00
NSString * basePath = ( [ paths count ] > 0 ) ? [ paths objectAtIndex : 0 ] : nil ;
basePath = [ basePath stringByAppendingPathComponent : @ "videos" ] ;
if ( ! [ [ NSFileManager defaultManager ] fileExistsAtPath : basePath ] ) {
[ [ NSFileManager defaultManager ] createDirectoryAtPath : basePath withIntermediateDirectories : YES attributes : nil error : nil ] ;
}
2015-04-25 16:59:32 +02:00
return [ NSURL fileURLWithPath : basePath ] ;
}
- ( void ) sendQualityAdjustedAttachment : ( NSURL * ) movieURL {
AVAsset * video = [ AVAsset assetWithURL : movieURL ] ;
AVAssetExportSession * exportSession = [ AVAssetExportSession exportSessionWithAsset : video presetName : AVAssetExportPresetMediumQuality ] ;
exportSession . shouldOptimizeForNetworkUse = YES ;
exportSession . outputFileType = AVFileTypeMPEG4 ;
2015-02-17 00:14:50 +01:00
2015-01-29 03:57:53 +01:00
double currentTime = [ [ NSDate date ] timeIntervalSince1970 ] ;
NSString * strImageName = [ NSString stringWithFormat : @ "%f" , currentTime ] ;
2015-04-25 16:59:32 +02:00
NSURL * compressedVideoUrl = [ [ self videoTempFolder ] URLByAppendingPathComponent : [ NSString stringWithFormat : @ "%@.mp4" , strImageName ] ] ;
2015-02-17 00:14:50 +01:00
2015-01-14 22:30:01 +01:00
exportSession . outputURL = compressedVideoUrl ;
[ exportSession exportAsynchronouslyWithCompletionHandler : ^ {
2015-04-25 16:59:32 +02:00
NSError * error ;
2015-03-19 01:59:44 +01:00
[ self sendMessageAttachment : [ NSData dataWithContentsOfURL : compressedVideoUrl ] ofType : @ "video/mp4" ] ;
2015-04-25 16:59:32 +02:00
[ [ NSFileManager defaultManager ] removeItemAtURL : compressedVideoUrl error : & error ] ;
if ( error ) {
DDLogWarn ( @ "Failed to remove cached video file: %@" , error . debugDescription ) ;
}
2015-01-14 22:30:01 +01:00
} ] ;
2014-11-25 16:38:33 +01:00
}
2015-03-19 01:59:44 +01:00
- ( NSData * ) qualityAdjustedAttachmentForImage : ( UIImage * ) image {
2014-12-26 21:17:43 +01:00
return UIImageJPEGRepresentation ( [ self adjustedImageSizedForSending : image ] , [ self compressionRate ] ) ;
}
2015-03-19 01:59:44 +01:00
- ( UIImage * ) adjustedImageSizedForSending : ( UIImage * ) image {
2014-12-26 21:17:43 +01:00
CGFloat correctedWidth ;
switch ( [ Environment . preferences imageUploadQuality ] ) {
2014-12-26 23:18:54 +01:00
case TSImageQualityUncropped :
return image ;
2015-02-17 00:14:50 +01:00
2014-12-26 21:17:43 +01:00
case TSImageQualityHigh :
correctedWidth = 2048 ;
break ;
case TSImageQualityMedium :
correctedWidth = 1024 ;
break ;
case TSImageQualityLow :
correctedWidth = 512 ;
break ;
default :
break ;
}
2015-02-17 00:14:50 +01:00
2014-12-26 21:17:43 +01:00
return [ self imageScaled : image toMaxSize : correctedWidth ] ;
}
2015-03-19 01:59:44 +01:00
- ( UIImage * ) imageScaled : ( UIImage * ) image toMaxSize : ( CGFloat ) size {
2014-12-26 21:17:43 +01:00
CGFloat scaleFactor ;
CGFloat aspectRatio = image . size . height / image . size . width ;
2015-02-17 00:14:50 +01:00
2014-12-26 21:17:43 +01:00
if ( aspectRatio > 1 ) {
scaleFactor = size / image . size . width ;
}
else {
scaleFactor = size / image . size . height ;
}
2015-02-17 00:14:50 +01:00
2014-12-26 21:17:43 +01:00
CGSize newSize = CGSizeMake ( image . size . width * scaleFactor , image . size . height * scaleFactor ) ;
2015-02-17 00:14:50 +01:00
2014-12-26 21:17:43 +01:00
UIGraphicsBeginImageContext ( newSize ) ;
[ image drawInRect : CGRectMake ( 0 , 0 , newSize . width , newSize . height ) ] ;
UIImage * updatedImage = UIGraphicsGetImageFromCurrentImageContext ( ) ;
UIGraphicsEndImageContext ( ) ;
2015-02-17 00:14:50 +01:00
2014-12-26 21:17:43 +01:00
return updatedImage ;
}
2015-03-19 01:59:44 +01:00
- ( CGFloat ) compressionRate {
2014-12-26 21:17:43 +01:00
switch ( [ Environment . preferences imageUploadQuality ] ) {
2014-12-26 23:18:54 +01:00
case TSImageQualityUncropped :
return 1 ;
2014-12-26 21:17:43 +01:00
case TSImageQualityHigh :
return 0.9 f ;
case TSImageQualityMedium :
return 0.5 f ;
case TSImageQualityLow :
return 0.3 f ;
default :
break ;
}
}
2014-11-25 16:38:33 +01:00
# pragma mark Storage access
2014-12-06 17:45:42 +01:00
- ( YapDatabaseConnection * ) uiDatabaseConnection {
2014-11-25 16:38:33 +01:00
NSAssert ( [ NSThread isMainThread ] , @ "Must access uiDatabaseConnection on main thread!" ) ;
if ( ! _uiDatabaseConnection ) {
_uiDatabaseConnection = [ [ TSStorageManager sharedManager ] newDatabaseConnection ] ;
[ _uiDatabaseConnection beginLongLivedReadTransaction ] ;
[ [ NSNotificationCenter defaultCenter ] addObserver : self
selector : @ selector ( yapDatabaseModified : )
name : YapDatabaseModifiedNotification
object : nil ] ;
}
return _uiDatabaseConnection ;
}
2014-12-06 17:45:42 +01:00
- ( YapDatabaseConnection * ) editingDatabaseConnection {
if ( ! _editingDatabaseConnection ) {
_editingDatabaseConnection = [ [ TSStorageManager sharedManager ] newDatabaseConnection ] ;
}
return _editingDatabaseConnection ;
}
2014-12-24 02:25:10 +01:00
2015-03-19 01:59:44 +01:00
- ( void ) yapDatabaseModified : ( NSNotification * ) notification {
2015-05-23 15:54:50 +02:00
2015-10-31 16:53:32 +01:00
[ self updateBackButtonAsync ] ;
2015-05-23 15:54:50 +02:00
2014-12-24 02:25:10 +01:00
if ( isGroupConversation ) {
[ self . uiDatabaseConnection readWithBlock : ^ ( YapDatabaseReadTransaction * transaction ) {
TSGroupThread * gThread = ( TSGroupThread * ) self . thread ;
self . thread = [ TSGroupThread threadWithGroupModel : gThread . groupModel transaction : transaction ] ;
} ] ;
}
2015-02-17 00:14:50 +01:00
2014-11-25 16:38:33 +01:00
NSArray * notifications = [ self . uiDatabaseConnection beginLongLivedReadTransaction ] ;
2015-01-31 12:00:58 +01:00
2015-03-19 01:59:44 +01:00
if ( ! [ [ self . uiDatabaseConnection ext : TSMessageDatabaseViewExtensionName ] hasChangesForNotifications : notifications ] ) {
2015-01-31 12:00:58 +01:00
[ self . uiDatabaseConnection readWithBlock : ^ ( YapDatabaseReadTransaction * transaction ) {
[ self . messageMappings updateWithTransaction : transaction ] ;
} ] ;
return ;
}
2015-03-19 01:59:44 +01:00
if ( ! _isVisible ) {
2015-01-31 12:00:58 +01:00
// Since we moved our databaseConnection to a new commit ,
// we need to update the mappings too .
[ self . uiDatabaseConnection readWithBlock : ^ ( YapDatabaseReadTransaction * transaction ) {
[ self . messageMappings updateWithTransaction : transaction ] ;
} ] ;
return ;
}
2014-11-25 16:38:33 +01:00
NSArray * messageRowChanges = nil ;
2015-01-31 12:00:58 +01:00
NSArray * sectionChanges = nil ;
[ [ self . uiDatabaseConnection ext : TSMessageDatabaseViewExtensionName ] getSectionChanges : & sectionChanges
2014-11-25 16:38:33 +01:00
rowChanges : & messageRowChanges
forNotifications : notifications
withMappings : self . messageMappings ] ;
2015-02-17 00:14:50 +01:00
2015-01-05 16:31:00 +01:00
__block BOOL scrollToBottom = NO ;
2015-01-31 12:00:58 +01:00
2015-03-19 01:59:44 +01:00
if ( [ sectionChanges count ] = = 0 & [ messageRowChanges count ] = = 0 ) {
2014-12-08 23:12:22 +01:00
return ;
}
2015-01-31 12:00:58 +01:00
2014-12-12 22:41:28 +01:00
[ self . collectionView performBatchUpdates : ^ {
2015-03-19 01:59:44 +01:00
for ( YapDatabaseViewRowChange * rowChange in messageRowChanges ) {
switch ( rowChange . type ) {
2014-12-12 22:41:28 +01:00
case YapDatabaseViewChangeDelete :
{
[ self . collectionView deleteItemsAtIndexPaths : @ [ rowChange . indexPath ] ] ;
break ;
}
case YapDatabaseViewChangeInsert :
{
2014-12-31 13:22:40 +01:00
TSInteraction * interaction = [ self interactionAtIndexPath : rowChange . newIndexPath ] ;
[ [ TSAdapterCacheManager sharedManager ] cacheAdapter : [ TSMessageAdapter messageViewDataWithInteraction : interaction inThread : self . thread ] forInteractionId : interaction . uniqueId ] ;
2014-12-12 22:41:28 +01:00
[ self . collectionView insertItemsAtIndexPaths : @ [ rowChange . newIndexPath ] ] ;
2015-01-05 16:31:00 +01:00
scrollToBottom = YES ;
2014-12-12 22:41:28 +01:00
break ;
}
case YapDatabaseViewChangeMove :
{
2014-12-31 13:22:40 +01:00
[ self . collectionView deleteItemsAtIndexPaths : @ [ rowChange . indexPath ] ] ;
[ self . collectionView insertItemsAtIndexPaths : @ [ rowChange . newIndexPath ] ] ;
2014-12-12 22:41:28 +01:00
break ;
}
case YapDatabaseViewChangeUpdate :
{
NSMutableArray * rowsToUpdate = [ @ [ rowChange . indexPath ] mutableCopy ] ;
2015-02-17 00:14:50 +01:00
2014-12-12 22:41:28 +01:00
if ( _lastDeliveredMessageIndexPath ) {
[ rowsToUpdate addObject : _lastDeliveredMessageIndexPath ] ;
}
2015-02-17 00:14:50 +01:00
2014-12-31 13:22:40 +01:00
for ( NSIndexPath * indexPath in rowsToUpdate ) {
TSInteraction * interaction = [ self interactionAtIndexPath : indexPath ] ;
[ [ TSAdapterCacheManager sharedManager ] cacheAdapter : [ TSMessageAdapter messageViewDataWithInteraction : interaction inThread : self . thread ] forInteractionId : interaction . uniqueId ] ;
}
2015-02-17 00:14:50 +01:00
2014-12-12 22:41:28 +01:00
[ self . collectionView reloadItemsAtIndexPaths : rowsToUpdate ] ;
2015-01-05 16:31:00 +01:00
scrollToBottom = YES ;
2014-12-12 22:41:28 +01:00
break ;
2014-12-09 20:11:14 +01:00
}
2014-12-06 17:45:42 +01:00
}
}
2014-12-31 13:22:40 +01:00
} completion : ^ ( BOOL success ) {
2015-03-21 19:15:43 +01:00
if ( ! success ) {
2014-12-31 13:22:40 +01:00
[ self . collectionView . collectionViewLayout invalidateLayoutWithContext : [ JSQMessagesCollectionViewFlowLayoutInvalidationContext context ] ] ;
[ self . collectionView reloadData ] ;
}
2015-01-05 16:31:00 +01:00
if ( scrollToBottom ) {
2014-12-31 13:22:40 +01:00
[ self scrollToBottomAnimated : YES ] ;
}
2014-12-12 22:41:28 +01:00
} ] ;
2014-11-25 16:38:33 +01:00
}
# pragma mark - UICollectionView DataSource
2015-03-19 01:59:44 +01:00
- ( NSInteger ) collectionView : ( UICollectionView * ) collectionView numberOfItemsInSection : ( NSInteger ) section {
2014-12-31 21:30:20 +01:00
NSInteger numberOfMessages = ( NSInteger ) [ self . messageMappings numberOfItemsInSection : ( NSUInteger ) section ] ;
2014-11-25 16:38:33 +01:00
return numberOfMessages ;
2014-10-29 21:58:58 +01:00
}
2014-11-25 16:38:33 +01:00
2014-12-11 00:05:41 +01:00
- ( TSInteraction * ) interactionAtIndexPath : ( NSIndexPath * ) indexPath {
2014-11-25 16:38:33 +01:00
__block TSInteraction * message = nil ;
[ self . uiDatabaseConnection readWithBlock : ^ ( YapDatabaseReadTransaction * transaction ) {
YapDatabaseViewTransaction * viewTransaction = [ transaction ext : TSMessageDatabaseViewExtensionName ] ;
NSParameterAssert ( viewTransaction ! = nil ) ;
NSParameterAssert ( self . messageMappings ! = nil ) ;
NSParameterAssert ( indexPath ! = nil ) ;
2014-12-31 21:30:20 +01:00
NSUInteger row = ( NSUInteger ) indexPath . row ;
NSUInteger section = ( NSUInteger ) indexPath . section ;
2014-11-25 16:38:33 +01:00
NSUInteger numberOfItemsInSection = [ self . messageMappings numberOfItemsInSection : section ] ;
2015-02-17 00:14:50 +01:00
2014-11-25 16:38:33 +01:00
NSAssert ( row < numberOfItemsInSection , @ "Cannot fetch message because row %d is >= numberOfItemsInSection %d" , ( int ) row , ( int ) numberOfItemsInSection ) ;
2015-02-17 00:14:50 +01:00
2014-11-25 16:38:33 +01:00
message = [ viewTransaction objectAtRow : row inSection : section withMappings : self . messageMappings ] ;
NSParameterAssert ( message ! = nil ) ;
} ] ;
2015-02-17 00:14:50 +01:00
2014-12-11 00:05:41 +01:00
return message ;
}
- ( TSMessageAdapter * ) messageAtIndexPath : ( NSIndexPath * ) indexPath {
TSInteraction * interaction = [ self interactionAtIndexPath : indexPath ] ;
2014-12-31 13:22:40 +01:00
TSAdapterCacheManager * manager = [ TSAdapterCacheManager sharedManager ] ;
2015-02-17 00:14:50 +01:00
2014-12-31 13:22:40 +01:00
if ( ! [ manager containsCacheEntryForInteractionId : interaction . uniqueId ] ) {
[ manager cacheAdapter : [ TSMessageAdapter messageViewDataWithInteraction : interaction inThread : self . thread ] forInteractionId : interaction . uniqueId ] ;
}
2015-02-17 00:14:50 +01:00
2014-12-31 13:22:40 +01:00
return [ manager adapterForInteractionId : interaction . uniqueId ] ;
2014-11-25 16:38:33 +01:00
}
2014-12-31 13:22:40 +01:00
2014-12-17 06:44:36 +01:00
# pragma mark group action view
2015-01-14 22:30:01 +01:00
2014-12-17 06:44:36 +01:00
2015-01-22 05:08:12 +01:00
# pragma mark - Audio
- ( void ) recordAudio {
// Define the recorder setting
NSArray * pathComponents = [ NSArray arrayWithObjects :
[ NSSearchPathForDirectoriesInDomains ( NSDocumentDirectory , NSUserDomainMask , YES ) lastObject ] ,
[ NSString stringWithFormat : @ "%lld.m4a" , [ NSDate ows_millisecondTimeStamp ] ] ,
nil ] ;
NSURL * outputFileURL = [ NSURL fileURLWithPathComponents : pathComponents ] ;
2015-02-17 00:14:50 +01:00
2015-01-22 05:08:12 +01:00
// Setup audio session
AVAudioSession * session = [ AVAudioSession sharedInstance ] ;
[ session setCategory : AVAudioSessionCategoryPlayAndRecord error : nil ] ;
2015-02-17 00:14:50 +01:00
2015-01-22 05:08:12 +01:00
NSMutableDictionary * recordSetting = [ [ NSMutableDictionary alloc ] init ] ;
[ recordSetting setValue : [ NSNumber numberWithInt : kAudioFormatMPEG4AAC ] forKey : AVFormatIDKey ] ;
[ recordSetting setValue : [ NSNumber numberWithFloat : 44100.0 ] forKey : AVSampleRateKey ] ;
[ recordSetting setValue : [ NSNumber numberWithInt : 2 ] forKey : AVNumberOfChannelsKey ] ;
2015-02-17 00:14:50 +01:00
2015-01-22 05:08:12 +01:00
// Initiate and prepare the recorder
_audioRecorder = [ [ AVAudioRecorder alloc ] initWithURL : outputFileURL settings : recordSetting error : NULL ] ;
_audioRecorder . delegate = self ;
_audioRecorder . meteringEnabled = YES ;
[ _audioRecorder prepareToRecord ] ;
}
- ( void ) audioPlayerUpdated : ( NSTimer * ) timer {
double current = [ _audioPlayer currentTime ] / [ _audioPlayer duration ] ;
2015-01-25 00:48:40 +01:00
double interval = [ _audioPlayer duration ] - [ _audioPlayer currentTime ] ;
[ _currentMediaAdapter setDurationOfAudio : interval ] ;
[ _currentMediaAdapter setAudioProgressFromFloat : ( float ) current ] ;
2015-01-22 05:08:12 +01:00
}
- ( void ) audioPlayerDidFinishPlaying : ( AVAudioPlayer * ) player successfully : ( BOOL ) flag {
[ _audioPlayerPoller invalidate ] ;
2015-01-25 00:48:40 +01:00
[ _currentMediaAdapter setAudioProgressFromFloat : 0 ] ;
[ _currentMediaAdapter setDurationOfAudio : _audioPlayer . duration ] ;
[ _currentMediaAdapter setAudioIconToPlay ] ;
2015-01-22 05:08:12 +01:00
}
- ( void ) audioRecorderDidFinishRecording : ( AVAudioRecorder * ) recorder
successfully : ( BOOL ) flag {
if ( flag ) {
[ self sendMessageAttachment : [ NSData dataWithContentsOfURL : recorder . url ] ofType : @ "audio/m4a" ] ;
}
}
2014-11-25 16:38:33 +01:00
# pragma mark Accessory View
2015-03-19 01:59:44 +01:00
- ( void ) didPressAccessoryButton : ( UIButton * ) sender {
2015-01-30 23:28:05 +01:00
[ self dismissKeyBoard ] ;
2015-02-17 00:14:50 +01:00
2014-11-25 16:38:33 +01:00
UIView * presenter = self . parentViewController . view ;
2015-02-17 00:14:50 +01:00
2014-11-25 16:38:33 +01:00
[ DJWActionSheet showInView : presenter
withTitle : nil
2015-02-18 23:21:03 +01:00
cancelButtonTitle : NSLocalizedString ( @ "TXT_CANCEL_TITLE" , @ "" )
2014-11-25 16:38:33 +01:00
destructiveButtonTitle : nil
2015-02-18 23:21:03 +01:00
otherButtonTitles : @ [ NSLocalizedString ( @ "TAKE_MEDIA_BUTTON" , @ "" ) , NSLocalizedString ( @ "CHOOSE_MEDIA_BUTTON" , @ "" ) ] // , @ "Record audio" ]
2014-11-25 16:38:33 +01:00
tapBlock : ^ ( DJWActionSheet * actionSheet , NSInteger tappedButtonIndex ) {
if ( tappedButtonIndex = = actionSheet . cancelButtonIndex ) {
2014-12-26 23:18:54 +01:00
DDLogVerbose ( @ "User Cancelled" ) ;
2014-11-25 16:38:33 +01:00
} else if ( tappedButtonIndex = = actionSheet . destructiveButtonIndex ) {
2014-12-26 23:18:54 +01:00
DDLogVerbose ( @ "Destructive button tapped" ) ;
2015-03-19 01:59:44 +01:00
} else {
2014-11-25 16:38:33 +01:00
switch ( tappedButtonIndex ) {
case 0 :
[ self takePictureOrVideo ] ;
break ;
case 1 :
2015-02-10 17:26:12 +01:00
[ self chooseFromLibrary ] ;
2014-11-25 16:38:33 +01:00
break ;
2015-01-14 22:30:01 +01:00
case 2 :
[ self recordAudio ] ;
break ;
2014-11-25 16:38:33 +01:00
default :
break ;
}
}
} ] ;
}
2014-12-06 17:45:42 +01:00
- ( void ) markAllMessagesAsRead {
2015-03-21 19:15:43 +01:00
[ self . editingDatabaseConnection asyncReadWriteWithBlock : ^ ( YapDatabaseReadWriteTransaction * transaction ) {
2015-02-17 00:14:50 +01:00
[ self . thread markAllAsReadWithTransaction : transaction ] ;
2014-12-06 17:45:42 +01:00
} ] ;
}
2014-12-26 23:18:54 +01:00
- ( BOOL ) collectionView : ( UICollectionView * ) collectionView canPerformAction : ( SEL ) action forItemAtIndexPath : ( NSIndexPath * ) indexPath withSender : ( id ) sender
{
if ( action = = @ selector ( delete : ) ) {
return YES ;
}
2015-02-17 00:14:50 +01:00
2014-12-26 23:18:54 +01:00
return [ super collectionView : collectionView canPerformAction : action forItemAtIndexPath : indexPath withSender : sender ] ;
}
2015-03-19 01:59:44 +01:00
- ( void ) collectionView : ( UICollectionView * ) collectionView performAction : ( SEL ) action forItemAtIndexPath : ( NSIndexPath * ) indexPath withSender : ( id ) sender {
2014-12-26 23:18:54 +01:00
if ( action = = @ selector ( delete : ) ) {
[ self deleteMessageAtIndexPath : indexPath ] ;
}
else {
[ super collectionView : collectionView performAction : action forItemAtIndexPath : indexPath withSender : sender ] ;
}
}
2015-02-28 17:49:46 +01:00
- ( void ) updateGroup {
2015-01-14 22:30:01 +01:00
[ self . navController hideDropDown : self ] ;
2015-02-17 00:14:50 +01:00
2015-01-14 22:30:01 +01:00
[ self performSegueWithIdentifier : kUpdateGroupSegueIdentifier sender : self ] ;
}
2015-02-28 17:49:46 +01:00
- ( void ) leaveGroup {
2015-01-14 22:30:01 +01:00
[ self . navController hideDropDown : self ] ;
2015-02-17 00:14:50 +01:00
2014-12-24 02:25:10 +01:00
TSGroupThread * gThread = ( TSGroupThread * ) _thread ;
TSOutgoingMessage * message = [ [ TSOutgoingMessage alloc ] initWithTimestamp : [ NSDate ows_millisecondTimeStamp ] inThread : gThread messageBody : @ "" attachments : [ [ NSMutableArray alloc ] init ] ] ;
message . groupMetaMessage = TSGroupMessageQuit ;
2015-09-01 19:22:08 +02:00
[ [ TSMessagesManager sharedManager ] sendMessage : message inThread : gThread success : nil failure : nil ] ;
2014-12-24 02:25:10 +01:00
[ self . editingDatabaseConnection readWriteWithBlock : ^ ( YapDatabaseReadWriteTransaction * transaction ) {
NSMutableArray * newGroupMemberIds = [ NSMutableArray arrayWithArray : gThread . groupModel . groupMemberIds ] ;
[ newGroupMemberIds removeObject : [ SignalKeyingStorage . localNumber toE164 ] ] ;
gThread . groupModel . groupMemberIds = newGroupMemberIds ;
[ gThread saveWithTransaction : transaction ] ;
} ] ;
2015-01-14 22:30:01 +01:00
[ self hideInputIfNeeded ] ;
2014-12-24 02:25:10 +01:00
}
2015-01-16 15:28:41 +01:00
- ( void ) updateGroupModelTo : ( TSGroupModel * ) newGroupModel {
2015-02-17 00:14:50 +01:00
__block TSGroupThread * groupThread ;
__block TSOutgoingMessage * message ;
2014-12-24 02:25:10 +01:00
[ self . editingDatabaseConnection readWriteWithBlock : ^ ( YapDatabaseReadWriteTransaction * transaction ) {
2015-02-17 00:14:50 +01:00
groupThread = [ TSGroupThread getOrCreateThreadWithGroupModel : newGroupModel transaction : transaction ] ;
groupThread . groupModel = newGroupModel ;
[ groupThread saveWithTransaction : transaction ] ;
message = [ [ TSOutgoingMessage alloc ] initWithTimestamp : [ NSDate ows_millisecondTimeStamp ] inThread : groupThread messageBody : @ "" attachments : [ [ NSMutableArray alloc ] init ] ] ;
2014-12-24 02:25:10 +01:00
message . groupMetaMessage = TSGroupMessageUpdate ;
} ] ;
2015-02-17 00:14:50 +01:00
if ( newGroupModel . groupImage ! = nil ) {
[ [ TSMessagesManager sharedManager ] sendAttachment : UIImagePNGRepresentation ( newGroupModel . groupImage ) contentType : @ "image/png" inMessage : message thread : groupThread ] ;
}
else {
2015-09-01 19:22:08 +02:00
[ [ TSMessagesManager sharedManager ] sendMessage : message inThread : groupThread success : nil failure : nil ] ;
2015-02-17 00:14:50 +01:00
}
self . thread = groupThread ;
2014-12-24 02:25:10 +01:00
}
2015-03-19 01:59:44 +01:00
- ( IBAction ) unwindGroupUpdated : ( UIStoryboardSegue * ) segue {
2014-12-24 02:25:10 +01:00
NewGroupViewController * ngc = [ segue sourceViewController ] ;
2015-01-16 15:28:41 +01:00
TSGroupModel * newGroupModel = [ ngc groupModel ] ;
2015-02-28 17:49:46 +01:00
NSMutableSet * groupMemberIds = [ NSMutableSet setWithArray : newGroupModel . groupMemberIds ] ;
2014-12-24 02:25:10 +01:00
[ groupMemberIds addObject : [ SignalKeyingStorage . localNumber toE164 ] ] ;
2015-02-28 17:49:46 +01:00
newGroupModel . groupMemberIds = [ NSMutableArray arrayWithArray : [ groupMemberIds allObjects ] ] ;
2014-12-24 02:25:10 +01:00
[ self updateGroupModelTo : newGroupModel ] ;
2015-02-28 17:49:46 +01:00
[ self . collectionView . collectionViewLayout invalidateLayoutWithContext : [ JSQMessagesCollectionViewFlowLayoutInvalidationContext context ] ] ;
[ self . collectionView reloadData ] ;
2014-12-24 02:25:10 +01:00
}
2015-04-14 21:49:00 +02:00
- ( void ) popKeyBoard {
[ self . inputToolbar . contentView . textView becomeFirstResponder ] ;
}
2015-01-30 23:28:05 +01:00
- ( void ) dismissKeyBoard {
[ self . inputToolbar . contentView . textView resignFirstResponder ] ;
}
2015-03-01 00:04:39 +01:00
# pragma mark Drafts
2015-03-19 01:59:44 +01:00
- ( void ) loadDraftInCompose {
2015-03-01 00:04:39 +01:00
__block NSString * placeholder ;
[ self . editingDatabaseConnection asyncReadWithBlock : ^ ( YapDatabaseReadTransaction * transaction ) {
placeholder = [ _thread currentDraftWithTransaction : transaction ] ;
} completionBlock : ^ {
dispatch_async ( dispatch_get _main _queue ( ) , ^ {
[ self . inputToolbar . contentView . textView setText : placeholder ] ;
[ self textViewDidChange : self . inputToolbar . contentView . textView ] ;
} ) ;
} ] ;
}
2015-03-19 01:59:44 +01:00
- ( void ) saveDraft {
2015-03-01 00:04:39 +01:00
if ( self . inputToolbar . hidden = = NO ) {
2015-03-21 19:15:43 +01:00
__block TSThread * thread = _thread ;
__block NSString * currentDraft = self . inputToolbar . contentView . textView . text ;
[ self . editingDatabaseConnection asyncReadWriteWithBlock : ^ ( YapDatabaseReadWriteTransaction * transaction ) {
[ thread setDraft : currentDraft transaction : transaction ] ;
2015-03-01 00:04:39 +01:00
} ] ;
}
}
2015-05-23 15:54:50 +02:00
# pragma mark Unread Badge
- ( void ) setUnreadCount : ( NSUInteger ) unreadCount {
if ( _unreadCount ! = unreadCount ) {
_unreadCount = unreadCount ;
if ( _unreadCount > 0 ) {
if ( _unreadContainer = = nil ) {
static UIImage * backgroundImage = nil ;
static dispatch_once _t onceToken ;
dispatch_once ( & onceToken , ^
{
UIGraphicsBeginImageContextWithOptions ( CGSizeMake ( 17.0 f , 17.0 f ) , false , 0.0 f ) ;
CGContextRef context = UIGraphicsGetCurrentContext ( ) ;
CGContextSetFillColorWithColor ( context , [ UIColor redColor ] . CGColor ) ;
CGContextFillEllipseInRect ( context , CGRectMake ( 0.0 f , 0.0 f , 17.0 f , 17.0 f ) ) ;
backgroundImage = [ UIGraphicsGetImageFromCurrentImageContext ( ) stretchableImageWithLeftCapWidth : 8 topCapHeight : 8 ] ;
UIGraphicsEndImageContext ( ) ;
} ) ;
_unreadContainer = [ [ UIImageView alloc ] initWithFrame : CGRectMake ( 0.0 f , 0.0 f , 10.0 f , 10.0 f ) ] ;
_unreadContainer . userInteractionEnabled = NO ;
_unreadContainer . layer . zPosition = 2000 ;
[ self . navigationController . navigationBar addSubview : _unreadContainer ] ;
_unreadBackground = [ [ UIImageView alloc ] initWithImage : backgroundImage ] ;
[ _unreadContainer addSubview : _unreadBackground ] ;
_unreadLabel = [ [ UILabel alloc ] init ] ;
_unreadLabel . backgroundColor = [ UIColor clearColor ] ;
_unreadLabel . textColor = [ UIColor whiteColor ] ;
_unreadLabel . font = [ UIFont systemFontOfSize : 12 ] ;
[ _unreadContainer addSubview : _unreadLabel ] ;
}
_unreadContainer . hidden = false ;
_unreadLabel . text = [ NSString stringWithFormat : @ "%lu" , ( unsigned long ) unreadCount ] ;
[ _unreadLabel sizeToFit ] ;
CGPoint offset = CGPointMake ( 17.0 f , 2.0 f ) ;
_unreadBackground . frame = CGRectMake ( offset . x , offset . y ,
MAX ( _unreadLabel . frame . size . width + 8.0 f , 17.0 f ) , 17.0 f ) ;
_unreadLabel . frame = CGRectMake ( offset . x + floor ( ( 2.0 f * ( _unreadBackground . frame . size . width - _unreadLabel . frame . size . width ) / 2.0 f ) / 2.0 f ) , offset . y + 1.0 f ,
_unreadLabel . frame . size . width , _unreadLabel . frame . size . height ) ;
} else if ( _unreadContainer ! = nil ) {
_unreadContainer . hidden = true ;
}
}
}
2015-03-19 01:59:44 +01:00
- ( void ) dealloc {
2014-12-09 20:11:14 +01:00
[ [ NSNotificationCenter defaultCenter ] removeObserver : self ] ;
2014-12-06 17:45:42 +01:00
}
2014-10-29 21:58:58 +01:00
@ end