diff --git a/Classes/Base.lproj/ChatRoomViewController.xib b/Classes/Base.lproj/ChatRoomViewController.xib index 92c0c0cac..58c8256f6 100644 --- a/Classes/Base.lproj/ChatRoomViewController.xib +++ b/Classes/Base.lproj/ChatRoomViewController.xib @@ -1,31 +1,26 @@ - + - + - - - - - @@ -90,46 +85,11 @@ - - + @@ -235,17 +195,6 @@ - @@ -264,16 +213,12 @@ - - - - diff --git a/Classes/ChatRoomTableViewController.h b/Classes/ChatRoomTableViewController.h index afc1c75fa..f8e317dca 100644 --- a/Classes/ChatRoomTableViewController.h +++ b/Classes/ChatRoomTableViewController.h @@ -23,7 +23,6 @@ @protocol ChatRoomDelegate -- (BOOL)chatRoomStartImageDownload:(LinphoneChatMessage*)msg; - (BOOL)chatRoomStartImageUpload:(UIImage*)image url:(NSURL*)url; - (void)resendChat:(NSString*)message withExternalUrl:(NSString*)url; diff --git a/Classes/ChatRoomTableViewController.m b/Classes/ChatRoomTableViewController.m index 88b4d24f9..2cc654e1e 100644 --- a/Classes/ChatRoomTableViewController.m +++ b/Classes/ChatRoomTableViewController.m @@ -63,6 +63,16 @@ if( !chatRoom ) return; [self clearMessageList]; self->messageList = linphone_chat_room_get_history(chatRoom, 0); + + // also append transient upload messages because they are not in history yet! + for (FileTransferDelegate *ftd in [[LinphoneManager instance] fileTransferDelegates]) { + if (linphone_chat_room_get_peer_address(linphone_chat_message_get_chat_room(ftd.message)) == + linphone_chat_room_get_peer_address(chatRoom) && + linphone_chat_message_is_outgoing(ftd.message)) { + LOGI(@"Appending transient upload message %p", ftd.message); + self->messageList = ms_list_append(self->messageList, ftd.message); + } + } } - (void)reloadData { @@ -76,9 +86,10 @@ messageList = ms_list_append(messageList, linphone_chat_message_ref(chat)); int pos = ms_list_size(messageList) - 1; - [self.tableView beginUpdates]; - [self.tableView insertRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:pos inSection:0]] withRowAnimation:UITableViewRowAnimationFade]; - [self.tableView endUpdates]; + NSIndexPath *indexPath = [NSIndexPath indexPathForRow:pos inSection:0]; + [self.tableView beginUpdates]; + [self.tableView insertRowsAtIndexPaths:@[ indexPath ] withRowAnimation:UITableViewRowAnimationFade]; + [self.tableView endUpdates]; } - (void)updateChatEntry:(LinphoneChatMessage*)chat { diff --git a/Classes/ChatRoomViewController.h b/Classes/ChatRoomViewController.h index ef59ea284..763018847 100644 --- a/Classes/ChatRoomViewController.h +++ b/Classes/ChatRoomViewController.h @@ -24,14 +24,12 @@ #import "ChatRoomTableViewController.h" #import "HPGrowingTextView.h" #import "ImagePickerViewController.h" -#import "ImageSharing.h" #import "OrderedDictionary.h" #include "linphone/linphonecore.h" -@interface ChatRoomViewController : UIViewController { +@interface ChatRoomViewController : UIViewController { LinphoneChatRoom *chatRoom; - ImageSharing *imageSharing; OrderedDictionary *imageQualities; BOOL scrollOnGrowingEnabled; BOOL composingVisible; @@ -54,17 +52,12 @@ @property (strong, nonatomic) IBOutlet UIView *composeIndicatorView; @property (nonatomic, strong) IBOutlet UIButton* pictureButton; -@property (nonatomic, strong) IBOutlet UIButton* cancelTransferButton; -@property (nonatomic, strong) IBOutlet UIProgressView* imageTransferProgressBar; -@property (nonatomic, strong) IBOutlet UIView* transferView; -@property (nonatomic, strong) IBOutlet UIView* waitView; - (IBAction)onBackClick:(id)event; - (IBAction)onEditClick:(id)event; - (IBAction)onMessageChange:(id)sender; - (IBAction)onSendClick:(id)event; - (IBAction)onPictureClick:(id)event; -- (IBAction)onTransferCancelClick:(id)event; - (IBAction)onListTap:(id)sender; - (void)setChatRoom:(LinphoneChatRoom*)room; diff --git a/Classes/ChatRoomViewController.m b/Classes/ChatRoomViewController.m index 4c46cd65b..5509d9efa 100644 --- a/Classes/ChatRoomViewController.m +++ b/Classes/ChatRoomViewController.m @@ -21,20 +21,14 @@ #import "PhoneMainView.h" #import "DTActionSheet.h" #import "UILinphone.h" -//#import "UIAlertView+Blocks.h" #import "DTAlertView.h" - +#import "Utils/FileTransferDelegate.h" #import #import #import "Utils.h" +#import "UIChatRoomCell.h" -@implementation ChatRoomViewController { - /* Message transfer transient storage */ - NSData* upload_data; - size_t upload_bytes_sent; - - NSMutableData* download_data; -} +@implementation ChatRoomViewController @synthesize tableController; @synthesize sendButton; @@ -52,32 +46,23 @@ @synthesize listTapGestureRecognizer; @synthesize listSwipeGestureRecognizer; @synthesize pictureButton; -@synthesize imageTransferProgressBar; -@synthesize cancelTransferButton; -@synthesize transferView; -@synthesize waitView; #pragma mark - Lifecycle Functions - (id)init { - self = [super initWithNibName:@"ChatRoomViewController" bundle:[NSBundle mainBundle]]; - if (self != nil) { - self->scrollOnGrowingEnabled = TRUE; - self->chatRoom = NULL; - self->imageSharing = NULL; - self->listTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onListTap:)]; - self.listSwipeGestureRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(onListSwipe:)]; - self->imageQualities = [[OrderedDictionary alloc] initWithObjectsAndKeys: - [NSNumber numberWithFloat:0.9], NSLocalizedString(@"Maximum", nil), - [NSNumber numberWithFloat:0.5], NSLocalizedString(@"Average", nil), - [NSNumber numberWithFloat:0.0], NSLocalizedString(@"Minimum", nil), nil]; - self->composingVisible = TRUE; - - self->upload_data = nil; - self->upload_bytes_sent = 0; - self->download_data = nil; - } - return self; + self = [super initWithNibName:@"ChatRoomViewController" bundle:[NSBundle mainBundle]]; + if (self != nil) { + self->scrollOnGrowingEnabled = TRUE; + self->chatRoom = NULL; + self->listTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onListTap:)]; + self.listSwipeGestureRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(onListSwipe:)]; + self->imageQualities = [[OrderedDictionary alloc] initWithObjectsAndKeys: + [NSNumber numberWithFloat:0.9], NSLocalizedString(@"Maximum", nil), + [NSNumber numberWithFloat:0.5], NSLocalizedString(@"Average", nil), + [NSNumber numberWithFloat:0.0], NSLocalizedString(@"Minimum", nil), nil]; + self->composingVisible = TRUE; + } + return self; } - (void)dealloc { @@ -168,6 +153,7 @@ static UICompositeViewDescription *compositeDescription = nil; selector:@selector(textComposeEvent:) name:kLinphoneTextComposeEvent object:nil]; + if([tableController isEditing]) [tableController setEditing:FALSE animated:FALSE]; [editButton setOff]; @@ -179,39 +165,16 @@ static UICompositeViewDescription *compositeDescription = nil; BOOL fileSharingEnabled = [[LinphoneManager instance] lpConfigStringForKey:@"sharing_server_preference"] != NULL && [[[LinphoneManager instance] lpConfigStringForKey:@"sharing_server_preference"] length]>0; [pictureButton setEnabled:fileSharingEnabled]; - [waitView setHidden:TRUE]; } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; - if(upload_data || download_data ) { - // TODO: when the API permits it, we should cancel the transfer. - } - [messageField resignFirstResponder]; [self setComposingVisible:FALSE withDelay:0]; // will hide the "user is composing.." message - [[NSNotificationCenter defaultCenter] removeObserver:self - name:UIApplicationDidBecomeActiveNotification - object:nil]; - - [[NSNotificationCenter defaultCenter] removeObserver:self - name:UIKeyboardWillShowNotification - object:nil]; - [[NSNotificationCenter defaultCenter] removeObserver:self - name:UIKeyboardWillHideNotification - object:nil]; - [[NSNotificationCenter defaultCenter] removeObserver:self - name:kLinphoneTextReceived - object:nil]; - [[NSNotificationCenter defaultCenter] removeObserver:self - name:UITextViewTextDidChangeNotification - object:nil]; - [[NSNotificationCenter defaultCenter] removeObserver:self - name:kLinphoneTextComposeEvent - object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self]; } - (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation { @@ -298,9 +261,9 @@ static UICompositeViewDescription *compositeDescription = nil; } static void message_status(LinphoneChatMessage* msg,LinphoneChatMessageState state,void* ud) { - ChatRoomViewController* thiz = (__bridge ChatRoomViewController*)ud; - const char*text = linphone_chat_message_get_text(msg); + const char *text = (linphone_chat_message_get_file_transfer_information(msg) != NULL) ? "photo transfer" : linphone_chat_message_get_text(msg); LOGI(@"Delivery status for [%s] is [%s]",text,linphone_chat_message_state_to_string(state)); + ChatRoomViewController* thiz = (__bridge ChatRoomViewController*)ud; [thiz.tableController updateChatEntry:msg]; } @@ -332,8 +295,6 @@ static void message_status(LinphoneChatMessage* msg,LinphoneChatMessageState sta } - (void)chooseImageQuality:(UIImage*)image url:(NSURL*)url { - [waitView setHidden:FALSE]; - DTActionSheet *sheet = [[DTActionSheet alloc] initWithTitle:NSLocalizedString(@"Choose the image size", nil)]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ //UIImage *image = [original_image normalizedImage]; @@ -349,7 +310,6 @@ static void message_status(LinphoneChatMessage* msg,LinphoneChatMessageState sta } [sheet addCancelButtonWithTitle:NSLocalizedString(@"Cancel", nil) block:nil]; dispatch_async(dispatch_get_main_queue(), ^{ - [waitView setHidden:TRUE]; [sheet showInView:[PhoneMainView instance].view]; }); }); @@ -482,20 +442,8 @@ static void message_status(LinphoneChatMessage* msg,LinphoneChatMessageState sta #pragma mark - Action Functions - (IBAction)onBackClick:(id)event { - if( upload_data != nil || download_data != nil ){ - DTAlertView *alertView = [[DTAlertView alloc] initWithTitle:NSLocalizedString(@"Cancel transfer?", nil) - message:NSLocalizedString(@"You have a transfer in progress, leaving this view will cancel it. Are you sure?", nil)]; - [alertView addCancelButtonWithTitle:NSLocalizedString(@"Cancel", nil) - block:nil]; - [alertView addButtonWithTitle:NSLocalizedString(@"Yes", nil) - block:^{ - [self.tableController setChatRoom:NULL]; - [[PhoneMainView instance] popCurrentView]; - }]; - } else { - [self.tableController setChatRoom:NULL]; - [[PhoneMainView instance] popCurrentView]; - } + [self.tableController setChatRoom:NULL]; + [[PhoneMainView instance] popCurrentView]; } - (IBAction)onEditClick:(id)event { @@ -573,76 +521,21 @@ static void message_status(LinphoneChatMessage* msg,LinphoneChatMessageState sta [sheet showInView:[PhoneMainView instance].view]; } -- (IBAction)onTransferCancelClick:(id)event { - if(imageSharing) { - [imageSharing cancel]; - } -} - - #pragma mark ChatRoomDelegate -- (BOOL)chatRoomStartImageDownload:(LinphoneChatMessage*)msg { - if(self->download_data == nil) { - const char* url = linphone_chat_message_get_external_body_url(msg); - LOGI(@"Content to download: %s", url); - - if( url == nil ) return FALSE; - - download_data = [[NSMutableData alloc] init]; - - linphone_chat_message_set_user_data(msg, (void*)CFBridgingRetain(self)); - linphone_chat_message_start_file_download(msg,NULL,NULL); - - [messageView setHidden:TRUE]; - [transferView setHidden:FALSE]; - return TRUE; - } - return FALSE; -} - - (BOOL)chatRoomStartImageUpload:(UIImage*)image url:(NSURL*)url{ - if( self->upload_data == nil) { - LinphoneContent* content = linphone_core_create_content(linphone_chat_room_get_lc(chatRoom)); - self->upload_data = UIImageJPEGRepresentation(image, 1.0); - linphone_content_set_type(content, "image"); - linphone_content_set_subtype(content, "jpeg"); - linphone_content_set_name(content, [[NSString stringWithFormat:@"%li-%f.jpg", (long)[image hash],[NSDate timeIntervalSinceReferenceDate]] UTF8String]); - linphone_content_set_size(content, [self->upload_data length]); - - LinphoneChatMessage* message = linphone_chat_room_create_file_transfer_message(chatRoom, content); - linphone_chat_message_set_user_data(message, (void*)CFBridgingRetain(self)); - - if ( url ) { - // internal url is saved in the appdata for display and later save - [LinphoneManager setValueInMessageAppData:[url absoluteString] forKey:@"localimage" inMessage:message]; - } - - // TODO: in the user data, we should maybe put a standalone delegate alloced and retained, instead of self. - // This will make sure that when receiving a memory alert or go to another view, we still send the message - linphone_chat_room_send_message2(chatRoom, message, message_status, (void*)CFBridgingRetain(self)); - - [tableController addChatEntry:linphone_chat_message_ref(message)]; - [tableController scrollToBottom:true]; - [messageView setHidden:TRUE]; - [transferView setHidden:FALSE]; - return TRUE; - } - return FALSE; + FileTransferDelegate * fileTransfer = [[FileTransferDelegate alloc] init]; + [[[LinphoneManager instance] fileTransferDelegates] addObject:fileTransfer]; + [fileTransfer upload:image withURL:url forChatRoom:chatRoom]; + [tableController addChatEntry:linphone_chat_message_ref(fileTransfer.message)]; + [tableController scrollToBottom:true]; + return TRUE; } - (void)resendChat:(NSString *)message withExternalUrl:(NSString *)url { [self sendMessage:message withExterlBodyUrl:[NSURL URLWithString:url] withInternalURL:nil]; } -#pragma mark ImageSharingDelegate - -- (void)imageSharingAborted:(ImageSharing*)aimageSharing { - [messageView setHidden:FALSE]; - [transferView setHidden:TRUE]; - imageSharing = nil; -} - #pragma mark ImagePickerDelegate - (void)imagePickerDelegateImage:(UIImage*)image info:(NSDictionary *)info { @@ -659,94 +552,6 @@ static void message_status(LinphoneChatMessage* msg,LinphoneChatMessageState sta [self chooseImageQuality:image url:url]; } -#pragma mark - LinphoneChatContentTransferDelegate - -- (void)onProgressReport:(LinphoneChatMessage *)msg forContent:(const LinphoneContent *)content percent:(int)percent { - [imageTransferProgressBar setProgress:percent/100.0f]; -} - -- (void)onDataRequested:(LinphoneChatMessage *)msg forContent:(const LinphoneContent *)content buffer:(char *)buffer withSize:(size_t *)size { - if( self->upload_data ){ - size_t to_send = *size; - size_t remaining = [upload_data length] - self->upload_bytes_sent; - - LOGI(@"Asking %ld bytes, sent %ld of %d, %ld remaining", to_send, self->upload_bytes_sent, [upload_data length], remaining); - - if( remaining < to_send ) to_send = remaining; - - @try { - [upload_data getBytes:(void*)buffer range:NSMakeRange(upload_bytes_sent, to_send)]; - upload_bytes_sent += to_send; - *size = to_send; - } - @catch (NSException *exception) { - LOGE(@"Exception: %@", exception); - } - - - if( to_send == 0 || upload_bytes_sent == [upload_data length] ){ - LOGI(@"Upload finished, cleanup.."); - upload_data = nil; - upload_bytes_sent = 0; - - // update UI - dispatch_async(dispatch_get_main_queue(), ^{ - [messageView setHidden:FALSE]; - [transferView setHidden:TRUE]; - }); - } - - } else { - LOGE(@"Error: no upload data in progress!"); - } -} - -- (void)onDataReceived:(LinphoneChatMessage *)msg forContent:(const LinphoneContent *)content buffer:(const char *)buffer withSize:(size_t)size { - if( download_data ){ - LOGI(@"Receiving data for %s : %zu bytes, already got %d of %zu", linphone_content_get_name(content), size, [download_data length], linphone_content_get_size(content)); - - if( size != 0 ){ - [download_data appendBytes:buffer length:size]; - } - - if( size == 0 && [download_data length] == linphone_content_get_size(content)){ - - LOGI(@"Transfer is finished, save image and update chat"); - - dispatch_async(dispatch_get_main_queue(), ^{ - //we're finished, save the image and update the message - [messageView setHidden:FALSE]; - [transferView setHidden:TRUE]; - - UIImage* image = [UIImage imageWithData:download_data]; - - download_data = nil; - - [[LinphoneManager instance].photoLibrary - writeImageToSavedPhotosAlbum:image.CGImage - orientation:(ALAssetOrientation)[image imageOrientation] - completionBlock:^(NSURL *assetURL, NSError *error){ - if (error) { - LOGE(@"Cannot save image data downloaded [%@]", [error localizedDescription]); - - UIAlertView* errorAlert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Transfer error", nil) - message:NSLocalizedString(@"Cannot write image to photo library", nil) - delegate:nil - cancelButtonTitle:NSLocalizedString(@"Ok",nil) - otherButtonTitles:nil ,nil]; - [errorAlert show]; - return; - } - LOGI(@"Image saved to [%@]", [assetURL absoluteString]); - [LinphoneManager setValueInMessageAppData:[assetURL absoluteString] forKey:@"localimage" inMessage:msg]; - [tableController updateChatEntry:msg]; - }]; - - }); - } - } -} - #pragma mark - Keyboard Event Functions - (void)keyboardWillHide:(NSNotification *)notif { diff --git a/Classes/ChatTableViewController.m b/Classes/ChatTableViewController.m index 23097283e..c5f38c70a 100644 --- a/Classes/ChatTableViewController.m +++ b/Classes/ChatTableViewController.m @@ -64,43 +64,44 @@ static int sorted_history_comparison(LinphoneChatRoom *to_insert, LinphoneChatRo LinphoneChatMessage* last_elem_message = linphone_chat_room_get_user_data(elem); if( last_new_message && last_elem_message ){ - time_t new = linphone_chat_message_get_time(last_new_message); - time_t old = linphone_chat_message_get_time(last_elem_message); - if ( new < old ) return 1; - else if ( new > old) return -1; - } - return 0; + time_t new = linphone_chat_message_get_time(last_new_message); + time_t old = linphone_chat_message_get_time(last_elem_message); + if (new < old) + return 1; + else if (new > old) + return -1; + } + return 0; } - (MSList*)sortChatRooms { - MSList* sorted = nil; - MSList* unsorted = linphone_core_get_chat_rooms([LinphoneManager getLc]); - MSList* iter = unsorted; + MSList *sorted = nil; + MSList *unsorted = linphone_core_get_chat_rooms([LinphoneManager getLc]); + MSList *iter = unsorted; - while (iter) { - // store last message in user data - MSList* history = linphone_chat_room_get_history(iter->data, 1); - LinphoneChatMessage* last_msg = history? history->data : NULL; - if( last_msg ){ - linphone_chat_room_set_user_data(iter->data, linphone_chat_message_ref(last_msg)); - } - ms_list_free_with_data(history, (void (*)(void *))linphone_chat_message_unref); + while (iter) { + // store last message in user data + LinphoneChatRoom *chat_room = iter->data; + MSList *history = linphone_chat_room_get_history(iter->data, 1); + assert(ms_list_size(history) <= 1); + LinphoneChatMessage *last_msg = history ? history->data : NULL; + if (last_msg) { + linphone_chat_message_ref(last_msg); + linphone_chat_room_set_user_data(chat_room, last_msg); + } + sorted = ms_list_insert_sorted(sorted, chat_room, (MSCompareFunc)sorted_history_comparison); - sorted = ms_list_insert_sorted(sorted, - iter->data, - (MSCompareFunc)sorted_history_comparison); - - iter = iter->next; - } - return sorted; + iter = iter->next; + } + return sorted; } static void chatTable_free_chatrooms(void *data){ - LinphoneChatMessage* lastMsg = linphone_chat_room_get_user_data(data); - if( lastMsg ){ - linphone_chat_message_unref(lastMsg); - linphone_chat_room_set_user_data(data, NULL); - } + LinphoneChatMessage *lastMsg = linphone_chat_room_get_user_data(data); + if (lastMsg) { + linphone_chat_message_unref(lastMsg); + linphone_chat_room_set_user_data(data, NULL); + } } - (void)loadData { diff --git a/Classes/ImageSharing.h b/Classes/ImageSharing.h deleted file mode 100644 index e222e747d..000000000 --- a/Classes/ImageSharing.h +++ /dev/null @@ -1,52 +0,0 @@ -/* ImageSharing.h - * - * Copyright (C) 2012 Belledonne Comunications, Grenoble, France - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - */ - -#import - -@class ImageSharing; - -@protocol ImageSharingDelegate - -- (void)imageSharingProgress:(ImageSharing*)imageSharing progress:(float)progress; -- (void)imageSharingAborted:(ImageSharing*)imageSharing; -- (void)imageSharingError:(ImageSharing*)imageSharing error:(NSError *)error; -- (void)imageSharingUploadDone:(ImageSharing*)imageSharing url:(NSURL*)url; -- (void)imageSharingDownloadDone:(ImageSharing*)imageSharing image:(UIImage *)image; - -@end - -@interface ImageSharing : NSObject { -@private - NSInteger totalBytesExpectedToRead; - id delegate; - NSInteger statusCode; -} - -+ (id)newImageSharingUpload:(NSURL*)url image:(UIImage*)image delegate:(id)delegate userInfo:(id)userInfo; -+ (id)newImageSharingDownload:(NSURL*)url delegate:(id)delegate userInfo:(id)userInfo; - -- (void)cancel; - -@property (nonatomic, strong) id userInfo; - -@property (nonatomic, readonly) BOOL upload; -@property (nonatomic, readonly) NSMutableData* data; -@property (nonatomic, readonly) NSURLConnection* connection; - -@end diff --git a/Classes/ImageSharing.m b/Classes/ImageSharing.m deleted file mode 100644 index 3ebee1b80..000000000 --- a/Classes/ImageSharing.m +++ /dev/null @@ -1,176 +0,0 @@ -/* ImageSharing.m - * - * Copyright (C) 2012 Belledonne Comunications, Grenoble, France - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - */ - -#import "ImageSharing.h" -#import "Utils.h" -#import "LinphoneManager.h" - -@implementation ImageSharing - -@synthesize connection; -@synthesize data; -@synthesize upload; -@synthesize userInfo; - -#pragma mark - Lifecycle Functions - -+ (id)newImageSharingUpload:(NSURL*)url image:(UIImage*)image delegate:(id)delegate userInfo:(id)auserInfo{ - ImageSharing *imgs = [[ImageSharing alloc] init]; - if(imgs != nil) { - imgs.userInfo = auserInfo; - imgs->upload = TRUE; - imgs->delegate = delegate; - imgs->data = [[NSMutableData alloc] init]; - if(delegate) { - [delegate imageSharingProgress:imgs progress:0]; - } - [imgs uploadImageTo:url image:image]; - } - return imgs; -} - -+ (id)newImageSharingDownload:(NSURL*)url delegate:(id)delegate userInfo:(id)auserInfo{ - ImageSharing *imgs = [[ImageSharing alloc] init]; - if(imgs != nil) { - imgs.userInfo = auserInfo; - imgs->upload = FALSE; - imgs->delegate = delegate; - imgs->data = [[NSMutableData alloc] init]; - if(delegate) { - [delegate imageSharingProgress:imgs progress:0]; - } - [imgs downloadImageFrom:url]; - } - return imgs; -} - - - -#pragma mark - - -- (void)cancel { - [connection cancel]; - LOGI(@"File transfer interrupted by user"); - if(delegate) { - [delegate imageSharingAborted:self]; - } -} - - -- (void)downloadImageFrom:(NSURL*)url { - LOGI(@"downloading [%@]", [url absoluteString]); - - NSURLRequest* request = [NSURLRequest requestWithURL:url - cachePolicy:NSURLRequestUseProtocolCachePolicy - timeoutInterval:60.0]; - - connection = [[NSURLConnection alloc] initWithRequest:request delegate: self]; -} - - -- (void)uploadImageTo:(NSURL*)url image:(UIImage*)image { - LOGI(@"downloading [%@]", [url absoluteString]); - // setting up the request object now - NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init]; - [request setURL:url]; - [request setHTTPMethod:@"POST"]; - - /* - add some header info now - we always need a boundary when we post a file - also we need to set the content type - - You might want to generate a random boundary.. this is just the same - as my output from wireshark on a valid html post - */ - NSString *boundary = @"---------------------------14737809831466499882746641449"; - NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@",boundary]; - [request addValue:contentType forHTTPHeaderField: @"Content-Type"]; - - /* - now lets create the body of the post - */ - NSMutableData *body = [NSMutableData data]; - NSString *imageName = [NSString stringWithFormat:@"%lu-%f.jpg", (unsigned long)[image hash],[NSDate timeIntervalSinceReferenceDate]]; - [body appendData:[[NSString stringWithFormat:@"\r\n--%@\r\n",boundary] dataUsingEncoding:NSUTF8StringEncoding]]; - [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"userfile\"; filename=\"%@\"\r\n",imageName] dataUsingEncoding:NSUTF8StringEncoding]]; - [body appendData:[@"Content-Type: application/octet-stream\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]]; - [body appendData:[NSData dataWithData:UIImageJPEGRepresentation(image, 1.0)]]; - [body appendData:[[NSString stringWithFormat:@"\r\n--%@--\r\n",boundary] dataUsingEncoding:NSUTF8StringEncoding]]; - [request setHTTPBody:body]; - - connection = [[NSURLConnection alloc] initWithRequest:(NSURLRequest *)request delegate:self]; -} - - -#pragma mark - NSURLConnectionDelegate - -- (void)connection:(NSURLConnection *)aconnection didFailWithError:(NSError *)error { - if(delegate) { - [delegate imageSharingError:self error:error]; - } -} - -- (void)connection:(NSURLConnection *)connection didSendBodyData:(NSInteger)bytesWritten totalBytesWritten:(NSInteger)totalBytesWritten totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite { - if(upload && delegate) { - [delegate imageSharingProgress:self progress:(float)totalBytesWritten/(float)totalBytesExpectedToWrite]; - } -} - -- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)adata { - [data appendData:adata]; - if(!upload && delegate) { - [delegate imageSharingProgress:self progress:(float)data.length/(float)totalBytesExpectedToRead]; - } -} - -- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { - NSHTTPURLResponse * httpResponse = (NSHTTPURLResponse *) response; - statusCode = httpResponse.statusCode; - LOGI(@"File transfer status code [%i]", statusCode); - - if (statusCode == 200 && !upload) { - totalBytesExpectedToRead = (int)[response expectedContentLength]; - } -} - -- (void)connectionDidFinishLoading:(NSURLConnection *)connection { - if(statusCode >= 400) { - NSError *error = [NSError errorWithDomain:@"ImageSharing" code:statusCode userInfo:nil]; - if(delegate) { - [delegate imageSharingError:self error:error]; - } - return; - } - if (upload) { - NSString* imageRemoteUrl = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; - LOGI(@"File can be downloaded from [%@]", imageRemoteUrl); - if(delegate) { - [delegate imageSharingUploadDone:self url:[NSURL URLWithString:imageRemoteUrl]]; - } - } else { - UIImage* image = [UIImage imageWithData:data]; - LOGI(@"File downloaded"); - if(delegate) { - [delegate imageSharingDownloadDone:self image:image]; - } - } -} - -@end diff --git a/Classes/LinphoneCoreSettingsStore.m b/Classes/LinphoneCoreSettingsStore.m index 191972865..471fa9d3a 100644 --- a/Classes/LinphoneCoreSettingsStore.m +++ b/Classes/LinphoneCoreSettingsStore.m @@ -693,7 +693,7 @@ extern void linphone_iphone_log_handler(int lev, const char *fmt, va_list args); NSString* sharing_server = [self stringForKey:@"sharing_server_preference"]; [[LinphoneManager instance] lpConfigSetString:sharing_server forKey:@"sharing_server_preference"]; - + linphone_core_set_file_transfer_server(lc, [sharing_server UTF8String]); //Tunnel if (linphone_core_tunnel_available()){ diff --git a/Classes/LinphoneManager.h b/Classes/LinphoneManager.h index 59dba725e..08f868929 100644 --- a/Classes/LinphoneManager.h +++ b/Classes/LinphoneManager.h @@ -52,6 +52,8 @@ extern NSString *const kLinphoneBluetoothAvailabilityUpdate; extern NSString *const kLinphoneConfiguringStateUpdate; extern NSString *const kLinphoneGlobalStateUpdate; extern NSString *const kLinphoneNotifyReceived; +extern NSString *const kLinphoneFileTransferSendUpdate; +extern NSString *const kLinphoneFileTransferRecvUpdate; typedef enum _NetworkType { network_none = 0, @@ -98,14 +100,6 @@ struct NetworkReachabilityContext { }; @end -@protocol LinphoneChatContentTransferDelegate - --(void)onProgressReport:(LinphoneChatMessage*)msg forContent:(const LinphoneContent*)content percent:(int)percent; --(void)onDataRequested:(LinphoneChatMessage*)msg forContent:(const LinphoneContent*)content buffer:(char*)buffer withSize:(size_t*)size; --(void)onDataReceived:(LinphoneChatMessage*)msg forContent:(const LinphoneContent*)content buffer:(const char*)buffer withSize:(size_t)size; - -@end - typedef struct _LinphoneManagerSounds { SystemSoundID vibrate; } LinphoneManagerSounds; @@ -211,5 +205,6 @@ typedef struct _LinphoneManagerSounds { @property (readonly) BOOL wasRemoteProvisioned; @property (readonly) LpConfig *configDb; @property (readonly) InAppProductsManager *iapManager; -@end +@property(strong, nonatomic) NSMutableArray *fileTransferDelegates; +@end \ No newline at end of file diff --git a/Classes/LinphoneManager.m b/Classes/LinphoneManager.m index e2e93603e..db9bc07f3 100644 --- a/Classes/LinphoneManager.m +++ b/Classes/LinphoneManager.m @@ -68,7 +68,8 @@ NSString *const kLinphoneBluetoothAvailabilityUpdate = @"LinphoneBluetoothAvaila NSString *const kLinphoneConfiguringStateUpdate = @"LinphoneConfiguringStateUpdate"; NSString *const kLinphoneGlobalStateUpdate = @"LinphoneGlobalStateUpdate"; NSString *const kLinphoneNotifyReceived = @"LinphoneNotifyReceived"; - +NSString *const kLinphoneFileTransferSendUpdate = @"LinphoneFileTransferSendUpdate"; +NSString *const kLinphoneFileTransferRecvUpdate = @"LinphoneFileTransferRecvUpdate"; const int kLinphoneAudioVbrCodecDefaultBitrate=36; /*you can override this from linphonerc or linphonerc-factory*/ @@ -273,6 +274,7 @@ struct codec_name_pref_table codec_pref_table[]={ bluetoothEnabled = FALSE; tunnelMode = FALSE; + _fileTransferDelegates = [[NSMutableArray alloc] init]; pushCallIDs = [[NSMutableArray alloc] init ]; photoLibrary = [[ALAssetsLibrary alloc] init]; @@ -915,29 +917,6 @@ static void linphone_iphone_notify_received(LinphoneCore *lc, LinphoneEvent *lev [(__bridge LinphoneManager*)linphone_core_get_user_data(lc) onNotifyReceived:lc event:lev notifyEvent:notified_event content:body]; } -#pragma mark - FileTransfer functions - -static void linphone_iphone_file_transfer_recv(LinphoneCore *lc, LinphoneChatMessage *message, const LinphoneContent* content, const char* buff, size_t size) { - id delegate = (__bridge id)linphone_chat_message_get_user_data(message); - LOGI(@"Transfer of %s, incoming data (%d bytes)", linphone_content_get_name(content), size); - [delegate onDataReceived:message forContent:content buffer:buff withSize:size]; -} - -static void linphone_iphone_file_transfer_send(LinphoneCore *lc, LinphoneChatMessage *message, const LinphoneContent* content, char* buff, size_t* size){ - id delegate = (__bridge id)linphone_chat_message_get_user_data(message); - LOGI(@"Transfer of %s, requesting data (%d bytes)", linphone_content_get_name(content), *size); - [delegate onDataRequested:message forContent:content buffer:buff withSize:size]; -} - -static void linphone_iphone_file_transfer_progress(LinphoneCore *lc, LinphoneChatMessage *message, const LinphoneContent* content, size_t offset, size_t total){ - id delegate = (__bridge id)linphone_chat_message_get_user_data(message); - float progress = offset*100.f/total; - LOGI(@"Progress of transfer %s: %d%%", linphone_content_get_name(content), progress); - [delegate onProgressReport:message forContent:content percent:progress]; -} - - - #pragma mark - Message composition start - (void)onMessageComposeReceived:(LinphoneCore*)core forRoom:(LinphoneChatRoom*)room { @@ -1228,11 +1207,7 @@ static LinphoneCoreVTable linphonec_vtable = { .is_composing_received = linphone_iphone_is_composing_received, .configuring_status = linphone_iphone_configuring_status_changed, .global_state_changed = linphone_iphone_global_state_changed, - .notify_received = linphone_iphone_notify_received, - .file_transfer_recv = linphone_iphone_file_transfer_recv, - .file_transfer_send = linphone_iphone_file_transfer_send, - .file_transfer_progress_indication = linphone_iphone_file_transfer_progress - + .notify_received = linphone_iphone_notify_received }; #pragma mark - diff --git a/Classes/LinphoneUI/Base.lproj/UIChatRoomCell.xib b/Classes/LinphoneUI/Base.lproj/UIChatRoomCell.xib index e4a9593a5..9817a998a 100644 --- a/Classes/LinphoneUI/Base.lproj/UIChatRoomCell.xib +++ b/Classes/LinphoneUI/Base.lproj/UIChatRoomCell.xib @@ -1,7 +1,8 @@ - + - + + @@ -9,9 +10,11 @@ + + @@ -54,6 +57,7 @@ + + @@ -121,4 +152,9 @@ - \ No newline at end of file + + + + + + diff --git a/Classes/LinphoneUI/UIChatCell.m b/Classes/LinphoneUI/UIChatCell.m index aacc5e510..b80e2cfd7 100644 --- a/Classes/LinphoneUI/UIChatCell.m +++ b/Classes/LinphoneUI/UIChatCell.m @@ -106,11 +106,12 @@ if( last_message ){ - const char* text = linphone_chat_message_get_text(last_message); - const char* url = linphone_chat_message_get_external_body_url(last_message); - // Message - if(url) { - [chatContentLabel setText:@""]; + const char* text = linphone_chat_message_get_text(last_message); + const char* url = linphone_chat_message_get_external_body_url(last_message); + const LinphoneContent *last_content = linphone_chat_message_get_file_transfer_information(last_message); + // Message + if(url||last_content) { + [chatContentLabel setText:@"🗻"]; } else if (text) { NSString *message = [NSString stringWithUTF8String:text]; // shorten long messages diff --git a/Classes/LinphoneUI/UIChatRoomCell.h b/Classes/LinphoneUI/UIChatRoomCell.h index d71a9ec27..9502e5e05 100644 --- a/Classes/LinphoneUI/UIChatRoomCell.h +++ b/Classes/LinphoneUI/UIChatRoomCell.h @@ -24,7 +24,7 @@ #import "UITransparentTVCell.h" #import "UITextViewNoDefine.h" #include "linphone/linphonecore.h" - +#import "FileTransferDelegate.h" @interface UIChatRoomCell : UITransparentTVCell { LinphoneChatMessage* chat; @@ -41,6 +41,8 @@ @property (nonatomic, strong) IBOutlet UIButton* downloadButton; @property (nonatomic, strong) IBOutlet UITapGestureRecognizer* imageTapGestureRecognizer; @property (nonatomic, strong) IBOutlet UITapGestureRecognizer* resendTapGestureRecognizer; +@property (weak, nonatomic) IBOutlet UIProgressView *fileTransferProgress; +@property (weak, nonatomic) IBOutlet UIButton *cancelButton; - (id)initWithIdentifier:(NSString*)identifier; + (CGFloat)height:(LinphoneChatMessage*)chatMessage width:(int)width; @@ -50,7 +52,10 @@ - (IBAction)onDeleteClick:(id)event; - (IBAction)onDownloadClick:(id)event; - (IBAction)onImageClick:(id)event; +- (IBAction)onCancelDownloadClick:(id)sender; - (void)setChatMessage:(LinphoneChatMessage*)message; +- (void)connectToFileDelegate:(FileTransferDelegate*)ftd; + @end diff --git a/Classes/LinphoneUI/UIChatRoomCell.m b/Classes/LinphoneUI/UIChatRoomCell.m index 6eaccfcbc..34a341a96 100644 --- a/Classes/LinphoneUI/UIChatRoomCell.m +++ b/Classes/LinphoneUI/UIChatRoomCell.m @@ -28,7 +28,9 @@ #import #include "linphone/linphonecore.h" -@implementation UIChatRoomCell +@implementation UIChatRoomCell { + FileTransferDelegate* ftd; +} @synthesize innerView; @synthesize bubbleView; @@ -56,304 +58,395 @@ static UIFont *CELL_FONT = nil; #pragma mark - Lifecycle Functions - (id)initWithIdentifier:(NSString*)identifier { - if ((self = [super initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier]) != nil) { - [[NSBundle mainBundle] loadNibNamed:@"UIChatRoomCell" - owner:self - options:nil]; - imageTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onImageClick:)]; - [messageImageView addGestureRecognizer:imageTapGestureRecognizer]; + if ((self = [super initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier]) != nil) { + [[NSBundle mainBundle] loadNibNamed:@"UIChatRoomCell" + owner:self + options:nil]; + imageTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onImageClick:)]; + [messageImageView addGestureRecognizer:imageTapGestureRecognizer]; - resendTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onResendClick:)]; - [dateLabel addGestureRecognizer:resendTapGestureRecognizer]; + resendTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onResendClick:)]; + [dateLabel addGestureRecognizer:resendTapGestureRecognizer]; - [self addSubview:innerView]; - [deleteButton setAlpha:0.0f]; + [self addSubview:innerView]; + [deleteButton setAlpha:0.0f]; - // shift message box, otherwise it will collide with the bubble - CGRect messageCoords = [messageText frame]; - messageCoords.origin.x += 2; - messageCoords.origin.y += 2; - messageCoords.size.width -= 5; - [messageText setFrame:messageCoords]; - messageText.allowSelectAll = TRUE; - } - return self; + // shift message box, otherwise it will collide with the bubble + CGRect messageCoords = [messageText frame]; + messageCoords.origin.x += 2; + messageCoords.origin.y += 2; + messageCoords.size.width -= 5; + [messageText setFrame:messageCoords]; + messageText.allowSelectAll = TRUE; + } + + return self; } +- (void)dealloc { + [self disconnectFromFileDelegate]; +} #pragma mark - - (void)setChatMessage:(LinphoneChatMessage *)message { - self->chat = message; - [self update]; + if (message != self->chat) { + self->chat = message; + messageImageView.image = nil; + [self disconnectFromFileDelegate]; + for (FileTransferDelegate *aftd in [[LinphoneManager instance] fileTransferDelegates]) { + if (message && + linphone_chat_message_get_storage_id(aftd.message) == linphone_chat_message_get_storage_id(message)) { + LOGI(@"Chat message [%p] with file transfer delegate [%p], connecting to it!", message, + linphone_chat_message_get_user_data(message)); + [self connectToFileDelegate:aftd]; + break; + } + } + [self update]; + } } + (NSString*)decodeTextMessage:(const char*)text { - NSString* decoded = [NSString stringWithUTF8String:text]; - if( decoded == nil ){ - // couldn't decode the string as UTF8, do a lossy conversion - decoded = [NSString stringWithCString:text encoding:NSASCIIStringEncoding]; - if( decoded == nil ){ - decoded = @"(invalid string)"; - } - } - return decoded; + NSString* decoded = [NSString stringWithUTF8String:text]; + if( decoded == nil ){ + // couldn't decode the string as UTF8, do a lossy conversion + decoded = [NSString stringWithCString:text encoding:NSASCIIStringEncoding]; + if( decoded == nil ){ + decoded = @"(invalid string)"; + } + } + return decoded; } - (void)update { - if(chat == nil) { - LOGW(@"Cannot update chat room cell: null chat"); - return; - } - const char* url = linphone_chat_message_get_external_body_url(chat); - const char* text = linphone_chat_message_get_text(chat); - BOOL is_external = url && (strstr(url, "http") == url); - NSString* localImage = [LinphoneManager getMessageAppDataForKey:@"localimage" inMessage:chat]; + if(chat == nil) { + LOGW(@"Cannot update chat room cell: null chat"); + return; + } + const char* url = linphone_chat_message_get_external_body_url(chat); + const char* text = linphone_chat_message_get_text(chat); + BOOL is_external = + (url && (strstr(url, "http") == url)) || linphone_chat_message_get_file_transfer_information(chat); + NSString* localImage = [LinphoneManager getMessageAppDataForKey:@"localimage" inMessage:chat]; + // this is an image (either to download or already downloaded) + if (is_external || localImage) { + if(localImage) { + if (messageImageView.image == nil) { + NSURL *imageUrl = [NSURL URLWithString:localImage]; + messageText.hidden = YES; + [messageImageView startLoading]; + __block LinphoneChatMessage *achat = chat; + [[LinphoneManager instance] + .photoLibrary assetForURL:imageUrl + resultBlock:^(ALAsset *asset) { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, (unsigned long)NULL), + ^(void) { + if (achat == self->chat) { // Avoid glitch and scrolling + UIImage *image = [[UIImage alloc] initWithCGImage:[asset thumbnail]]; + dispatch_async(dispatch_get_main_queue(), ^{ + [messageImageView setImage:image]; + [messageImageView setFullImageUrl:asset]; + [messageImageView stopLoading]; + messageImageView.hidden = NO; + }); + } + }); + } + failureBlock:^(NSError *error) { + LOGE(@"Can't read image"); + }]; + } + if (ftd.message != nil) { + _cancelButton.hidden = NO; + _fileTransferProgress.hidden = NO; + downloadButton.hidden = YES; + } else { + _cancelButton.hidden = _fileTransferProgress.hidden = downloadButton.hidden = YES; + } + } else { + messageText.hidden = YES; + messageImageView.hidden = _cancelButton.hidden = _fileTransferProgress.hidden = (ftd.message == nil); + downloadButton.hidden = !_cancelButton.hidden; + } + // simple text message + } else { + [messageText setHidden:FALSE]; + if ( text ){ + NSString* nstext = [UIChatRoomCell decodeTextMessage:text]; - if(is_external && !localImage ) { - [messageText setHidden:TRUE]; - [messageImageView setImage:nil]; - [messageImageView setHidden:TRUE]; - [downloadButton setHidden:FALSE]; + /* We need to use an attributed string here so that data detector don't mess + * with the text style. See http://stackoverflow.com/a/20669356 */ - } else if(localImage) { + NSAttributedString* attr_text = [[NSAttributedString alloc] + initWithString:nstext + attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:17.0], + NSForegroundColorAttributeName:[UIColor darkGrayColor]}]; + messageText.attributedText = attr_text; - NSURL* imageUrl = [NSURL URLWithString:localImage]; + } else { + messageText.text = @""; + } + messageImageView.hidden = YES; + _cancelButton.hidden = _fileTransferProgress.hidden = downloadButton.hidden = YES; + } - [messageText setHidden:TRUE]; - [messageImageView setImage:nil]; - [messageImageView startLoading]; - __block LinphoneChatMessage *achat = chat; - [[LinphoneManager instance].photoLibrary assetForURL:imageUrl resultBlock:^(ALAsset *asset) { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, (unsigned long)NULL), ^(void) { - if(achat == self->chat) { //Avoid glitch and scrolling - UIImage* image = [[UIImage alloc] initWithCGImage:[asset thumbnail]]; - dispatch_async(dispatch_get_main_queue(), ^{ - [messageImageView setImage:image]; - [messageImageView setFullImageUrl:asset]; - [messageImageView stopLoading]; - }); - } - }); - } failureBlock:^(NSError *error) { - LOGE(@"Can't read image"); - }]; + // Date + time_t chattime = linphone_chat_message_get_time(chat); + NSDate *message_date = (chattime == 0) ? [[NSDate alloc] init] : [NSDate dateWithTimeIntervalSince1970:chattime]; + NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; + [dateFormatter setTimeStyle:NSDateFormatterMediumStyle]; + [dateFormatter setDateStyle:NSDateFormatterMediumStyle]; + NSLocale *locale = [NSLocale currentLocale]; + [dateFormatter setLocale:locale]; + [dateLabel setText:[dateFormatter stringFromDate:message_date]]; - [messageImageView setHidden:FALSE]; - [downloadButton setHidden:TRUE]; - } else { - // simple text message - [messageText setHidden:FALSE]; - if ( text ){ - NSString* nstext = [UIChatRoomCell decodeTextMessage:text]; + LinphoneChatMessageState state = linphone_chat_message_get_state(chat); + BOOL outgoing = linphone_chat_message_is_outgoing(chat); - /* We need to use an attributed string here so that data detector don't mess - * with the text style. See http://stackoverflow.com/a/20669356 */ - - NSAttributedString* attr_text = [[NSAttributedString alloc] - initWithString:nstext - attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:17.0], - NSForegroundColorAttributeName:[UIColor darkGrayColor]}]; - messageText.attributedText = attr_text; - - } else { - messageText.text = @""; - } - - [messageImageView setImage:nil]; - [messageImageView setHidden:TRUE]; - - [downloadButton setHidden:TRUE]; - } - - // Date - NSDate* message_date = [NSDate dateWithTimeIntervalSince1970:linphone_chat_message_get_time(chat)]; - NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; - [dateFormatter setTimeStyle:NSDateFormatterMediumStyle]; - [dateFormatter setDateStyle:NSDateFormatterMediumStyle]; - NSLocale *locale = [NSLocale currentLocale]; - [dateFormatter setLocale:locale]; - [dateLabel setText:[dateFormatter stringFromDate:message_date]]; - - LinphoneChatMessageState state = linphone_chat_message_get_state(chat); - BOOL outgoing = linphone_chat_message_is_outgoing(chat); - - if( !outgoing ){ - statusImage.hidden = TRUE; // not useful for incoming chats.. - } else if (state== LinphoneChatMessageStateInProgress) { + if( !outgoing ){ + statusImage.hidden = TRUE; // not useful for incoming chats.. + } else if (state== LinphoneChatMessageStateInProgress) { [statusImage setImage:[UIImage imageNamed:@"chat_message_inprogress.png"]]; - [statusImage setAccessibilityValue:@"in progress"]; + [statusImage setAccessibilityValue:@"in progress"]; statusImage.hidden = FALSE; - } else if (state == LinphoneChatMessageStateDelivered) { + } else if (state == LinphoneChatMessageStateDelivered || state == LinphoneChatMessageStateFileTransferDone) { [statusImage setImage:[UIImage imageNamed:@"chat_message_delivered.png"]]; - [statusImage setAccessibilityValue:@"delivered"]; + [statusImage setAccessibilityValue:@"delivered"]; statusImage.hidden = FALSE; } else { [statusImage setImage:[UIImage imageNamed:@"chat_message_not_delivered.png"]]; - [statusImage setAccessibilityValue:@"not delivered"]; + [statusImage setAccessibilityValue:@"not delivered"]; statusImage.hidden = FALSE; - NSAttributedString* resend_text = [[NSAttributedString alloc] - initWithString:NSLocalizedString(@"Resend", @"Resend") - attributes:@{NSForegroundColorAttributeName: [UIColor redColor]}]; - [dateLabel setAttributedText:resend_text]; + NSAttributedString* resend_text = [[NSAttributedString alloc] + initWithString:NSLocalizedString(@"Resend", @"Resend") + attributes:@{NSForegroundColorAttributeName: [UIColor redColor]}]; + [dateLabel setAttributedText:resend_text]; } - if( outgoing){ - [messageText setAccessibilityLabel:@"Outgoing message"]; - } else { - [messageText setAccessibilityLabel:@"Incoming message"]; - } - + if( outgoing){ + [messageText setAccessibilityLabel:@"Outgoing message"]; + } else { + [messageText setAccessibilityLabel:@"Incoming message"]; + } } - (void)setEditing:(BOOL)editing { - [self setEditing:editing animated:FALSE]; + [self setEditing:editing animated:FALSE]; } - (void)setEditing:(BOOL)editing animated:(BOOL)animated { - if(animated) { - [UIView beginAnimations:nil context:nil]; - [UIView setAnimationDuration:0.3]; - } - if(editing) { - [deleteButton setAlpha:1.0f]; - } else { - [deleteButton setAlpha:0.0f]; - } - if(animated) { - [UIView commitAnimations]; - } + if(animated) { + [UIView beginAnimations:nil context:nil]; + [UIView setAnimationDuration:0.3]; + } + if(editing) { + [deleteButton setAlpha:1.0f]; + } else { + [deleteButton setAlpha:0.0f]; + } + if(animated) { + [UIView commitAnimations]; + } } + (CGSize)viewSize:(LinphoneChatMessage*)chat width:(int)width { - CGSize messageSize; - const char* url = linphone_chat_message_get_external_body_url(chat); - const char* text = linphone_chat_message_get_text(chat); - NSString* messageText = text ? [UIChatRoomCell decodeTextMessage:text] : @""; - if(url == nil) { - if(CELL_FONT == nil) { - CELL_FONT = [UIFont systemFontOfSize:CELL_FONT_SIZE]; - } + CGSize messageSize; + const char* url = linphone_chat_message_get_external_body_url(chat); + const char* text = linphone_chat_message_get_text(chat); + NSString* messageText = text ? [UIChatRoomCell decodeTextMessage:text] : @""; + if (url == nil && linphone_chat_message_get_file_transfer_information(chat) == NULL) { + if(CELL_FONT == nil) { + CELL_FONT = [UIFont systemFontOfSize:CELL_FONT_SIZE]; + } #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 - if( [[[UIDevice currentDevice] systemVersion] doubleValue] >= 7){ - messageSize = [messageText - boundingRectWithSize:CGSizeMake(width - CELL_MESSAGE_X_MARGIN, CGFLOAT_MAX) - options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingTruncatesLastVisibleLine|NSStringDrawingUsesFontLeading) - attributes:@{NSFontAttributeName: CELL_FONT} - context:nil].size; - } else + if( [[[UIDevice currentDevice] systemVersion] doubleValue] >= 7){ + messageSize = [messageText + boundingRectWithSize:CGSizeMake(width - CELL_MESSAGE_X_MARGIN, CGFLOAT_MAX) + options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingTruncatesLastVisibleLine|NSStringDrawingUsesFontLeading) + attributes:@{NSFontAttributeName: CELL_FONT} + context:nil].size; + } else #endif - { - messageSize = [messageText sizeWithFont: CELL_FONT - constrainedToSize: CGSizeMake(width - CELL_MESSAGE_X_MARGIN, 10000.0f) - lineBreakMode: NSLineBreakByTruncatingTail]; - } - } else { - messageSize = CGSizeMake(CELL_IMAGE_WIDTH, CELL_IMAGE_HEIGHT); - } - messageSize.height += CELL_MESSAGE_Y_MARGIN; - if(messageSize.height < CELL_MIN_HEIGHT) - messageSize.height = CELL_MIN_HEIGHT; - messageSize.width += CELL_MESSAGE_X_MARGIN; - if(messageSize.width < CELL_MIN_WIDTH) - messageSize.width = CELL_MIN_WIDTH; - return messageSize; + { + messageSize = [messageText sizeWithFont: CELL_FONT + constrainedToSize: CGSizeMake(width - CELL_MESSAGE_X_MARGIN, 10000.0f) + lineBreakMode: NSLineBreakByTruncatingTail]; + } + } else { + messageSize = CGSizeMake(CELL_IMAGE_WIDTH, CELL_IMAGE_HEIGHT); + } + messageSize.height += CELL_MESSAGE_Y_MARGIN; + if(messageSize.height < CELL_MIN_HEIGHT) + messageSize.height = CELL_MIN_HEIGHT; + messageSize.width += CELL_MESSAGE_X_MARGIN; + if(messageSize.width < CELL_MIN_WIDTH) + messageSize.width = CELL_MIN_WIDTH; + return messageSize; } + (CGFloat)height:(LinphoneChatMessage*)chatMessage width:(int)width { - return [UIChatRoomCell viewSize:chatMessage width:width].height; + return [UIChatRoomCell viewSize:chatMessage width:width].height; } #pragma mark - View Functions - (void)layoutSubviews { - [super layoutSubviews]; - if(chat != nil) { - // Resize inner - CGRect innerFrame; - BOOL is_outgoing = linphone_chat_message_is_outgoing(chat); - innerFrame.size = [UIChatRoomCell viewSize:chat width:[self frame].size.width]; - if(!is_outgoing) { // Inverted - innerFrame.origin.x = 0.0f; - innerFrame.origin.y = 0.0f; - } else { - innerFrame.origin.x = [self frame].size.width - innerFrame.size.width; - innerFrame.origin.y = 0.0f; - } - [innerView setFrame:innerFrame]; + [super layoutSubviews]; + if(chat != nil) { + // Resize inner + CGRect innerFrame; + BOOL is_outgoing = linphone_chat_message_is_outgoing(chat); + innerFrame.size = [UIChatRoomCell viewSize:chat width:[self frame].size.width]; + if(!is_outgoing) { // Inverted + innerFrame.origin.x = 0.0f; + innerFrame.origin.y = 0.0f; + } else { + innerFrame.origin.x = [self frame].size.width - innerFrame.size.width; + innerFrame.origin.y = 0.0f; + } + [innerView setFrame:innerFrame]; - CGRect messageFrame = [bubbleView frame]; - messageFrame.origin.y = ([innerView frame].size.height - messageFrame.size.height)/2; - if(!is_outgoing) { // Inverted - UIImage* image = [UIImage imageNamed:@"chat_bubble_incoming"]; - image = [image resizableImageWithCapInsets:UIEdgeInsetsMake(26, 32, 34, 56)]; - [backgroundImage setImage:image]; - messageFrame.origin.y += 5; - } else { - UIImage* image = [UIImage imageNamed:@"chat_bubble_outgoing"]; - image = [image resizableImageWithCapInsets:UIEdgeInsetsMake(14, 15, 25, 40)]; - [backgroundImage setImage:image]; - messageFrame.origin.y -= 5; - } - [bubbleView setFrame:messageFrame]; - } + CGRect messageFrame = [bubbleView frame]; + messageFrame.origin.y = ([innerView frame].size.height - messageFrame.size.height)/2; + if(!is_outgoing) { // Inverted + UIImage* image = [UIImage imageNamed:@"chat_bubble_incoming"]; + image = [image resizableImageWithCapInsets:UIEdgeInsetsMake(26, 32, 34, 56)]; + [backgroundImage setImage:image]; + messageFrame.origin.y += 5; + } else { + UIImage* image = [UIImage imageNamed:@"chat_bubble_outgoing"]; + image = [image resizableImageWithCapInsets:UIEdgeInsetsMake(14, 15, 25, 40)]; + [backgroundImage setImage:image]; + messageFrame.origin.y -= 5; + } + [bubbleView setFrame:messageFrame]; + } } #pragma mark - Action Functions - (IBAction)onDeleteClick:(id)event { - if(chat != NULL) { - UIView *view = [self superview]; - // Find TableViewCell - while(view != nil && ![view isKindOfClass:[UITableView class]]) view = [view superview]; - if(view != nil) { - UITableView *tableView = (UITableView*) view; - NSIndexPath *indexPath = [tableView indexPathForCell:self]; - [[tableView dataSource] tableView:tableView commitEditingStyle:UITableViewCellEditingStyleDelete forRowAtIndexPath:indexPath]; - } - } + if(chat != NULL) { + if (ftd.message != nil) { + [ftd cancel]; + } + UIView *view = [self superview]; + // Find TableViewCell + while(view != nil && ![view isKindOfClass:[UITableView class]]) view = [view superview]; + if(view != nil) { + UITableView *tableView = (UITableView*) view; + NSIndexPath *indexPath = [tableView indexPathForCell:self]; + [[tableView dataSource] tableView:tableView commitEditingStyle:UITableViewCellEditingStyleDelete forRowAtIndexPath:indexPath]; + } + } } - (IBAction)onDownloadClick:(id)event { - [chatRoomDelegate chatRoomStartImageDownload:chat]; + if (ftd.message == nil) { + ftd = [[FileTransferDelegate alloc] init]; + [[[LinphoneManager instance] fileTransferDelegates] addObject:ftd]; + [self connectToFileDelegate:ftd]; + [ftd download:chat]; + _cancelButton.hidden = NO; + downloadButton.hidden = YES; + } } +- (IBAction)onCancelDownloadClick:(id)sender { + [ftd cancel]; + [self disconnectFromFileDelegate]; + [self update]; +} + + - (IBAction)onImageClick:(id)event { - if(![messageImageView isLoading]) { - ImageViewController *controller = DYNAMIC_CAST([[PhoneMainView instance] changeCurrentView:[ImageViewController compositeViewDescription] push:TRUE], ImageViewController); - if(controller != nil) { - CGImageRef fullScreenRef = [[messageImageView.fullImageUrl defaultRepresentation] fullScreenImage]; - UIImage* fullScreen = [UIImage imageWithCGImage:fullScreenRef]; - [controller setImage:fullScreen]; - } - } + // if(![messageImageView isLoading]) { + // ImageViewController *controller = DYNAMIC_CAST([[PhoneMainView instance] + // changeCurrentView:[ImageViewController compositeViewDescription] push:TRUE], ImageViewController); + // if(controller != nil) { + // CGImageRef fullScreenRef = [[messageImageView.fullImageUrl defaultRepresentation] fullScreenImage]; + // UIImage* fullScreen = [UIImage imageWithCGImage:fullScreenRef]; + // [controller setImage:fullScreen]; + // } + // } + + [LinphoneManager setValueInMessageAppData:nil forKey:@"localimage" inMessage:chat]; } - (IBAction)onResendClick:(id)event { - if( chat == nil ) return; + if( chat == nil ) return; - LinphoneChatMessageState state = linphone_chat_message_get_state(self->chat); - if (state == LinphoneChatMessageStateNotDelivered) { - const char* text = linphone_chat_message_get_text(self->chat); - const char* url = linphone_chat_message_get_external_body_url(self->chat); - NSString* message = text ? [NSString stringWithUTF8String:text] : nil; - NSString* exturl = url ? [NSString stringWithUTF8String:url] : nil; + LinphoneChatMessageState state = linphone_chat_message_get_state(self->chat); + if (state == LinphoneChatMessageStateNotDelivered) { + const char* text = linphone_chat_message_get_text(self->chat); + const char* url = linphone_chat_message_get_external_body_url(self->chat); + NSString* message = text ? [NSString stringWithUTF8String:text] : nil; + NSString* exturl = url ? [NSString stringWithUTF8String:url] : nil; - [self onDeleteClick:nil]; + [self onDeleteClick:nil]; - double delayInSeconds = 0.4; - dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); - dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ - [chatRoomDelegate resendChat:message withExternalUrl:exturl]; - }); - } + double delayInSeconds = 0.4; + dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); + dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ + [chatRoomDelegate resendChat:message withExternalUrl:exturl]; + }); + } +} + +#pragma mark - LinphoneFileTransfer Notifications Handling + +- (void)connectToFileDelegate:(FileTransferDelegate*)aftd { + ftd = aftd; + _fileTransferProgress.progress = 0; + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(onFileTransferSendUpdate:) + name:kLinphoneFileTransferSendUpdate + object:ftd]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(onFileTransferRecvUpdate:) + name:kLinphoneFileTransferRecvUpdate + object:ftd]; +} + +- (void)disconnectFromFileDelegate { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + ftd = nil; +} + +- (void)onFileTransferSendUpdate:(NSNotification*)notif { + LinphoneChatMessageState state = [[[notif userInfo] objectForKey:@"state"] intValue]; + + if (state == LinphoneChatMessageStateInProgress) { + float progress = [[[notif userInfo] objectForKey:@"progress"] floatValue]; + // When uploading a file, the message file is first uploaded to the server, + // so we are in progress state. Then state goes to filetransfertdone. Then, + // the exact same message is sent to the other chat participant and we come + // back to in progress again. This second time is NOT an upload, so we must + // not update progress! + _fileTransferProgress.progress = MAX(_fileTransferProgress.progress, progress); + _fileTransferProgress.hidden = _cancelButton.hidden = (_fileTransferProgress.progress == 1.f); + } else { + [self update]; + } +} +- (void)onFileTransferRecvUpdate:(NSNotification*)notif { + LinphoneChatMessageState state = [[[notif userInfo] objectForKey:@"state"] intValue]; + if (state == LinphoneChatMessageStateInProgress) { + float progress = [[[notif userInfo] objectForKey:@"progress"] floatValue]; + _fileTransferProgress.progress = MAX(_fileTransferProgress.progress, progress); + _fileTransferProgress.hidden = _cancelButton.hidden = (_fileTransferProgress.progress == 1.f); + } else { + [self update]; + } } @end diff --git a/Classes/Utils/FileTransferDelegate.h b/Classes/Utils/FileTransferDelegate.h new file mode 100644 index 000000000..a62af10e0 --- /dev/null +++ b/Classes/Utils/FileTransferDelegate.h @@ -0,0 +1,20 @@ +// +// FileTransferDelegate.h +// linphone +// +// Created by Gautier Pelloux-Prayer on 10/06/15. +// +// + +#import + +#import "LinphoneManager.h" + +@interface FileTransferDelegate : NSObject + +- (void)upload:(UIImage *)image withURL:(NSURL *)url forChatRoom:(LinphoneChatRoom *)chatRoom; +- (void)cancel; +- (BOOL)download:(LinphoneChatMessage *)message; + +@property() LinphoneChatMessage *message; +@end diff --git a/Classes/Utils/FileTransferDelegate.m b/Classes/Utils/FileTransferDelegate.m new file mode 100644 index 000000000..6a6adf1ef --- /dev/null +++ b/Classes/Utils/FileTransferDelegate.m @@ -0,0 +1,207 @@ +// +// FileTransferDelegate.m +// linphone +// +// Created by Gautier Pelloux-Prayer on 10/06/15. +// +// + +#import "FileTransferDelegate.h" +@interface FileTransferDelegate () +@property(strong) NSMutableData *data; +@end + +@implementation FileTransferDelegate + +- (void)dealloc { + if (_message != nil) { + [self cancel]; + } +} + +static void linphone_iphone_file_transfer_recv(LinphoneChatMessage *message, const LinphoneContent *content, + const LinphoneBuffer *buffer) { + FileTransferDelegate *thiz = (__bridge FileTransferDelegate *)linphone_chat_message_get_user_data(message); + size_t size = linphone_buffer_get_size(buffer); + + if (!thiz.data) { + thiz.data = [[NSMutableData alloc] initWithCapacity:linphone_content_get_size(content)]; + } + + if (size == 0) { + LOGI(@"Transfer of %s (%d bytes): download finished", linphone_content_get_name(content), size); + assert([thiz.data length] == linphone_content_get_size(content)); + + // we're finished, save the image and update the message + UIImage *image = [UIImage imageWithData:thiz.data]; + + + [[[LinphoneManager instance] fileTransferDelegates] removeObject:thiz]; + + [[LinphoneManager instance] + .photoLibrary + writeImageToSavedPhotosAlbum:image.CGImage + orientation:(ALAssetOrientation)[image imageOrientation] + completionBlock:^(NSURL *assetURL, NSError *error) { + if (error) { + LOGE(@"Cannot save image data downloaded [%@]", [error localizedDescription]); + + UIAlertView *errorAlert = [[UIAlertView alloc] + initWithTitle:NSLocalizedString(@"Transfer error", nil) + message:NSLocalizedString(@"Cannot write image to photo library", nil) + delegate:nil + cancelButtonTitle:NSLocalizedString(@"Ok", nil) + otherButtonTitles:nil, nil]; + [errorAlert show]; + } else { + LOGI(@"Image saved to [%@]", [assetURL absoluteString]); + [LinphoneManager setValueInMessageAppData:[assetURL absoluteString] + forKey:@"localimage" + inMessage:message]; + } + thiz.message = NULL; + [[NSNotificationCenter defaultCenter] + postNotificationName:kLinphoneFileTransferRecvUpdate + object:thiz + userInfo:@{ + @"state" : @(linphone_chat_message_get_state(message)), + @"image" : image, + @"progress" : + @([thiz.data length] * 1.f / linphone_content_get_size(content)), + }]; + + CFRelease((__bridge CFTypeRef)thiz); + }]; + } else { + LOGI(@"Transfer of %s (%d bytes): already %ld sent, adding %ld", linphone_content_get_name(content), + linphone_content_get_size(content), [thiz.data length], size); + [thiz.data appendBytes:linphone_buffer_get_string_content(buffer) length:size]; + [[NSNotificationCenter defaultCenter] + postNotificationName:kLinphoneFileTransferRecvUpdate + object:thiz + userInfo:@{ + @"state" : @(linphone_chat_message_get_state(message)), + @"progress" : @([thiz.data length] * 1.f / linphone_content_get_size(content)), + }]; + } +} + +static LinphoneBuffer *linphone_iphone_file_transfer_send(LinphoneChatMessage *message, const LinphoneContent *content, + size_t offset, size_t size) { + FileTransferDelegate *thiz = (__bridge FileTransferDelegate *)linphone_chat_message_get_user_data(message); + size_t total = thiz.data.length; + if (thiz.data) { + size_t remaining = total - offset; + + NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithDictionary:@{ + @"state" : @(linphone_chat_message_get_state(message)), + @"progress" : @(offset * 1.f / total), + }]; + LOGI(@"Transfer of %s (%d bytes): already sent %ld, remaining %ld", linphone_content_get_name(content), total, + offset, remaining); + [[NSNotificationCenter defaultCenter] postNotificationName:kLinphoneFileTransferSendUpdate + object:thiz + userInfo:dict]; + @try { + return linphone_buffer_new_from_data([thiz.data subdataWithRange:NSMakeRange(offset, size)].bytes, size); + } @catch (NSException *exception) { + LOGE(@"Exception: %@", exception); + } + } else { + LOGE(@"Transfer of %s (%d bytes): %d Error - no upload data in progress!", linphone_content_get_name(content), + total, offset); + } + + return NULL; +} + +static void message_status(LinphoneChatMessage *msg, LinphoneChatMessageState state) { + FileTransferDelegate *thiz = (__bridge FileTransferDelegate *)linphone_chat_message_get_user_data(msg); + + NSString *notification = + linphone_chat_message_is_outgoing(msg) ? kLinphoneFileTransferSendUpdate : kLinphoneFileTransferRecvUpdate; + + const char *text = (linphone_chat_message_get_file_transfer_information(msg) != NULL) + ? "photo transfer" + : linphone_chat_message_get_text(msg); + LOGI(@"Delivery status for [%s] is [%s]", text, linphone_chat_message_state_to_string(state)); + + NSDictionary *dict = @{ @"state" : @(state), @"progress" : @0.f }; + if (state == LinphoneChatMessageStateFileTransferDone || state == LinphoneChatMessageStateFileTransferError) { + thiz.message = NULL; + } + [[NSNotificationCenter defaultCenter] postNotificationName:notification object:thiz userInfo:dict]; + if (linphone_chat_message_is_outgoing(msg)) { + [thiz stopAndDestroy]; + } +} + +- (void)upload:(UIImage *)image withURL:(NSURL *)url forChatRoom:(LinphoneChatRoom *)chatRoom { + LinphoneContent *content = linphone_core_create_content(linphone_chat_room_get_lc(chatRoom)); + _data = [NSMutableData dataWithData:UIImageJPEGRepresentation(image, 1.0)]; + linphone_content_set_type(content, "image"); + linphone_content_set_subtype(content, "jpeg"); + linphone_content_set_name(content, + [[NSString stringWithFormat:@"%li-%f.jpg", (long)[image hash], + [NSDate timeIntervalSinceReferenceDate]] UTF8String]); + linphone_content_set_size(content, [_data length]); + + CFTypeRef myself = (__bridge CFTypeRef)self; + _message = linphone_chat_room_create_file_transfer_message(chatRoom, content); + linphone_chat_message_ref(_message); + linphone_chat_message_set_user_data(_message, (void *)CFRetain(myself)); + linphone_chat_message_cbs_set_file_transfer_send(linphone_chat_message_get_callbacks(_message), + linphone_iphone_file_transfer_send); + linphone_chat_message_cbs_set_msg_state_changed(linphone_chat_message_get_callbacks(_message), message_status); + + if (url) { + // internal url is saved in the appdata for display and later save + [LinphoneManager setValueInMessageAppData:[url absoluteString] forKey:@"localimage" inMessage:_message]; + } + + linphone_chat_room_send_chat_message(chatRoom, _message); +} + +- (BOOL)download:(LinphoneChatMessage *)message { + _message = message; + // we need to keep a ref on the message to continue downloading even if user quit a chatroom which destroy all chat + // messages + linphone_chat_message_ref(_message); + const char *url = linphone_chat_message_get_external_body_url(_message); + LOGI(@"Content to download: %s", url); + + if (url == nil) + return FALSE; + + linphone_chat_message_set_user_data(_message, (void *)CFBridgingRetain(self)); + + linphone_chat_message_cbs_set_file_transfer_recv(linphone_chat_message_get_callbacks(_message), + linphone_iphone_file_transfer_recv); + linphone_chat_message_cbs_set_msg_state_changed(linphone_chat_message_get_callbacks(_message), message_status); + + linphone_chat_message_download_file(_message); + + return TRUE; +} + +- (void)stopAndDestroy { + [[[LinphoneManager instance] fileTransferDelegates] removeObject:self]; + if (_message != NULL) { + linphone_chat_message_set_user_data(_message, NULL); + + linphone_chat_message_cbs_set_file_transfer_progress_indication(linphone_chat_message_get_callbacks(_message), + NULL); + linphone_chat_message_cbs_set_file_transfer_recv(linphone_chat_message_get_callbacks(_message), NULL); + linphone_chat_message_cbs_set_msg_state_changed(linphone_chat_message_get_callbacks(_message), NULL); + linphone_chat_message_cancel_file_transfer(_message); + linphone_chat_message_unref(_message); + } + _message = nil; + _data = nil; +} + +- (void)cancel { + [self stopAndDestroy]; +} + +@end diff --git a/Classes/Utils/Utils.m b/Classes/Utils/Utils.m index c9d56d7d1..971a7da71 100644 --- a/Classes/Utils/Utils.m +++ b/Classes/Utils/Utils.m @@ -29,7 +29,10 @@ OrtpLogLevel ortp_severity; int filesize = 20; if (severity <= LinphoneLoggerDebug) { - ortp_severity = ORTP_DEBUG; + // lol: ortp_debug(XXX) can be disabled at compile time, but ortp_log(ORTP_DEBUG, xxx) will always be valid even + // not in debug build... + ortp_debug("%*s:%3d - %s", filesize, file+MAX((int)strlen(file)-filesize,0), line, [str UTF8String]); + return; } else if(severity <= LinphoneLoggerLog) { ortp_severity = ORTP_MESSAGE; } else if(severity <= LinphoneLoggerWarning) { diff --git a/KifTests/ChatTester.m b/KifTests/ChatTester.m index 1b0056fc5..3c61400e9 100644 --- a/KifTests/ChatTester.m +++ b/KifTests/ChatTester.m @@ -8,6 +8,7 @@ #import "ChatTester.h" #include "LinphoneManager.h" +#import "UIChatRoomCell.h" @implementation ChatTester @@ -17,12 +18,36 @@ - (void)beforeAll { [super beforeAll]; [self switchToValidAccountIfNeeded]; - - [tester tapViewWithAccessibilityLabel:LOCALIZED(@"Chat")]; +} + +- (void)beforeEach { + [super beforeEach]; + if ([tester tryFindingTappableViewWithAccessibilityLabel:LOCALIZED(@"Back") error:nil]) { + [self goBackFromChat]; + } + [tester tapViewWithAccessibilityLabel:LOCALIZED(@"Chat")]; + [self removeAllRooms]; +} + +- (void)afterAll { + // at the end of tests, go back to chat rooms to display main bar + if ([tester tryFindingTappableViewWithAccessibilityLabel:LOCALIZED(@"Back") error:nil]) { + [self goBackFromChat]; + } } #pragma mark - tools +- (void)removeAllRooms { + [tester tapViewWithAccessibilityLabel:@"Edit" traits:UIAccessibilityTraitButton]; + while ( + [tester tryFindingTappableViewWithAccessibilityLabel:@"Delete" traits:UIAccessibilityTraitButton error:nil]) { + [tester tapViewWithAccessibilityLabel:@"Delete" traits:UIAccessibilityTraitButton]; + } + [tester tapViewWithAccessibilityLabel:@"Edit" + traits:UIAccessibilityTraitButton]; // same as the first but it is "OK" on screen +} + - (void)goBackFromChat { [tester tapViewWithAccessibilityLabel:LOCALIZED(@"Back")]; } @@ -114,29 +139,91 @@ for( int i =0; i< uuids.count; i++){ [tester tapViewWithAccessibilityLabel:@"Delete" traits:UIAccessibilityTraitButton]; } - - // then we try to delete all the rest of chatrooms - while ( [tester tryFindingTappableViewWithAccessibilityLabel:@"Delete" traits:UIAccessibilityTraitButton error:nil] ) - { - [tester tapViewWithAccessibilityLabel:@"Delete" traits:UIAccessibilityTraitButton]; - NSLog(@"Deleting an extra chat"); - } - - [tester tapViewWithAccessibilityLabel:@"Edit" traits:UIAccessibilityTraitButton]; // same as the first but it is "OK" on screen - - // check that the tableview is empty - UITableView* tv = nil; - NSError* err = nil; - if( [tester tryFindingAccessibilityElement:nil view:&tv withIdentifier:@"ChatRoom list" tappable:false error:&err] ){ - XCTAssert(tv != nil); - XCTAssert([tv numberOfRowsInSection:0] == 0); // no more chat rooms - } else { - NSLog(@"Error: %@",err); - } - - // test that there's no more chatrooms in the core - XCTAssert(linphone_core_get_chat_rooms([LinphoneManager getLc]) == nil); + + [tester tapViewWithAccessibilityLabel:@"Edit" + traits:UIAccessibilityTraitButton]; // same as the first but it is "OK" on screen + + // check that the tableview is empty + UITableView *tv = [self findTableView:@"ChatRoom list"]; + XCTAssert([tv numberOfRowsInSection:0] == 0); + + // test that there's no more chatrooms in the core + XCTAssert(linphone_core_get_chat_rooms([LinphoneManager getLc]) == nil); } +- (UITableView *)findTableView:(NSString *)table { + UITableView *tv = nil; + NSError *err = nil; + if ([tester tryFindingAccessibilityElement:nil view:&tv withIdentifier:table tappable:false error:&err]) { + XCTAssertNotNil(tv); + } else { + XCTFail(@"Error: %@", err); + } + return tv; +} + +- (void)uploadImage { + NSString *user = @"testios"; + + [self startChatWith:user]; + + [tester tapViewWithAccessibilityLabel:LOCALIZED(@"Send picture")]; + [tester tapViewWithAccessibilityLabel:LOCALIZED(@"Photo library")]; + // if popup "Linphone would access your photo" pops up, click OK. + if ([ALAssetsLibrary authorizationStatus] == ALAuthorizationStatusNotDetermined) { + [tester acknowledgeSystemAlert]; + } + + [tester choosePhotoInAlbum:@"Camera Roll" atRow:1 column:1]; + + // TODO: do not harcode size! + [tester tapViewWithAccessibilityLabel:LOCALIZED(@"Minimum (108.9 KB)")]; + + UITableView *tv = [self findTableView:@"Chat list"]; + XCTAssertEqual([tv numberOfRowsInSection:0], 1); + XCTAssertEqual([[[LinphoneManager instance] fileTransferDelegates] count], 1); +} + +- (void)testUploadImage { + NSString *user = @"testios"; + + [self uploadImage]; + [self goBackFromChat]; + + // if we go back to the same chatroom, the message should be still there + [self startChatWith:user]; + UITableView *tv = [self findTableView:@"Chat list"]; + XCTAssertEqual([tv numberOfRowsInSection:0], 1); + + [tester waitForViewWithAccessibilityLabel:LOCALIZED(@"Download")]; + + XCTAssertEqual([tv numberOfRowsInSection:0], 2); + XCTAssertEqual([[[LinphoneManager instance] fileTransferDelegates] count], 0); +} + +- (void)testCancelUploadImage { + [self uploadImage]; + [tester tapViewWithAccessibilityLabel:LOCALIZED(@"Cancel transfer")]; + XCTAssertEqual([[[LinphoneManager instance] fileTransferDelegates] count], 0); +} + +- (void)downloadImage { + [self uploadImage]; + [tester tapViewWithAccessibilityLabel:LOCALIZED(@"Download")]; + [tester waitForTimeInterval:.5f]; // just wait a few secs to start download + XCTAssertEqual([[[LinphoneManager instance] fileTransferDelegates] count], 1); +} + +- (void)testDownloadImage { + [self downloadImage]; + [tester waitForAbsenceOfViewWithAccessibilityLabel:@"Cancel transfer"]; + XCTAssertEqual([[[LinphoneManager instance] fileTransferDelegates] count], 0); +} + +- (void)testCancelDownloadImage { + [self downloadImage]; + [tester tapViewWithAccessibilityLabel:LOCALIZED(@"Cancel transfer")]; + XCTAssertEqual([[[LinphoneManager instance] fileTransferDelegates] count], 0); +} @end diff --git a/Resources/linphonerc b/Resources/linphonerc index 0f3e852c7..b773a7c27 100644 --- a/Resources/linphonerc +++ b/Resources/linphonerc @@ -73,7 +73,7 @@ username_length=4 expires=1314000 push_notification=1 transport=tls -sharing_server=https://www.linphone.org:444/upload.php +sharing_server=https://www.linphone.org:444/lft.php ice=1 stun=stun.linphone.org diff --git a/Resources/linphonerc~ipad b/Resources/linphonerc~ipad index 787349e1a..d2e96d5c1 100644 --- a/Resources/linphonerc~ipad +++ b/Resources/linphonerc~ipad @@ -73,7 +73,7 @@ username_length=4 expires=1314000 push_notification=1 transport=tls -sharing_server=https://www.linphone.org:444/upload.php +sharing_server=https://www.linphone.org:444/lft.php ice=1 stun=stun.linphone.org diff --git a/linphone.xcodeproj/project.pbxproj b/linphone.xcodeproj/project.pbxproj index 7cf06a6bf..c31fe3c6b 100755 --- a/linphone.xcodeproj/project.pbxproj +++ b/linphone.xcodeproj/project.pbxproj @@ -120,6 +120,7 @@ 636316D41A1DEC650009B839 /* SettingsViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 636316D61A1DEC650009B839 /* SettingsViewController.xib */; }; 636316D91A1DECC90009B839 /* PhoneMainView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 636316D71A1DECC90009B839 /* PhoneMainView.xib */; }; 636316DE1A1DEF2F0009B839 /* UIButtonShrinkable.m in Sources */ = {isa = PBXBuildFile; fileRef = 636316DD1A1DEF2F0009B839 /* UIButtonShrinkable.m */; }; + 637157A11B283FE200C91677 /* FileTransferDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 637157A01B283FE200C91677 /* FileTransferDelegate.m */; }; 639CEAFD1A1DF4D9004DE38F /* UIStateBar.xib in Resources */ = {isa = PBXBuildFile; fileRef = 639CEAFF1A1DF4D9004DE38F /* UIStateBar.xib */; }; 639CEB001A1DF4E4004DE38F /* UIHistoryCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 639CEB021A1DF4E4004DE38F /* UIHistoryCell.xib */; }; 639CEB031A1DF4EB004DE38F /* UICompositeViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 639CEB051A1DF4EB004DE38F /* UICompositeViewController.xib */; }; @@ -306,7 +307,6 @@ D36C43F7158F61EA0048BA40 /* call_state_play_over.png in Resources */ = {isa = PBXBuildFile; fileRef = D36C43F0158F61EA0048BA40 /* call_state_play_over.png */; }; D36FB2D51589EF7C0036F6F2 /* UIPauseButton.m in Sources */ = {isa = PBXBuildFile; fileRef = D36FB2D41589EF7C0036F6F2 /* UIPauseButton.m */; }; D37295DB158B3C9600D2C0C7 /* video_off_disabled.png in Resources */ = {isa = PBXBuildFile; fileRef = D37295DA158B3C9600D2C0C7 /* video_off_disabled.png */; }; - D374D3FD16071762003D25FF /* ImageSharing.m in Sources */ = {isa = PBXBuildFile; fileRef = D374D3FC16071762003D25FF /* ImageSharing.m */; }; D377BBFA15A19DA6002B696B /* video_on_disabled.png in Resources */ = {isa = PBXBuildFile; fileRef = D377BBF915A19DA6002B696B /* video_on_disabled.png */; }; D378906515AC373B00BD776C /* ContactDetailsLabelViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D378906315AC373B00BD776C /* ContactDetailsLabelViewController.m */; }; D378AB2A15DCDB4A0098505D /* ImagePickerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D378AB2915DCDB490098505D /* ImagePickerViewController.m */; }; @@ -892,7 +892,6 @@ /* Begin PBXFileReference section */ 045B5CB218D72E9A0088350C /* libbzrtp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libbzrtp.a; path = "liblinphone-sdk/apple-darwin/lib/libbzrtp.a"; sourceTree = ""; }; - 15017E6F1773578400784ACB /* libxml2.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libxml2.a; path = "liblinphone-sdk/apple-darwin/lib/libxml2.a"; sourceTree = ""; }; 152F22331B15E83B008C0621 /* libilbcrfc3951.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libilbcrfc3951.a; path = "liblinphone-sdk/apple-darwin/lib/libilbcrfc3951.a"; sourceTree = ""; }; 152F22351B15E889008C0621 /* libxml2.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libxml2.dylib; path = usr/lib/libxml2.dylib; sourceTree = SDKROOT; }; 1560821E18EEF26100765332 /* libmsopenh264.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libmsopenh264.a; path = "liblinphone-sdk/apple-darwin/lib/mediastreamer/plugins/libmsopenh264.a"; sourceTree = ""; }; @@ -1039,6 +1038,8 @@ 636316DB1A1DEDD80009B839 /* ru */ = {isa = PBXFileReference; fileEncoding = 2483028224; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/HistoryDetailsViewController.strings; sourceTree = ""; }; 636316DC1A1DEECB0009B839 /* UIButtonShrinkable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UIButtonShrinkable.h; sourceTree = ""; }; 636316DD1A1DEF2F0009B839 /* UIButtonShrinkable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIButtonShrinkable.m; sourceTree = ""; }; + 6371579F1B283FE200C91677 /* FileTransferDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FileTransferDelegate.h; path = Utils/FileTransferDelegate.h; sourceTree = ""; }; + 637157A01B283FE200C91677 /* FileTransferDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FileTransferDelegate.m; path = Utils/FileTransferDelegate.m; sourceTree = ""; }; 639CEAFE1A1DF4D9004DE38F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/UIStateBar.xib; sourceTree = ""; }; 639CEB011A1DF4E4004DE38F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/UIHistoryCell.xib; sourceTree = ""; }; 639CEB041A1DF4EB004DE38F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/UICompositeViewController.xib; sourceTree = ""; }; @@ -1258,8 +1259,6 @@ D36FB2D31589EF7C0036F6F2 /* UIPauseButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UIPauseButton.h; sourceTree = ""; }; D36FB2D41589EF7C0036F6F2 /* UIPauseButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIPauseButton.m; sourceTree = ""; }; D37295DA158B3C9600D2C0C7 /* video_off_disabled.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = video_off_disabled.png; path = Resources/video_off_disabled.png; sourceTree = ""; }; - D374D3FB16071762003D25FF /* ImageSharing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ImageSharing.h; sourceTree = ""; }; - D374D3FC16071762003D25FF /* ImageSharing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ImageSharing.m; sourceTree = ""; }; D377BBF915A19DA6002B696B /* video_on_disabled.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = video_on_disabled.png; path = Resources/video_on_disabled.png; sourceTree = ""; }; D378906215AC373B00BD776C /* ContactDetailsLabelViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ContactDetailsLabelViewController.h; sourceTree = ""; }; D378906315AC373B00BD776C /* ContactDetailsLabelViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ContactDetailsLabelViewController.m; sourceTree = ""; }; @@ -2041,8 +2040,6 @@ D38187D415FE346B00C3EDCA /* HistoryViewController.xib */, D378AB2815DCDB480098505D /* ImagePickerViewController.h */, D378AB2915DCDB490098505D /* ImagePickerViewController.m */, - D374D3FB16071762003D25FF /* ImageSharing.h */, - D374D3FC16071762003D25FF /* ImageSharing.m */, 22405EFD1601C19000B92522 /* ImageViewController.h */, 22405EFE1601C19100B92522 /* ImageViewController.m */, D37EE11016035793003608A6 /* ImageViewController.xib */, @@ -2319,7 +2316,6 @@ D30BF33216A427BC00AF0026 /* libtunnel.a */, 7066FC0B13E830E400EFC6DC /* libvpx.a */, 22AA8AFB13D7125500B30535 /* libx264.a */, - 15017E6F1773578400784ACB /* libxml2.a */, F0B89C2118DC89E30050B60E /* MediaPlayer.framework */, D37DC7171594AF3400B2A5EB /* MessageUI.framework */, 226EF06B15FA256B005865C7 /* MobileCoreServices.framework */, @@ -2345,6 +2341,8 @@ D37EE15F160377D7003608A6 /* DTFoundation */, D32B9DFA15A2F131000B6DEC /* FastAddressBook.h */, D32B9DFB15A2F131000B6DEC /* FastAddressBook.m */, + 6371579F1B283FE200C91677 /* FileTransferDelegate.h */, + 637157A01B283FE200C91677 /* FileTransferDelegate.m */, D3ED40141602172200BF332B /* GrowingTextView */, D3807FC715C2894A005BE9BC /* InAppSettingsKit */, D3B90E1115C2CB5700F64F8C /* NinePatch.xcodeproj */, @@ -4059,13 +4057,13 @@ D380800515C28A7A005BE9BC /* UILinphone.m in Sources */, D380801315C299D0005BE9BC /* ColorSpaceUtilites.m in Sources */, 6359DE7F1ADEB54200EA15C0 /* InAppProductsViewController.m in Sources */, + 637157A11B283FE200C91677 /* FileTransferDelegate.m in Sources */, D378AB2A15DCDB4A0098505D /* ImagePickerViewController.m in Sources */, 6359DE841ADEB64100EA15C0 /* InAppProductsCell.m in Sources */, 22405F001601C19200B92522 /* ImageViewController.m in Sources */, D3ED40191602172200BF332B /* HPGrowingTextView.m in Sources */, D3ED401B1602172200BF332B /* HPTextViewInternal.m in Sources */, D37EE162160377D7003608A6 /* DTActionSheet.m in Sources */, - D374D3FD16071762003D25FF /* ImageSharing.m in Sources */, D35E91F4160CA10B0023116B /* UILinphoneTextField.m in Sources */, D35E91F8160CA4FF0023116B /* UILinphoneButton.m in Sources */, D306459E1611EC2A00BB571E /* UILoadingImageView.m in Sources */, diff --git a/submodules/belle-sip b/submodules/belle-sip index 611762072..778fbca58 160000 --- a/submodules/belle-sip +++ b/submodules/belle-sip @@ -1 +1 @@ -Subproject commit 611762072933bf7bcbac9848bfead5a367a22ded +Subproject commit 778fbca580812d759979c8431b01899feafa93cd diff --git a/submodules/linphone b/submodules/linphone index ad1d7c12c..869938486 160000 --- a/submodules/linphone +++ b/submodules/linphone @@ -1 +1 @@ -Subproject commit ad1d7c12c9b459660b34d63408b144bf5890f3b6 +Subproject commit 869938486e66225ded9d46d534a24df30a84d953 diff --git a/submodules/msopenh264 b/submodules/msopenh264 index 172e97a83..40c29ef77 160000 --- a/submodules/msopenh264 +++ b/submodules/msopenh264 @@ -1 +1 @@ -Subproject commit 172e97a83aa2a868fd3dbbd6e7d57ad7d55f3054 +Subproject commit 40c29ef7736feb11af1e34f587788e49fa18b5c8