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"
|
|
|
|
|
|
|
|
#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"
|
|
|
|
#import "JSQCall.h"
|
|
|
|
|
2014-11-29 19:54:33 +01:00
|
|
|
#import "JSQDisplayedMessageCollectionViewCell.h"
|
|
|
|
#import "JSQInfoMessage.h"
|
|
|
|
#import "JSQErrorMessage.h"
|
|
|
|
|
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>
|
|
|
|
#import <AVFoundation/AVFoundation.h>
|
|
|
|
#import <CoreMedia/CoreMedia.h>
|
|
|
|
|
2014-11-25 16:38:33 +01:00
|
|
|
#import "TSContactThread.h"
|
|
|
|
#import "TSGroupThread.h"
|
|
|
|
|
|
|
|
#import "TSStorageManager.h"
|
|
|
|
#import "TSDatabaseView.h"
|
2015-01-14 22:30:01 +01:00
|
|
|
#import "UIColor+OWS.h"
|
|
|
|
#import "UIFont+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"
|
2014-12-11 00:05:41 +01:00
|
|
|
#import "TSInteraction.h"
|
2014-12-21 12:52:42 +01:00
|
|
|
#import "TSAttachmentAdapter.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 "PhoneNumber.h"
|
|
|
|
#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
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
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;
|
|
|
|
@property (nonatomic, retain) JSQMessagesBubbleImage *outgoingMessageFailedImageData;
|
2015-01-22 05:08:12 +01:00
|
|
|
@property (nonatomic, strong) NSTimer *audioPlayerPoller;
|
2014-12-06 17:45:42 +01:00
|
|
|
|
|
|
|
@property (nonatomic, retain) NSTimer *readTimer;
|
2014-11-25 16:38:33 +01:00
|
|
|
|
2014-12-06 23:21:15 +01:00
|
|
|
@property (nonatomic, retain) NSIndexPath *lastDeliveredMessageIndexPath;
|
|
|
|
|
2014-12-31 13:22:40 +01:00
|
|
|
@property NSUInteger page;
|
|
|
|
|
2014-10-29 21:58:58 +01:00
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation MessagesViewController
|
|
|
|
|
2014-11-26 16:00:10 +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];
|
2015-01-22 05:08:12 +01:00
|
|
|
|
2014-12-24 02:25:10 +01:00
|
|
|
TSOutgoingMessage *message = [[TSOutgoingMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] inThread:self.thread messageBody:@"" attachments:[[NSMutableArray alloc] init]];
|
2014-12-17 06:44:36 +01:00
|
|
|
message.groupMetaMessage = TSGroupMessageNew;
|
2014-12-24 02:25:10 +01:00
|
|
|
if(model.groupImage!=nil) {
|
|
|
|
[[TSMessagesManager sharedManager] sendAttachment:UIImagePNGRepresentation(model.groupImage) contentType:@"image/png" inMessage:message thread:self.thread];
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
[[TSMessagesManager sharedManager] sendMessage:message inThread:self.thread];
|
|
|
|
}
|
2014-12-17 06:44:36 +01:00
|
|
|
isGroupConversation = YES;
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
2014-11-26 16:00:10 +01:00
|
|
|
- (void)setupWithThread:(TSThread *)thread{
|
|
|
|
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-01-14 22:30:01 +01:00
|
|
|
|
|
|
|
-(void) hideInputIfNeeded {
|
|
|
|
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
|
|
|
|
self.navigationItem.rightBarButtonItem = nil;
|
|
|
|
}
|
|
|
|
}
|
2014-10-29 21:58:58 +01:00
|
|
|
- (void)viewDidLoad {
|
2015-01-22 05:08:12 +01:00
|
|
|
[super viewDidLoad];
|
2015-01-14 22:30:01 +01:00
|
|
|
[self.navigationController.navigationBar setTranslucent:NO];
|
|
|
|
|
2015-01-22 05:08:12 +01:00
|
|
|
[super viewDidLoad];
|
|
|
|
|
2014-12-06 17:45:42 +01:00
|
|
|
[self markAllMessagesAsRead];
|
2015-01-22 05:08:12 +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-01-22 05:08:12 +01:00
|
|
|
|
2014-12-31 13:22:40 +01:00
|
|
|
self.page = 0;
|
2015-01-22 05:08:12 +01:00
|
|
|
|
2014-12-31 13:22:40 +01:00
|
|
|
[self updateRangeOptionsForPage:self.page];
|
2015-01-22 05:08:12 +01:00
|
|
|
|
2014-11-25 16:38:33 +01:00
|
|
|
[self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
|
|
|
|
[self.messageMappings updateWithTransaction:transaction];
|
|
|
|
}];
|
2015-01-22 05:08:12 +01:00
|
|
|
|
2014-12-24 02:25:10 +01:00
|
|
|
[self initializeToolbars];
|
2014-11-29 19:54:33 +01:00
|
|
|
[self initializeCollectionViewLayout];
|
2015-01-22 05:08:12 +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-01-22 05:08:12 +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-01-14 22:30:01 +01:00
|
|
|
-(void) initializeTextView {
|
|
|
|
[self.inputToolbar.contentView.textView setFont:[UIFont ows_regularFontWithSize:17.f]];
|
|
|
|
[self.inputToolbar.contentView.rightBarButtonItem.titleLabel setFont:[UIFont ows_mediumFontWithSize:15.f]];
|
|
|
|
[self.inputToolbar.contentView.rightBarButtonItem setTitleColor:[UIColor ows_materialBlueColor] forState:UIControlStateNormal];
|
|
|
|
}
|
|
|
|
|
2014-12-24 11:50:07 +01:00
|
|
|
-(void)viewWillAppear:(BOOL)animated
|
|
|
|
{
|
|
|
|
[super viewWillAppear:animated];
|
2015-01-22 05:08:12 +01:00
|
|
|
|
2015-01-05 03:15:18 +01:00
|
|
|
NSInteger numberOfMessages = (NSInteger)[self.messageMappings numberOfItemsInGroup:self.thread.uniqueId];
|
2015-01-22 05:08:12 +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
|
|
|
}
|
|
|
|
|
2014-12-09 20:11:14 +01:00
|
|
|
- (void)startReadTimer{
|
|
|
|
self.readTimer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(markAllMessagesAsRead) userInfo:nil repeats:YES];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)cancelReadTimer{
|
|
|
|
[self.readTimer invalidate];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)viewDidAppear:(BOOL)animated{
|
|
|
|
[super viewDidAppear:animated];
|
|
|
|
[self startReadTimer];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (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];
|
|
|
|
|
|
|
|
[_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];
|
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-22 05:08:12 +01:00
|
|
|
|
2015-01-14 22:30:01 +01:00
|
|
|
UIBarButtonItem *spaceEdge = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
|
|
|
|
|
|
|
|
spaceEdge.width = 40;
|
2015-01-22 05:08:12 +01:00
|
|
|
|
2015-01-14 22:30:01 +01:00
|
|
|
UIBarButtonItem *spaceMiddleIcons = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
|
|
|
|
spaceMiddleIcons.width = 61;
|
|
|
|
|
|
|
|
UIBarButtonItem *spaceMiddleWords = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
|
|
|
|
|
|
|
|
|
2014-12-24 02:25:10 +01:00
|
|
|
if (!isGroupConversation) {
|
2015-01-22 05:08:12 +01:00
|
|
|
|
2015-01-14 22:30:01 +01:00
|
|
|
//UIBarButtonItem* contactAddOrLaunch = [[UIBarButtonItem alloc] initWithImage:[[UIImage imageNamed:@"contact-add@1x"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal] style:UIBarButtonItemStylePlain target:self action:nil];
|
2015-01-22 05:08:12 +01:00
|
|
|
|
2015-01-14 22:30:01 +01:00
|
|
|
UIBarButtonItem* contactSecurity = [[UIBarButtonItem alloc]initWithImage:[[UIImage imageNamed:@"contact-security@1x"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal] style:UIBarButtonItemStylePlain target:self action:@selector(showFingerprint)];
|
2015-01-22 05:08:12 +01:00
|
|
|
|
|
|
|
|
2015-01-07 18:57:58 +01:00
|
|
|
if ([self isRedPhoneReachable] && ![((TSContactThread*)_thread).contactIdentifier isEqualToString:[SignalKeyingStorage.localNumber toE164]]) {
|
2015-01-14 22:30:01 +01:00
|
|
|
UIBarButtonItem * callButton = [[UIBarButtonItem alloc] initWithImage:[[UIImage imageNamed:@"contact-call@1x"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal] style:UIBarButtonItemStylePlain target:self action:@selector(callAction)];
|
|
|
|
self.navController.dropDownToolbar.items = @[spaceEdge, callButton,spaceMiddleWords, contactSecurity, spaceEdge];
|
2014-12-24 02:25:10 +01:00
|
|
|
}
|
|
|
|
else {
|
2015-01-14 22:30:01 +01:00
|
|
|
self.navController.dropDownToolbar.items = @[spaceMiddleWords, contactSecurity, spaceMiddleWords];
|
2014-12-24 02:25:10 +01:00
|
|
|
}
|
2014-11-29 19:54:33 +01:00
|
|
|
}
|
2015-01-14 22:30:01 +01:00
|
|
|
else {
|
|
|
|
UIBarButtonItem *groupUpdateButton = [[UIBarButtonItem alloc] initWithTitle:@"Update" style:UIBarButtonItemStylePlain target:self action:@selector(updateGroup)];
|
|
|
|
UIBarButtonItem *groupLeaveButton = [[UIBarButtonItem alloc] initWithTitle:@"Leave" style:UIBarButtonItemStylePlain target:self action:@selector(leaveGroup)];
|
2015-01-22 05:08:12 +01:00
|
|
|
|
2015-01-14 22:30:01 +01:00
|
|
|
UIBarButtonItem *showGroupMembersButton = [[UIBarButtonItem alloc] initWithTitle:@"Members" style:UIBarButtonItemStylePlain target:self action:@selector(showGroupMembers)];
|
2015-01-22 05:08:12 +01:00
|
|
|
|
2015-01-14 22:30:01 +01:00
|
|
|
self.navController.dropDownToolbar.items =@[spaceEdge, groupUpdateButton, spaceMiddleWords, groupLeaveButton, spaceMiddleWords, showGroupMembersButton, spaceEdge];
|
|
|
|
}
|
|
|
|
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];
|
|
|
|
}
|
|
|
|
|
|
|
|
-(void) setNavigationTitle {
|
|
|
|
NSString* navTitle = !isGroupConversation ? self.thread.name : ((TSGroupThread*)self.thread).groupModel.groupName;
|
|
|
|
self.navController.activeNavigationBarTitle = nil;
|
|
|
|
self.title = navTitle;
|
|
|
|
}
|
|
|
|
|
|
|
|
-(void)initializeToolbars {
|
2015-01-22 05:08:12 +01:00
|
|
|
|
2015-01-14 22:30:01 +01:00
|
|
|
self.navController = (APNavigationController*)self.navigationController;
|
|
|
|
//self.navController.activeBarButtonTitle = @"Hide";
|
|
|
|
[self setNavigationTitle];
|
|
|
|
[self hideInputIfNeeded];
|
2014-11-29 19:54:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
-(void)initializeBubbles
|
|
|
|
{
|
|
|
|
JSQMessagesBubbleImageFactory *bubbleFactory = [[JSQMessagesBubbleImageFactory alloc] init];
|
2015-01-22 05:08:12 +01:00
|
|
|
|
2015-01-14 22:30:01 +01:00
|
|
|
self.outgoingBubbleImageData = [bubbleFactory outgoingMessagesBubbleImageWithColor:[UIColor ows_materialBlueColor]];
|
2014-11-29 19:54:33 +01:00
|
|
|
self.incomingBubbleImageData = [bubbleFactory incomingMessagesBubbleImageWithColor:[UIColor jsq_messageBubbleLightGrayColor]];
|
|
|
|
self.outgoingMessageFailedImageData = [bubbleFactory outgoingMessageFailedBubbleImageWithColor:[UIColor ows_fadedBlueColor]];
|
|
|
|
}
|
|
|
|
|
|
|
|
-(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.0f]];
|
2015-01-22 05:08:12 +01:00
|
|
|
|
2014-12-09 22:18:39 +01:00
|
|
|
self.collectionView.showsVerticalScrollIndicator = NO;
|
|
|
|
self.collectionView.showsHorizontalScrollIndicator = NO;
|
2015-01-22 05:08:12 +01:00
|
|
|
|
2014-12-31 13:22:40 +01:00
|
|
|
[self updateLoadEarlierVisible];
|
2015-01-22 05:08:12 +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
|
|
|
|
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
|
|
|
|
|
|
|
|
-(BOOL)isRedPhoneReachable
|
|
|
|
{
|
2014-12-09 22:18:39 +01:00
|
|
|
return [[Environment getCurrent].contactsManager isPhoneNumberRegisteredWithRedPhone:[self phoneNumberForThread]];
|
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];
|
2015-01-22 05:08:12 +01:00
|
|
|
|
2014-12-29 11:43:11 +01:00
|
|
|
[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");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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-01-22 05:08:12 +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-01-22 05:08:12 +01:00
|
|
|
|
2014-11-25 16:38:33 +01:00
|
|
|
[[TSMessagesManager sharedManager] sendMessage:message inThread:self.thread];
|
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-01-22 05:08:12 +01:00
|
|
|
|
2014-10-29 21:58:58 +01:00
|
|
|
if ([message.senderId isEqualToString:self.senderId]) {
|
2014-11-27 13:10:49 +01:00
|
|
|
if (message.messageState == TSOutgoingMessageStateUnsent || message.messageState == TSOutgoingMessageStateAttemptingOut) {
|
|
|
|
return self.outgoingMessageFailedImageData;
|
2014-12-09 22:18:39 +01:00
|
|
|
}
|
2014-11-25 16:38:33 +01:00
|
|
|
return self.outgoingBubbleImageData;
|
2014-10-29 21:58:58 +01:00
|
|
|
}
|
2015-01-22 05:08:12 +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-01-22 05:08:12 +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-01-22 05:08:12 +01:00
|
|
|
|
2014-11-29 19:54:33 +01:00
|
|
|
default:
|
|
|
|
NSLog(@"Something went wrong");
|
|
|
|
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-12-26 23:18:54 +01:00
|
|
|
cell.textView.selectable = NO;
|
2014-11-25 16:38:33 +01:00
|
|
|
cell.textView.linkTextAttributes = @{ NSForegroundColorAttributeName : cell.textView.textColor,
|
|
|
|
NSUnderlineStyleAttributeName : @(NSUnderlineStyleSingle | NSUnderlinePatternSolid) };
|
2014-10-29 21:58:58 +01:00
|
|
|
}
|
2015-01-22 05:08:12 +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];
|
|
|
|
cell.textView.selectable = NO;
|
2014-11-29 19:54:33 +01:00
|
|
|
cell.textView.linkTextAttributes = @{ NSForegroundColorAttributeName : cell.textView.textColor,
|
|
|
|
NSUnderlineStyleAttributeName : @(NSUnderlineStyleSingle | NSUnderlinePatternSolid) };
|
|
|
|
}
|
2015-01-22 05:08:12 +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-01-22 05:08:12 +01:00
|
|
|
|
2014-10-29 21:58:58 +01:00
|
|
|
return 0.0f;
|
|
|
|
}
|
|
|
|
|
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-01-22 05:08:12 +01:00
|
|
|
|
2014-11-25 21:33:29 +01:00
|
|
|
TSMessageAdapter *previousMessage = [self messageAtIndexPath:[NSIndexPath indexPathForItem:indexPath.row-1 inSection:indexPath.section]];
|
2015-01-22 05:08:12 +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-01-22 05:08:12 +01:00
|
|
|
|
2014-12-17 06:44:36 +01:00
|
|
|
if ([self showDateAtIndexPath:indexPath]) {
|
|
|
|
TSMessageAdapter *currentMessage = [self messageAtIndexPath:indexPath];
|
2015-01-22 05:08:12 +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-01-22 05:08:12 +01:00
|
|
|
|
2014-11-29 19:54:33 +01:00
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
-(BOOL)shouldShowMessageStatusAtIndexPath:(NSIndexPath*)indexPath
|
|
|
|
{
|
2015-01-22 05:08:12 +01:00
|
|
|
|
2014-12-17 06:44:36 +01:00
|
|
|
TSMessageAdapter *currentMessage = [self messageAtIndexPath:indexPath];
|
|
|
|
if([self.thread isKindOfClass:[TSGroupThread class]]) {
|
|
|
|
return currentMessage.messageType == TSIncomingMessageAdapter;
|
2014-12-04 15:01:05 +01:00
|
|
|
}
|
2014-12-17 06:44:36 +01:00
|
|
|
else {
|
|
|
|
if (indexPath.item == [self.collectionView numberOfItemsInSection:indexPath.section]-1) {
|
|
|
|
return [self isMessageOutgoingAndDelivered:currentMessage];
|
|
|
|
}
|
2015-01-22 05:08:12 +01:00
|
|
|
|
2014-12-17 06:44:36 +01:00
|
|
|
if (![self isMessageOutgoingAndDelivered:currentMessage]) {
|
|
|
|
return NO;
|
|
|
|
}
|
2015-01-22 05:08:12 +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-01-22 05:08:12 +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-01-22 05:08:12 +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];
|
|
|
|
if ([self shouldShowMessageStatusAtIndexPath:indexPath]) {
|
|
|
|
if([self.thread isKindOfClass:[TSGroupThread class]]) {
|
|
|
|
NSTextAttachment *textAttachment = [[NSTextAttachment alloc] init];
|
|
|
|
textAttachment.bounds = CGRectMake(0, 0, 11.0f, 10.0f);
|
|
|
|
NSString *name = [[Environment getCurrent].contactsManager nameStringForPhoneIdentifier:msg.senderId];
|
|
|
|
name = name ? name : msg.senderId;
|
|
|
|
NSMutableAttributedString * attrStr = [[NSMutableAttributedString alloc]initWithString:name];
|
|
|
|
[attrStr appendAttributedString:[NSAttributedString attributedStringWithAttachment:textAttachment]];
|
2015-01-22 05:08:12 +01:00
|
|
|
|
2014-12-17 06:44:36 +01:00
|
|
|
return (NSAttributedString*)attrStr;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
_lastDeliveredMessageIndexPath = indexPath;
|
|
|
|
NSTextAttachment *textAttachment = [[NSTextAttachment alloc] init];
|
|
|
|
textAttachment.bounds = CGRectMake(0, 0, 11.0f, 10.0f);
|
|
|
|
NSMutableAttributedString * attrStr = [[NSMutableAttributedString alloc]initWithString:@"Delivered"];
|
|
|
|
[attrStr appendAttributedString:[NSAttributedString attributedStringWithAttachment:textAttachment]];
|
2015-01-22 05:08:12 +01:00
|
|
|
|
2014-12-17 06:44:36 +01:00
|
|
|
return (NSAttributedString*)attrStr;
|
|
|
|
}
|
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
|
|
|
|
{
|
2014-11-29 19:54:33 +01:00
|
|
|
TSMessageAdapter * msg = [self messageAtIndexPath:indexPath];
|
2014-12-17 06:44:36 +01:00
|
|
|
if([self.thread isKindOfClass:[TSGroupThread class]]) {
|
|
|
|
if(msg.messageType == TSIncomingMessageAdapter) {
|
|
|
|
return 16.0f;
|
|
|
|
}
|
|
|
|
}
|
2014-12-29 23:55:57 +01:00
|
|
|
else if (msg.messageType == TSOutgoingMessageAdapter) {
|
2014-11-29 19:54:33 +01:00
|
|
|
return 16.0f;
|
|
|
|
}
|
2015-01-22 05:08:12 +01:00
|
|
|
|
2014-11-29 19:54:33 +01:00
|
|
|
return 0.0f;
|
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-01-22 05:08:12 +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-01-22 05:08:12 +01:00
|
|
|
|
2014-12-11 00:05:41 +01:00
|
|
|
BOOL isMediaMessage = [messageItem isMediaMessage];
|
2015-01-22 05:08:12 +01:00
|
|
|
|
2014-12-11 00:05:41 +01:00
|
|
|
if (isMediaMessage) {
|
2015-01-14 22:30:01 +01:00
|
|
|
if([[messageItem media] isKindOfClass:[TSAttachmentAdapter class]]) {
|
|
|
|
TSAttachmentAdapter* messageMedia = (TSAttachmentAdapter*)[messageItem media];
|
2015-01-22 05:08:12 +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-01-22 05:08:12 +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-01-22 05:08:12 +01:00
|
|
|
|
2015-01-14 22:30:01 +01:00
|
|
|
[self presentViewController:vc animated:YES completion:^{
|
|
|
|
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent];
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
} 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-22 05:08:12 +01:00
|
|
|
|
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-01-22 05:08:12 +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]]) {
|
|
|
|
_videoPlayer = [[MPMoviePlayerController alloc] initWithContentURL:attStream.mediaURL];
|
2015-01-14 22:30:01 +01:00
|
|
|
[_videoPlayer prepareToPlay];
|
|
|
|
|
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
|
|
selector:@selector(moviePlayBackDidFinish:)
|
|
|
|
name:MPMoviePlayerPlaybackDidFinishNotification
|
|
|
|
object: _videoPlayer];
|
2015-01-22 05:08:12 +01:00
|
|
|
|
2015-01-14 22:30:01 +01:00
|
|
|
_videoPlayer.controlStyle = MPMovieControlStyleDefault;
|
|
|
|
_videoPlayer.shouldAutoplay = YES;
|
|
|
|
[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];
|
|
|
|
|
|
|
|
// 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];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
_audioPlayerPoller = [NSTimer scheduledTimerWithTimeInterval:.01 target:self selector:@selector(audioPlayerUpdated:) userInfo:@{@"adapter": messageMedia} repeats:YES];
|
|
|
|
} 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];
|
|
|
|
[_audioPlayer prepareToPlay];
|
|
|
|
[_audioPlayer play];
|
|
|
|
[messageMedia setAudioIconToPause];
|
|
|
|
_audioPlayer.delegate = self;
|
|
|
|
_audioPlayerPoller = [NSTimer scheduledTimerWithTimeInterval:.01 target:self selector:@selector(audioPlayerUpdated:) userInfo:@{@"adapter": messageMedia} repeats:YES];
|
|
|
|
}
|
|
|
|
}
|
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:
|
|
|
|
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-01-14 22:30:01 +01:00
|
|
|
-(NSURL*) changeFile:(NSURL*)originalFile toHaveExtension:(NSString*)extension {
|
|
|
|
NSFileManager *fileManager = [NSFileManager defaultManager];
|
|
|
|
NSString* newPath = [[originalFile path] stringByAppendingPathExtension:extension];
|
|
|
|
if (![fileManager fileExistsAtPath:newPath]) {
|
|
|
|
NSError *error = nil;
|
|
|
|
[fileManager createSymbolicLinkAtPath:newPath withDestinationPath:[originalFile path] error: &error];
|
|
|
|
return [NSURL URLWithString:newPath];
|
|
|
|
}
|
|
|
|
return originalFile;
|
|
|
|
}
|
|
|
|
-(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-01-22 05:08:12 +01:00
|
|
|
|
2014-12-31 13:22:40 +01:00
|
|
|
NSInteger item = (NSInteger)[self scrollToItem];
|
2015-01-22 05:08:12 +01:00
|
|
|
|
2014-12-31 13:22:40 +01:00
|
|
|
[self updateRangeOptionsForPage:self.page];
|
2015-01-22 05:08:12 +01:00
|
|
|
|
2014-12-31 13:22:40 +01:00
|
|
|
[self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
|
|
|
|
[self.messageMappings updateWithTransaction:transaction];
|
|
|
|
}];
|
2015-01-22 05:08:12 +01:00
|
|
|
|
2014-12-31 13:22:40 +01:00
|
|
|
[self updateLayoutForEarlierMessagesWithOffset:item];
|
2015-01-22 05:08:12 +01:00
|
|
|
|
2014-12-31 13:22:40 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
-(BOOL)shouldShowLoadEarlierMessages
|
|
|
|
{
|
|
|
|
__block BOOL show = YES;
|
2015-01-22 05:08:12 +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-01-22 05:08:12 +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-01-22 05:08:12 +01:00
|
|
|
|
2014-12-31 13:22:40 +01:00
|
|
|
[self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
|
2015-01-22 05:08:12 +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-01-22 05:08:12 +01:00
|
|
|
|
2014-12-31 13:22:40 +01:00
|
|
|
BOOL canLoadFullRange = numberOfMessagesToLoad >= kYapDatabaseRangeLength;
|
2015-01-22 05:08:12 +01:00
|
|
|
|
2014-12-31 13:22:40 +01:00
|
|
|
if (!canLoadFullRange) {
|
|
|
|
item = numberOfMessagesToLoad;
|
|
|
|
}
|
|
|
|
}];
|
2015-01-22 05:08:12 +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-01-22 05:08:12 +01:00
|
|
|
|
2014-12-31 13:22:40 +01:00
|
|
|
[self.collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:offset inSection:0] atScrollPosition:UICollectionViewScrollPositionTop animated:NO];
|
2015-01-22 05:08:12 +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-01-22 05:08:12 +01:00
|
|
|
|
2014-12-31 13:22:40 +01:00
|
|
|
rangeOptions.maxLength = kYapDatabaseRangeMaxLength;
|
|
|
|
rangeOptions.minLength = kYapDatabaseRangeMinLength;
|
2015-01-22 05:08:12 +01:00
|
|
|
|
2014-12-31 13:22:40 +01:00
|
|
|
[self.messageMappings setRangeOptions:rangeOptions forGroup:self.thread.uniqueId];
|
2015-01-22 05:08:12 +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-06 23:24:54 +01:00
|
|
|
[self.inputToolbar.contentView.textView resignFirstResponder];
|
2015-01-14 22:30:01 +01:00
|
|
|
[DJWActionSheet showInView:self.parentViewController.view withTitle:nil cancelButtonTitle:@"Cancel" destructiveButtonTitle:@"Delete" otherButtonTitles:@[@"Send again"] tapBlock:^(DJWActionSheet *actionSheet, NSInteger tappedButtonIndex) {
|
2014-12-11 00:05:41 +01:00
|
|
|
if (tappedButtonIndex == actionSheet.cancelButtonIndex) {
|
|
|
|
NSLog(@"User Cancelled");
|
|
|
|
} else if (tappedButtonIndex == actionSheet.destructiveButtonIndex) {
|
|
|
|
[self.editingDatabaseConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction){
|
|
|
|
[message removeWithTransaction:transaction];
|
|
|
|
}];
|
|
|
|
}else {
|
|
|
|
[[TSMessagesManager sharedManager] sendMessage:message inThread:self.thread];
|
|
|
|
[self finishSendingMessage];
|
|
|
|
}
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
2014-12-26 23:18:54 +01:00
|
|
|
- (void)deleteMessageAtIndexPath:(NSIndexPath*)indexPath {
|
|
|
|
[self.editingDatabaseConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
|
|
|
TSInteraction *interaction = [self interactionAtIndexPath:indexPath];
|
|
|
|
[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];
|
2014-12-11 00:05:41 +01:00
|
|
|
NSString *messageString = [NSString stringWithFormat:@"Do you want to accept %@'s new identity key: %@", _thread.name, newKeyFingerprint];
|
|
|
|
NSArray *actions = @[@"Accept new identity key", @"Copy new identity key to pasteboard"];
|
2015-01-22 05:08:12 +01:00
|
|
|
|
2015-01-14 22:30:01 +01:00
|
|
|
[self.inputToolbar.contentView.textView resignFirstResponder];
|
2015-01-05 03:15:18 +01:00
|
|
|
|
2015-01-14 22:30:01 +01:00
|
|
|
[DJWActionSheet showInView:self.parentViewController.view withTitle:messageString cancelButtonTitle:@"Cancel" destructiveButtonTitle:@"Delete" otherButtonTitles:actions tapBlock:^(DJWActionSheet *actionSheet, NSInteger tappedButtonIndex) {
|
2014-11-29 19:54:33 +01:00
|
|
|
if (tappedButtonIndex == actionSheet.cancelButtonIndex) {
|
|
|
|
NSLog(@"User Cancelled");
|
|
|
|
} 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-01-22 05:08:12 +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
|
|
|
|
*/
|
|
|
|
|
|
|
|
- (void)takePictureOrVideo
|
|
|
|
{
|
|
|
|
UIImagePickerController *picker = [[UIImagePickerController alloc] init];
|
|
|
|
picker.delegate = self;
|
|
|
|
picker.allowsEditing = NO;
|
|
|
|
picker.sourceType = UIImagePickerControllerSourceTypeCamera;
|
2015-01-22 05:08:12 +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];
|
2014-10-29 21:58:58 +01:00
|
|
|
[self presentViewController:picker animated:YES completion:NULL];
|
|
|
|
}
|
2015-01-22 05:08:12 +01:00
|
|
|
|
2014-10-29 21:58:58 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
-(void)chooseFromLibrary:(kMediaTypes)mediaType
|
|
|
|
{
|
|
|
|
UIImagePickerController *picker = [[UIImagePickerController alloc] init];
|
|
|
|
picker.delegate = self;
|
2015-01-06 07:06:19 +01:00
|
|
|
picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
|
2015-01-22 05:08:12 +01:00
|
|
|
|
2015-01-06 07:06:19 +01:00
|
|
|
if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary])
|
2014-10-29 21:58:58 +01:00
|
|
|
{
|
|
|
|
NSArray* pictureTypeArray = [[NSArray alloc] initWithObjects:(NSString *)kUTTypeImage, nil];
|
2015-01-22 05:08:12 +01:00
|
|
|
|
2014-10-29 21:58:58 +01:00
|
|
|
NSArray* videoTypeArray = [[NSArray alloc] initWithObjects:(NSString *)kUTTypeMovie, (NSString*)kUTTypeVideo, nil];
|
2015-01-22 05:08:12 +01:00
|
|
|
|
2014-10-29 21:58:58 +01:00
|
|
|
picker.mediaTypes = (mediaType == kMediaTypePicture) ? pictureTypeArray : videoTypeArray;
|
2015-01-22 05:08:12 +01:00
|
|
|
|
2014-10-29 21:58:58 +01:00
|
|
|
[self presentViewController:picker animated:YES completion:nil];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Dismissing UIImagePickerController
|
|
|
|
*/
|
|
|
|
|
|
|
|
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker {
|
|
|
|
[self dismissViewControllerAnimated:YES completion:nil];
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2014-11-29 19:54:33 +01:00
|
|
|
* Fetching data from UIImagePickerController
|
2014-10-29 21:58:58 +01:00
|
|
|
*/
|
2014-11-29 19:54:33 +01:00
|
|
|
|
2014-10-29 21:58:58 +01:00
|
|
|
-(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
|
|
|
|
{
|
2015-01-14 22:30:01 +01:00
|
|
|
|
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 {
|
2015-01-22 05:08:12 +01:00
|
|
|
|
2015-01-14 22:30:01 +01:00
|
|
|
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-01-22 05:08:12 +01:00
|
|
|
|
2015-01-14 22:30:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
-(void) sendMessageAttachment:(NSData*)attachmentData ofType:(NSString*)attachmentType {
|
|
|
|
TSOutgoingMessage *message = [[TSOutgoingMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] inThread:self.thread messageBody:nil attachments:[NSMutableArray array]];
|
2015-01-22 05:08:12 +01:00
|
|
|
|
2015-01-14 22:30:01 +01:00
|
|
|
[[TSMessagesManager sharedManager] sendAttachment:attachmentData contentType:attachmentType inMessage:message thread:self.thread];
|
|
|
|
[self finishSendingMessage];
|
2015-01-22 05:08:12 +01:00
|
|
|
|
2014-10-29 21:58:58 +01:00
|
|
|
[self dismissViewControllerAnimated:YES completion:nil];
|
2015-01-22 05:08:12 +01:00
|
|
|
|
2015-01-14 22:30:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
-(void)sendQualityAdjustedAttachment:(NSURL*)movieURL {
|
|
|
|
|
|
|
|
AVAsset *video = [AVAsset assetWithURL:movieURL];
|
|
|
|
AVAssetExportSession *exportSession = [AVAssetExportSession exportSessionWithAsset:video presetName:AVAssetExportPresetMediumQuality];
|
|
|
|
exportSession.shouldOptimizeForNetworkUse = YES;
|
|
|
|
exportSession.outputFileType = AVFileTypeMPEG4;
|
2015-01-22 05:08:12 +01:00
|
|
|
|
2015-01-14 22:30:01 +01:00
|
|
|
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
|
|
|
|
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-01-22 05:08:12 +01:00
|
|
|
|
2015-01-14 22:30:01 +01:00
|
|
|
NSURL *compressedVideoUrl = [NSURL fileURLWithPath:basePath];
|
|
|
|
long currentTime = [[NSDate date] timeIntervalSince1970];
|
|
|
|
NSString *strImageName = [NSString stringWithFormat:@"%ld",currentTime];
|
|
|
|
compressedVideoUrl=[compressedVideoUrl URLByAppendingPathComponent:[NSString stringWithFormat:@"%@.mp4",strImageName]];
|
2015-01-22 05:08:12 +01:00
|
|
|
|
2015-01-14 22:30:01 +01:00
|
|
|
exportSession.outputURL = compressedVideoUrl;
|
|
|
|
[exportSession exportAsynchronouslyWithCompletionHandler:^{
|
2015-01-22 05:08:12 +01:00
|
|
|
|
2015-01-14 22:30:01 +01:00
|
|
|
}];
|
|
|
|
while(exportSession.progress!=1){
|
|
|
|
|
|
|
|
}
|
|
|
|
[self sendMessageAttachment:[NSData dataWithContentsOfURL:compressedVideoUrl] ofType:@"video/mp4"];
|
2015-01-22 05:08:12 +01:00
|
|
|
|
2015-01-14 22:30:01 +01:00
|
|
|
#if 0
|
|
|
|
return [NSData dataWithContentsOfURL:movieURL];
|
|
|
|
#endif
|
|
|
|
#if 0
|
|
|
|
NSString *serializationQueueDescription = [NSString stringWithFormat:@"%@ serialization queue", self];
|
2015-01-22 05:08:12 +01:00
|
|
|
|
2015-01-14 22:30:01 +01:00
|
|
|
// Create the main serialization queue.
|
|
|
|
self.mainSerializationQueue = dispatch_queue_create([serializationQueueDescription UTF8String], NULL);
|
|
|
|
NSString *rwAudioSerializationQueueDescription = [NSString stringWithFormat:@"%@ rw audio serialization queue", self];
|
2015-01-22 05:08:12 +01:00
|
|
|
|
2015-01-14 22:30:01 +01:00
|
|
|
// Create the serialization queue to use for reading and writing the audio data.
|
|
|
|
self.rwAudioSerializationQueue = dispatch_queue_create([rwAudioSerializationQueueDescription UTF8String], NULL);
|
|
|
|
NSString *rwVideoSerializationQueueDescription = [NSString stringWithFormat:@"%@ rw video serialization queue", self];
|
2015-01-22 05:08:12 +01:00
|
|
|
|
2015-01-14 22:30:01 +01:00
|
|
|
// Create the serialization queue to use for reading and writing the video data.
|
|
|
|
self.rwVideoSerializationQueue = dispatch_queue_create([rwVideoSerializationQueueDescription UTF8String], NULL);
|
|
|
|
|
2015-01-22 05:08:12 +01:00
|
|
|
|
|
|
|
|
2015-01-14 22:30:01 +01:00
|
|
|
int videoWidth = 1920;
|
|
|
|
int videoHeight = 1920;
|
|
|
|
int desiredKeyframeInterval = 2;
|
|
|
|
int desiredBitrate = 3000;
|
|
|
|
NSError *error = nil;
|
|
|
|
AVAssetWriter *videoWriter = [[AVAssetWriter alloc] initWithURL:
|
|
|
|
[NSURL fileURLWithPath:@"hello"]
|
|
|
|
fileType:AVFileTypeQuickTimeMovie
|
|
|
|
error:&error];
|
|
|
|
NSParameterAssert(videoWriter);
|
|
|
|
|
2015-01-22 05:08:12 +01:00
|
|
|
|
2015-01-14 22:30:01 +01:00
|
|
|
NSDictionary* settings = @{AVVideoCodecKey:AVVideoCodecH264,
|
|
|
|
AVVideoCompressionPropertiesKey:@{AVVideoAverageBitRateKey:[NSNumber numberWithInt:desiredBitrate],AVVideoProfileLevelKey:AVVideoProfileLevelH264Main31},
|
|
|
|
AVVideoWidthKey: [NSNumber numberWithInt:videoWidth],
|
|
|
|
AVVideoHeightKey:[NSNumber numberWithInt:videoHeight]};
|
2015-01-22 05:08:12 +01:00
|
|
|
|
|
|
|
|
2015-01-14 22:30:01 +01:00
|
|
|
AVAssetWriterInput* writerInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:settings];
|
|
|
|
NSParameterAssert(writerInput);
|
|
|
|
NSParameterAssert([videoWriter canAddInput:writerInput]);
|
|
|
|
[videoWriter addInput:writerInput];
|
|
|
|
#endif
|
2015-01-22 05:08:12 +01:00
|
|
|
|
2015-01-14 22:30:01 +01:00
|
|
|
|
2014-11-25 16:38:33 +01:00
|
|
|
}
|
|
|
|
|
2014-12-26 21:17:43 +01:00
|
|
|
-(NSData*)qualityAdjustedAttachmentForImage:(UIImage*)image
|
|
|
|
{
|
|
|
|
return UIImageJPEGRepresentation([self adjustedImageSizedForSending:image], [self compressionRate]);
|
|
|
|
}
|
|
|
|
|
|
|
|
-(UIImage*)adjustedImageSizedForSending:(UIImage*)image
|
|
|
|
{
|
|
|
|
CGFloat correctedWidth;
|
|
|
|
switch ([Environment.preferences imageUploadQuality]) {
|
2014-12-26 23:18:54 +01:00
|
|
|
case TSImageQualityUncropped:
|
|
|
|
return image;
|
2015-01-22 05:08:12 +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-01-22 05:08:12 +01:00
|
|
|
|
2014-12-26 21:17:43 +01:00
|
|
|
return [self imageScaled:image toMaxSize:correctedWidth];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (UIImage*)imageScaled:(UIImage *)image toMaxSize:(CGFloat)size
|
|
|
|
{
|
|
|
|
CGFloat scaleFactor;
|
|
|
|
CGFloat aspectRatio = image.size.height / image.size.width;
|
2015-01-22 05:08:12 +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-01-22 05:08:12 +01:00
|
|
|
|
2014-12-26 21:17:43 +01:00
|
|
|
CGSize newSize = CGSizeMake(image.size.width * scaleFactor, image.size.height * scaleFactor);
|
2015-01-22 05:08:12 +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-01-22 05:08:12 +01:00
|
|
|
|
2014-12-26 21:17:43 +01:00
|
|
|
return updatedImage;
|
|
|
|
}
|
|
|
|
|
|
|
|
-(CGFloat)compressionRate
|
|
|
|
{
|
|
|
|
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.9f;
|
|
|
|
case TSImageQualityMedium:
|
|
|
|
return 0.5f;
|
|
|
|
case TSImageQualityLow:
|
|
|
|
return 0.3f;
|
|
|
|
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
|
|
|
|
2014-11-25 16:38:33 +01:00
|
|
|
- (void)yapDatabaseModified:(NSNotification *)notification
|
|
|
|
{
|
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];
|
|
|
|
[self initializeToolbars];
|
|
|
|
}];
|
|
|
|
}
|
2014-11-25 16:38:33 +01:00
|
|
|
// Process the notification(s),
|
|
|
|
// and get the change-set(s) as applies to my view and mappings configuration.
|
|
|
|
NSArray *notifications = [self.uiDatabaseConnection beginLongLivedReadTransaction];
|
|
|
|
NSArray *messageRowChanges = nil;
|
2015-01-22 05:08:12 +01:00
|
|
|
|
2014-11-25 16:38:33 +01:00
|
|
|
[[self.uiDatabaseConnection ext:TSMessageDatabaseViewExtensionName] getSectionChanges:nil
|
|
|
|
rowChanges:&messageRowChanges
|
|
|
|
forNotifications:notifications
|
|
|
|
withMappings:self.messageMappings];
|
2015-01-22 05:08:12 +01:00
|
|
|
|
2015-01-05 16:31:00 +01:00
|
|
|
__block BOOL scrollToBottom = NO;
|
2015-01-22 05:08:12 +01:00
|
|
|
|
2014-12-08 23:12:22 +01:00
|
|
|
if (!messageRowChanges) {
|
|
|
|
return;
|
|
|
|
}
|
2015-01-22 05:08:12 +01:00
|
|
|
|
2014-12-12 22:41:28 +01:00
|
|
|
[self.collectionView performBatchUpdates:^{
|
2015-01-22 05:08:12 +01:00
|
|
|
|
2014-12-12 22:41:28 +01:00
|
|
|
for (YapDatabaseViewRowChange *rowChange in messageRowChanges)
|
2014-12-06 17:45:42 +01:00
|
|
|
{
|
2014-12-12 22:41:28 +01:00
|
|
|
switch (rowChange.type)
|
2014-12-06 17:45:42 +01:00
|
|
|
{
|
2014-12-12 22:41:28 +01:00
|
|
|
case YapDatabaseViewChangeDelete :
|
|
|
|
{
|
2014-12-31 13:22:40 +01:00
|
|
|
TSInteraction * interaction = [self interactionAtIndexPath:rowChange.indexPath];
|
|
|
|
[[TSAdapterCacheManager sharedManager] clearCacheEntryForInteractionId:interaction.uniqueId];
|
2014-12-12 22:41:28 +01:00
|
|
|
[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-01-22 05:08:12 +01:00
|
|
|
|
2014-12-12 22:41:28 +01:00
|
|
|
if (_lastDeliveredMessageIndexPath) {
|
|
|
|
[rowsToUpdate addObject:_lastDeliveredMessageIndexPath];
|
|
|
|
}
|
2015-01-22 05:08:12 +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-01-22 05:08:12 +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) {
|
|
|
|
if (success) {
|
|
|
|
[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
|
|
|
|
- (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-01-22 05:08:12 +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-01-22 05:08:12 +01:00
|
|
|
|
2014-11-25 16:38:33 +01:00
|
|
|
message = [viewTransaction objectAtRow:row inSection:section withMappings:self.messageMappings];
|
|
|
|
NSParameterAssert(message != nil);
|
|
|
|
}];
|
2015-01-22 05:08:12 +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-01-22 05:08:12 +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-01-22 05:08:12 +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];
|
|
|
|
|
|
|
|
// Setup audio session
|
|
|
|
AVAudioSession *session = [AVAudioSession sharedInstance];
|
|
|
|
[session setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
|
|
|
|
|
|
|
|
|
|
|
|
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];
|
|
|
|
|
|
|
|
// 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 {
|
|
|
|
NSDictionary *dict = [timer userInfo];
|
|
|
|
TSVideoAttachmentAdapter *messageMedia = dict[@"adapter"];
|
|
|
|
double current = [_audioPlayer currentTime]/[_audioPlayer duration];
|
|
|
|
[messageMedia setAudioProgressFromFloat:(float)current];
|
|
|
|
NSTimeInterval duration = ([_audioPlayer duration] - [_audioPlayer currentTime]);
|
|
|
|
[messageMedia setDurationOfAudio:duration];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void) audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag{
|
|
|
|
// stop audio polling
|
|
|
|
[_audioPlayerPoller invalidate];
|
|
|
|
|
|
|
|
// 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) {
|
|
|
|
TSVideoAttachmentAdapter* msgMedia = (TSVideoAttachmentAdapter*)[msgAdapter media];
|
|
|
|
if ([msgMedia isAudio]) {
|
|
|
|
[msgMedia setAudioProgressFromFloat:0];
|
|
|
|
[msgMedia setAudioIconToPlay];
|
|
|
|
[msgMedia removeDurationLabel];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (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
|
|
|
|
|
|
|
|
-(void)didPressAccessoryButton:(UIButton *)sender
|
|
|
|
{
|
|
|
|
[self.inputToolbar.contentView.textView resignFirstResponder];
|
2015-01-22 05:08:12 +01:00
|
|
|
|
2014-11-25 16:38:33 +01:00
|
|
|
UIView *presenter = self.parentViewController.view;
|
2015-01-22 05:08:12 +01:00
|
|
|
|
2014-11-25 16:38:33 +01:00
|
|
|
[DJWActionSheet showInView:presenter
|
|
|
|
withTitle:nil
|
|
|
|
cancelButtonTitle:@"Cancel"
|
|
|
|
destructiveButtonTitle:nil
|
2015-01-14 22:30:01 +01:00
|
|
|
otherButtonTitles:@[@"Take Photo or Video", @"Choose existing Photo",@"Choose existing Video"]//,@"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");
|
2014-11-25 16:38:33 +01:00
|
|
|
}else {
|
|
|
|
switch (tappedButtonIndex) {
|
|
|
|
case 0:
|
|
|
|
[self takePictureOrVideo];
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
[self chooseFromLibrary:kMediaTypePicture];
|
|
|
|
break;
|
2015-01-14 22:30:01 +01:00
|
|
|
|
|
|
|
case 2:
|
|
|
|
[self chooseFromLibrary:kMediaTypeVideo];
|
|
|
|
break;
|
|
|
|
case 3:
|
|
|
|
[self recordAudio];
|
|
|
|
break;
|
2014-11-25 16:38:33 +01:00
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
2014-12-06 17:45:42 +01:00
|
|
|
- (void)markAllMessagesAsRead {
|
|
|
|
[self.uiDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
|
|
|
|
YapDatabaseViewTransaction *viewTransaction = [transaction ext:TSUnreadDatabaseViewExtensionName];
|
|
|
|
NSUInteger numberOfItemsInSection = [viewTransaction numberOfItemsInGroup:self.thread.uniqueId];
|
|
|
|
[self.editingDatabaseConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *writeTransaction) {
|
|
|
|
for (NSUInteger i = 0; i < numberOfItemsInSection; i++) {
|
|
|
|
TSIncomingMessage *message = [viewTransaction objectAtIndex:i inGroup:self.thread.uniqueId];
|
|
|
|
message.read = YES;
|
|
|
|
[message saveWithTransaction:writeTransaction];
|
|
|
|
}
|
|
|
|
}];
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
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-01-22 05:08:12 +01:00
|
|
|
|
2014-12-26 23:18:54 +01:00
|
|
|
return [super collectionView:collectionView canPerformAction:action forItemAtIndexPath:indexPath withSender:sender];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)collectionView:(UICollectionView *)collectionView performAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender
|
|
|
|
{
|
|
|
|
if (action == @selector(delete:)) {
|
|
|
|
[self deleteMessageAtIndexPath:indexPath];
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
[super collectionView:collectionView performAction:action forItemAtIndexPath:indexPath withSender:sender];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-14 22:30:01 +01:00
|
|
|
-(void)updateGroup {
|
|
|
|
[self.navController hideDropDown:self];
|
|
|
|
|
|
|
|
[self performSegueWithIdentifier:kUpdateGroupSegueIdentifier sender:self];
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-12-24 02:25:10 +01:00
|
|
|
- (void) leaveGroup {
|
2015-01-14 22:30:01 +01:00
|
|
|
[self.navController hideDropDown:self];
|
|
|
|
|
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;
|
|
|
|
[[TSMessagesManager sharedManager] sendMessage:message inThread:gThread];
|
|
|
|
[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 {
|
2014-12-24 02:25:10 +01:00
|
|
|
[self.editingDatabaseConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
|
|
|
TSGroupThread* gThread = [TSGroupThread getOrCreateThreadWithGroupModel:newGroupModel transaction:transaction];
|
|
|
|
gThread.groupModel = newGroupModel;
|
|
|
|
[gThread saveWithTransaction:transaction];
|
|
|
|
TSOutgoingMessage *message = [[TSOutgoingMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] inThread:gThread messageBody:@"" attachments:[[NSMutableArray alloc] init]];
|
|
|
|
message.groupMetaMessage = TSGroupMessageUpdate;
|
|
|
|
if(newGroupModel.groupImage!=nil) {
|
|
|
|
[[TSMessagesManager sharedManager] sendAttachment:UIImagePNGRepresentation(newGroupModel.groupImage) contentType:@"image/png" inMessage:message thread:gThread];
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
[[TSMessagesManager sharedManager] sendMessage:message inThread:gThread];
|
|
|
|
}
|
2015-01-22 05:08:12 +01:00
|
|
|
|
2014-12-24 02:25:10 +01:00
|
|
|
self.thread = gThread;
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (IBAction)unwindGroupUpdated:(UIStoryboardSegue *)segue{
|
|
|
|
[self.inputToolbar.contentView.textView resignFirstResponder];
|
|
|
|
NewGroupViewController *ngc = [segue sourceViewController];
|
2015-01-16 15:28:41 +01:00
|
|
|
TSGroupModel* newGroupModel = [ngc groupModel];
|
2014-12-24 02:25:10 +01:00
|
|
|
NSMutableArray* groupMemberIds = [[NSMutableArray alloc] initWithArray:newGroupModel.groupMemberIds];
|
|
|
|
[groupMemberIds addObject:[SignalKeyingStorage.localNumber toE164]];
|
|
|
|
newGroupModel.groupMemberIds = groupMemberIds;
|
|
|
|
[self updateGroupModelTo:newGroupModel];
|
|
|
|
}
|
|
|
|
|
2014-12-09 20:11:14 +01:00
|
|
|
- (void)dealloc{
|
|
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
2014-12-06 17:45:42 +01:00
|
|
|
}
|
|
|
|
|
2014-10-29 21:58:58 +01:00
|
|
|
@end
|