diff --git a/CHANGELOG.md b/CHANGELOG.md index 232e6f4c2..b9956e1ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,8 +13,17 @@ Group changes to describe their impact on the project, as follows: ## [Unreleased] ### Added +- Auto-layout of images in chat messages +- Selection of multiple images to send in a chat message +- Sending text with image +- Latest Calls widget +- Latest Chatrooms widget +- Homescreen quick action : New message +- Rich message notifications with Linphone UI - Support of H265 video format based on Apple's VideoToolbox framework. +### Changed +- Use of Photokit instead of Asset Library for image handling ### Fixed - Fix Bluetooth management diff --git a/Classes/Base.lproj/ChatConversationView.xib b/Classes/Base.lproj/ChatConversationView.xib index 604c03dc1..90c5815cd 100644 --- a/Classes/Base.lproj/ChatConversationView.xib +++ b/Classes/Base.lproj/ChatConversationView.xib @@ -18,6 +18,8 @@ + + @@ -269,6 +271,28 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/Classes/ChatConversationTableView.h b/Classes/ChatConversationTableView.h index 0f6162f47..551a22a4d 100644 --- a/Classes/ChatConversationTableView.h +++ b/Classes/ChatConversationTableView.h @@ -27,8 +27,7 @@ @protocol ChatConversationDelegate -- (BOOL)startImageUpload:(UIImage *)image url:(NSURL *)url withQuality:(float)quality; -- (BOOL)startFileUpload:(NSData *)data withUrl:(NSURL *)url; +- (BOOL)startImageUpload:(UIImage *)image assetId:(NSString *)phAssetId withQuality:(float)quality; - (void)resendChat:(NSString *)message withExternalUrl:(NSString *)url; - (void)tableViewIsScrolling; @@ -41,6 +40,7 @@ @property(nonatomic) LinphoneChatRoom *chatRoom; @property(nonatomic, strong) id chatRoomDelegate; +@property NSMutableDictionary *imagesInChatroom; - (void)addEventEntry:(LinphoneEventLog *)event; - (void)scrollToBottom:(BOOL)animated; diff --git a/Classes/ChatConversationTableView.m b/Classes/ChatConversationTableView.m index 0ff816e0d..20842d192 100644 --- a/Classes/ChatConversationTableView.m +++ b/Classes/ChatConversationTableView.m @@ -39,29 +39,36 @@ - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; self.tableView.accessibilityIdentifier = @"ChatRoom list"; + _imagesInChatroom = [NSMutableDictionary dictionary]; } #pragma mark - - (void)clearEventList { - [eventList removeAllObjects]; + for (NSValue *value in eventList) { + LinphoneEventLog *event = value.pointerValue; + linphone_event_log_unref(event); + } + [eventList removeAllObjects]; } - (void)updateData { + [self clearEventList]; if (!_chatRoom) return; - [self clearEventList]; + LinphoneChatRoomCapabilitiesMask capabilities = linphone_chat_room_get_capabilities(_chatRoom); bctbx_list_t *chatRoomEvents = (capabilities & LinphoneChatRoomCapabilitiesOneToOne) ? linphone_chat_room_get_history_message_events(_chatRoom, 0) : linphone_chat_room_get_history_events(_chatRoom, 0); - + bctbx_list_t *head = chatRoomEvents; eventList = [[NSMutableArray alloc] initWithCapacity:bctbx_list_size(chatRoomEvents)]; while (chatRoomEvents) { LinphoneEventLog *event = (LinphoneEventLog *)chatRoomEvents->data; [eventList addObject:[NSValue valueWithPointer:linphone_event_log_ref(event)]]; chatRoomEvents = chatRoomEvents->next; } + bctbx_list_free_with_data(head, (bctbx_list_free_func)linphone_event_log_unref); for (FileTransferDelegate *ftd in [LinphoneManager.instance fileTransferDelegates]) { const LinphoneAddress *ftd_peer = @@ -83,7 +90,6 @@ - (void)addEventEntry:(LinphoneEventLog *)event { [eventList addObject:[NSValue valueWithPointer:linphone_event_log_ref(event)]]; int pos = (int)eventList.count - 1; - NSIndexPath *indexPath = [NSIndexPath indexPathForRow:pos inSection:0]; [self.tableView beginUpdates]; [self.tableView insertRowsAtIndexPaths:@[ indexPath ] withRowAnimation:UITableViewRowAnimationFade]; @@ -98,16 +104,16 @@ } [self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:index inSection:0]] withRowAnimation:FALSE]; // just reload - return; + return; } - (void)scrollToBottom:(BOOL)animated { - [self.tableView reloadData]; + //[self.tableView reloadData]; size_t count = eventList.count; if (!count) return; - [self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:(count - 1) inSection:0]]; + //[self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:(count - 1) inSection:0]]; [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:(count - 1) inSection:0] atScrollPosition:UITableViewScrollPositionBottom animated:YES]; diff --git a/Classes/ChatConversationView.h b/Classes/ChatConversationView.h index 7a264ca8e..422cad12a 100644 --- a/Classes/ChatConversationView.h +++ b/Classes/ChatConversationView.h @@ -27,12 +27,13 @@ #import "UIRoundedImageView.h" #import "UIBackToCallButton.h" #import "Utils/HPGrowingTextView/HPGrowingTextView.h" +#import "UIImageViewDeletable.h" #include "linphone/linphonecore.h" @interface ChatConversationView - : TPMultiLayoutViewController { + : TPMultiLayoutViewController { OrderedDictionary *imageQualities; BOOL scrollOnGrowingEnabled; BOOL composingVisible; @@ -60,6 +61,13 @@ @property (weak, nonatomic) IBOutlet UIIconButton *infoButton; @property (weak, nonatomic) IBOutlet UILabel *particpantsLabel; @property (nonatomic, strong) UIDocumentInteractionController *documentInteractionController; +@property (nonatomic, strong) UIDocumentPickerViewController *documentPicker; +@property NSMutableArray *imagesArray; +@property NSMutableArray *assetIdsArray; +@property NSMutableArray *qualitySettingsArray; +@property (weak, nonatomic) IBOutlet UICollectionView *imagesCollectionView; +@property (weak, nonatomic) IBOutlet UIView *imagesView; + + (void)markAsRead:(LinphoneChatRoom *)chatRoom; - (void)configureForRoom:(BOOL)editing; @@ -73,6 +81,8 @@ - (IBAction)onDeleteClick:(id)sender; - (IBAction)onEditionChangeClick:(id)sender; - (void)update; -- (void)openResults:(NSString *) filePath; +- (void)getIcloudFiles; +- (void)openFile:(NSString *) filePath; +- (void)clearMessageView; @end diff --git a/Classes/ChatConversationView.m b/Classes/ChatConversationView.m index bf5dd6555..cd0d3edd4 100644 --- a/Classes/ChatConversationView.m +++ b/Classes/ChatConversationView.m @@ -17,6 +17,8 @@ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ +#import + #import "ChatConversationView.h" #import "PhoneMainView.h" #import "Utils.h" @@ -99,6 +101,8 @@ static UICompositeViewDescription *compositeDescription = nil; _messageField.contentInset = UIEdgeInsetsMake(-15, 0, 0, 0); // _messageField.internalTextView.scrollIndicatorInsets = UIEdgeInsetsMake(0, 0, 0, 10); [_tableController setChatRoomDelegate:self]; + [_imagesCollectionView registerClass:[UIImageViewDeletable class] forCellWithReuseIdentifier:NSStringFromClass([UIImageViewDeletable class])]; + [_imagesCollectionView setDataSource:self]; } - (void)viewWillAppear:(BOOL)animated { @@ -123,6 +127,24 @@ static UICompositeViewDescription *compositeDescription = nil; selector:@selector(callUpdateEvent:) name:kLinphoneCallUpdate object:nil]; + + if ([_imagesArray count] > 0) { + [UIView animateWithDuration:0 + delay:0 + options:UIViewAnimationOptionBeginFromCurrentState + animations:^{ + // resizing imagesView + CGRect imagesFrame = [_imagesView frame]; + imagesFrame.origin.y = [_messageView frame].origin.y - 100; + imagesFrame.size.height = 100; + [_imagesView setFrame:imagesFrame]; + // resizing chatTable + CGRect tableViewFrame = [_tableController.tableView frame]; + tableViewFrame.size.height -= 100; + [_tableController.tableView setFrame:tableViewFrame]; + } + completion:nil]; + } } - (void)viewWillDisappear:(BOOL)animated { @@ -151,6 +173,7 @@ static UICompositeViewDescription *compositeDescription = nil; [self configureForRoom:true]; _backButton.hidden = _tableController.isEditing; [_tableController scrollToBottom:true]; + [self refreshImageDrawer]; } #pragma mark - @@ -201,7 +224,6 @@ static UICompositeViewDescription *compositeDescription = nil; _messageField.editable = !linphone_chat_room_has_been_left(_chatRoom); _pictureButton.enabled = !linphone_chat_room_has_been_left(_chatRoom); _messageView.userInteractionEnabled = !linphone_chat_room_has_been_left(_chatRoom); - [_messageField setText:@""]; [_tableController setChatRoom:_chatRoom]; _chatView.hidden = NO; @@ -211,38 +233,34 @@ static UICompositeViewDescription *compositeDescription = nil; - (void)shareFile { NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:groupName]; - - NSDictionary *dict = [defaults valueForKey:@"img"]; - NSDictionary *dictWeb = [defaults valueForKey:@"web"]; - NSDictionary *dictFile = [defaults valueForKey:@"mov"]; - NSDictionary *dictText = [defaults valueForKey:@"text"]; + NSDictionary *dict = [defaults valueForKey:@"photoData"]; + NSDictionary *dictFile = [defaults valueForKey:@"icloudData"]; + NSDictionary *dictUrl = [defaults valueForKey:@"url"]; if (dict) { - //share photo - NSData *data = dict[@"nsData"]; - UIImage *image = [[UIImage alloc] initWithData:data]; - [self chooseImageQuality:image url:nil]; - [defaults removeObjectForKey:@"img"]; - } else if (dictWeb) { - //share url, if local file, then upload file - NSString *url = dictWeb[@"url"]; - NSURL *fileUrl = [NSURL fileURLWithPath:url]; - if ([url hasPrefix:@"file"]) { - //local file - NSData *data = dictWeb[@"nsData"]; - [self confirmShare:data url:fileUrl text:nil]; + //file shared from photo lib + NSString *fileName = dict[@"url"]; + NSString *key = [[fileName componentsSeparatedByString:@"."] firstObject]; + NSMutableDictionary * assetDict = [LinphoneUtils photoAssetsDictionary]; + if ([fileName hasSuffix:@"JPG"] || [fileName hasSuffix:@"PNG"]) { + UIImage *image = [[UIImage alloc] initWithData:dict[@"nsData"]]; + [self chooseImageQuality:image assetId:[[assetDict objectForKey:key] localIdentifier]]; + } else if ([fileName hasSuffix:@"MOV"]) { + [self confirmShare:dict[@"nsData"] url:nil fileName:nil assetId:[[assetDict objectForKey:key] localIdentifier]]; } else { - [self confirmShare:nil url:nil text:url]; + LOGE(@"Unable to parse file %@",fileName); } - [defaults removeObjectForKey:@"web"]; - }else if (dictFile) { - //share file - NSData *data = dictFile[@"nsData"]; - [self confirmShare:data url:[NSURL fileURLWithPath:dictFile[@"url"]] text:nil]; - [defaults removeObjectForKey:@"mov"]; - }else if (dictText) { - //share text - [self confirmShare:nil url:nil text:dictText[@"name"]]; - [defaults removeObjectForKey:@"text"]; + + [defaults removeObjectForKey:@"photoData"]; + } else if (dictFile) { + NSString *fileName = dictFile[@"url"]; + [self confirmShare:dictFile[@"nsData"] url:nil fileName:fileName assetId:nil]; + + [defaults removeObjectForKey:@"icloudData"]; + } else if (dictUrl) { + NSString *url = dictUrl[@"url"]; + [self confirmShare:nil url:url fileName:nil assetId:nil]; + + [defaults removeObjectForKey:@"url"]; } } @@ -308,38 +326,15 @@ static UICompositeViewDescription *compositeDescription = nil; return TRUE; } -- (void)saveAndSend:(UIImage *)image url:(NSURL *)url withQuality:(float)quality{ - // photo from Camera, must be saved first - if (url == 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]); - - UIAlertController *errView = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Transfer error", nil) - message:NSLocalizedString(@"Cannot write image to photo library", - nil) - preferredStyle:UIAlertControllerStyleAlert]; - - UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:@"OK" - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) {}]; - - [errView addAction:defaultAction]; - [self presentViewController:errView animated:YES completion:nil]; - } else { - LOGI(@"Image saved to [%@]", [assetURL absoluteString]); - [self startImageUpload:image url:assetURL withQuality:quality]; - } - }]; - } else { - [self startImageUpload:image url:url withQuality:quality]; - } +- (void)saveAndSend:(UIImage *)image assetId:(NSString *)phAssetId withQuality:(float)quality{ + + [_imagesArray addObject:image]; + [_assetIdsArray addObject:phAssetId]; + [_qualitySettingsArray addObject:@(quality)]; + [self refreshImageDrawer]; } -- (void)chooseImageQuality:(UIImage *)image url:(NSURL *)url { +- (void)chooseImageQuality:(UIImage *)image assetId:(NSString *)phAssetId { DTActionSheet *sheet = [[DTActionSheet alloc] initWithTitle:NSLocalizedString(@"Choose the image size", nil)]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ for (NSString *key in [imageQualities allKeys]) { @@ -349,7 +344,7 @@ static UICompositeViewDescription *compositeDescription = nil; NSString *text = [NSString stringWithFormat:@"%@ (%@)", key, [size toHumanReadableSize]]; [sheet addButtonWithTitle:text block:^() { - [self saveAndSend:image url:url withQuality:[quality floatValue]]; + [self saveAndSend:image assetId:phAssetId withQuality:[quality floatValue]]; }]; } [sheet addCancelButtonWithTitle:NSLocalizedString(@"Cancel", nil) block:nil]; @@ -359,17 +354,17 @@ static UICompositeViewDescription *compositeDescription = nil; }); } -- (void)confirmShare:(NSData *)data url:(NSURL *)url text:(NSString *)text { +- (void)confirmShare:(NSData *)data url:(NSString *)url fileName:(NSString *)fileName assetId:(NSString *)phAssetId { DTActionSheet *sheet = [[DTActionSheet alloc] initWithTitle:NSLocalizedString(@"", nil)]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [sheet addButtonWithTitle:@"send to this friend" block:^() { - if(data && url) - [self startFileUpload:data withUrl:url]; + if (url) + [self sendMessage:url withExterlBodyUrl:nil withInternalURL:nil]; + else if (fileName) + [self startFileUpload:data withName:fileName]; else - [self sendMessage:text withExterlBodyUrl:nil withInternalURL:nil]; - + [self startFileUpload:data assetId:phAssetId]; }]; [sheet addCancelButtonWithTitle:NSLocalizedString(@"Cancel", nil) block:nil]; @@ -481,6 +476,12 @@ static UICompositeViewDescription *compositeDescription = nil; messageRect.size.height += diff; [_messageView setFrame:messageRect]; + if ([_imagesArray count] > 0) { + CGRect _imagesRect = [_imagesView frame]; + _imagesRect.origin.y -= diff; + [_imagesView setFrame:_imagesRect]; + } + // Always stay at bottom if (scrollOnGrowingEnabled) { CGRect tableFrame = [_tableController.view frame]; @@ -518,6 +519,15 @@ static UICompositeViewDescription *compositeDescription = nil; } - (IBAction)onSendClick:(id)event { + if ([_imagesArray count] > 0) { + int i = 0; + for (i = 0; i < [_imagesArray count] - 1; ++i) { + [self startImageUpload:[_imagesArray objectAtIndex:i] assetId:[_assetIdsArray objectAtIndex:i] withQuality:[_qualitySettingsArray objectAtIndex:i].floatValue]; + } + [self startImageUpload:[_imagesArray objectAtIndex:i] assetId:[_assetIdsArray objectAtIndex:i] withQuality:[_qualitySettingsArray objectAtIndex:i].floatValue andMessage:[self.messageField text]]; + [self clearMessageView]; + return; + } if ([self sendMessage:[_messageField text] withExterlBodyUrl:nil withInternalURL:nil]) { scrollOnGrowingEnabled = FALSE; [_messageField setText:@""]; @@ -601,16 +611,31 @@ static UICompositeViewDescription *compositeDescription = nil; #pragma mark ChatRoomDelegate -- (BOOL)startImageUpload:(UIImage *)image url:(NSURL *)url withQuality:(float)quality { +- (BOOL)startImageUpload:(UIImage *)image assetId:(NSString *)phAssetId withQuality:(float)quality { FileTransferDelegate *fileTransfer = [[FileTransferDelegate alloc] init]; - [fileTransfer upload:image withURL:url forChatRoom:_chatRoom withQuality:quality]; + [fileTransfer upload:image withassetId:phAssetId forChatRoom:_chatRoom withQuality:quality]; [_tableController scrollToBottom:true]; return TRUE; } -- (BOOL)startFileUpload:(NSData *)data withUrl:(NSURL *)url { +- (BOOL)startImageUpload:(UIImage *)image assetId:(NSString *)phAssetId withQuality:(float)quality andMessage:(NSString *)message { FileTransferDelegate *fileTransfer = [[FileTransferDelegate alloc] init]; - [fileTransfer uploadFile:data forChatRoom:_chatRoom withUrl:url]; + [fileTransfer setText:message]; + [fileTransfer upload:image withassetId:phAssetId forChatRoom:_chatRoom withQuality:quality]; + [_tableController scrollToBottom:true]; + return TRUE; +} + +- (BOOL)startFileUpload:(NSData *)data assetId:phAssetId { + FileTransferDelegate *fileTransfer = [[FileTransferDelegate alloc] init]; + [fileTransfer uploadVideo:data withassetId:phAssetId forChatRoom:_chatRoom]; + [_tableController scrollToBottom:true]; + return TRUE; +} + +- (BOOL)startFileUpload:(NSData *)data withName:(NSString *)name { + FileTransferDelegate *fileTransfer = [[FileTransferDelegate alloc] init]; + [fileTransfer uploadFile:data forChatRoom:_chatRoom withName:name]; [_tableController scrollToBottom:true]; return TRUE; } @@ -621,7 +646,7 @@ static UICompositeViewDescription *compositeDescription = nil; #pragma mark ImagePickerDelegate -- (void)imagePickerDelegateImage:(UIImage *)image info:(NSDictionary *)info { +- (void)imagePickerDelegateImage:(UIImage *)image info:(NSString *)phAssetId { // When getting image from the camera, it may be 90° rotated due to orientation // (image.imageOrientation = UIImageOrientationRight). Just rotate it to be face up. if (image.imageOrientation != UIImageOrientationUp) { @@ -635,9 +660,7 @@ static UICompositeViewDescription *compositeDescription = nil; if (IPAD) { [VIEW(ImagePickerView).popoverController dismissPopoverAnimated:TRUE]; } - - NSURL *url = [info valueForKey:UIImagePickerControllerReferenceURL]; - [self chooseImageQuality:image url:url]; + [self chooseImageQuality:image assetId:phAssetId]; } - (void)tableViewIsScrolling { @@ -651,6 +674,9 @@ static UICompositeViewDescription *compositeDescription = nil; - (void)keyboardWillHide:(NSNotification *)notif { NSTimeInterval duration = [[[notif userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]; + + int heightDiff = UIInterfaceOrientationIsLandscape([[UIApplication sharedApplication] statusBarOrientation]) ? 55 : 105; + [UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionBeginFromCurrentState @@ -694,6 +720,19 @@ static UICompositeViewDescription *compositeDescription = nil; } } } + + + if ([_imagesArray count] > 0){ + // resizing imagesView + CGRect imagesFrame = [_imagesView frame]; + imagesFrame.origin.y = [_messageView frame].origin.y - heightDiff; + imagesFrame.size.height = heightDiff; + [_imagesView setFrame:imagesFrame]; + // resizing chatTable + CGRect tableViewFrame = [_tableController.tableView frame]; + tableViewFrame.size.height = imagesFrame.origin.y - tableViewFrame.origin.y; + [_tableController.tableView setFrame:tableViewFrame]; + } } completion:^(BOOL finished){ @@ -702,7 +741,9 @@ static UICompositeViewDescription *compositeDescription = nil; - (void)keyboardWillShow:(NSNotification *)notif { NSTimeInterval duration = [[[notif userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]; - + + int heightDiff = UIInterfaceOrientationIsLandscape([[UIApplication sharedApplication] statusBarOrientation]) ? 55 : 105; + [UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionBeginFromCurrentState @@ -749,6 +790,18 @@ static UICompositeViewDescription *compositeDescription = nil; [_messageView frame].origin.y - tableFrame.origin.y - composeIndicatorCompensation; [_tableController.view setFrame:tableFrame]; } + + if ([_imagesArray count] > 0){ + // resizing imagesView + CGRect imagesFrame = [_imagesView frame]; + imagesFrame.origin.y = [_messageView frame].origin.y - heightDiff; + imagesFrame.size.height = heightDiff; + [_imagesView setFrame:imagesFrame]; + // resizing chatTable + CGRect tableViewFrame = [_tableController.tableView frame]; + tableViewFrame.size.height = imagesFrame.origin.y - tableViewFrame.origin.y; + [_tableController.tableView setFrame:tableViewFrame]; + } // Scroll NSInteger lastSection = [_tableController.tableView numberOfSections] - 1; @@ -761,8 +814,10 @@ static UICompositeViewDescription *compositeDescription = nil; animated:FALSE]; } } + } completion:^(BOOL finished){ + }]; } @@ -852,7 +907,17 @@ void on_chat_room_conference_left(LinphoneChatRoom *cr, const LinphoneEventLog * [view.tableController scrollToBottom:true]; } -- (void)openResults:(NSString *) filePath +- (void)getIcloudFiles +{ + _documentPicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[@"public.data"] + inMode:UIDocumentPickerModeImport]; + _documentPicker.delegate = self; + + _documentPicker.modalPresentationStyle = UIModalPresentationFormSheet; + [self presentViewController:_documentPicker animated:YES completion:nil]; +} + +- (void)openFile:(NSString *) filePath { // Open the controller. _documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:[NSURL fileURLWithPath:filePath]]; @@ -866,4 +931,95 @@ void on_chat_room_conference_left(LinphoneChatRoom *cr, const LinphoneEventLog * } } +- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentAtURL:(NSURL *)url { + + NSFileCoordinator *fileCoordinator = [[NSFileCoordinator alloc] initWithFilePresenter:nil]; + [fileCoordinator coordinateReadingItemAtURL:url options:NSFileCoordinatorReadingWithoutChanges error:nil byAccessor:^(NSURL * _Nonnull newURL) { + + NSString *fileName = [newURL lastPathComponent]; + NSData *data = [NSData dataWithContentsOfURL:newURL]; + + NSString *filePath = [[LinphoneManager cacheDirectory] stringByAppendingPathComponent:fileName]; + + [[NSFileManager defaultManager] createFileAtPath:filePath contents:data attributes:nil]; + [self openFile:filePath]; + }]; +} + +- (void)deleteImageWithAssetId:(NSString *)assetId { + NSUInteger key = [_assetIdsArray indexOfObject:assetId]; + [_imagesArray removeObjectAtIndex:key]; + [_assetIdsArray removeObjectAtIndex:key]; + [self refreshImageDrawer]; +} + +- (void)clearMessageView { + [_messageField setText:@""]; + _imagesArray = [NSMutableArray array]; + _assetIdsArray = [NSMutableArray array]; + + [self refreshImageDrawer]; +} + +- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { + return [_imagesArray count]; +} + +- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { + UIImageViewDeletable *imgView = [collectionView dequeueReusableCellWithReuseIdentifier:NSStringFromClass([UIImageViewDeletable class]) forIndexPath:indexPath]; + CGRect imgFrame = imgView.frame; + imgFrame.origin.y = 5; + if (UIInterfaceOrientationIsLandscape([[UIApplication sharedApplication] statusBarOrientation])) { + imgFrame.size.height = 50; + } else { + imgFrame.size.height = 100; + } + [imgView.image setImage:[UIImage resizeImage:[_imagesArray objectAtIndex:[indexPath item]] withMaxWidth:imgFrame.size.width andMaxHeight:imgFrame.size.height]]; + [imgView setAssetId:[_assetIdsArray objectAtIndex:[indexPath item]]]; + [imgView setDeleteDelegate:self]; + [imgView setFrame:imgFrame]; + [_sendButton setEnabled:TRUE]; + return imgView; +} + +- (void)refreshImageDrawer { + int heightDiff = UIInterfaceOrientationIsLandscape([[UIApplication sharedApplication] statusBarOrientation]) ? 55 : 105; + + if ([_imagesArray count] == 0) { + [UIView animateWithDuration:0 + delay:0 + options:UIViewAnimationOptionBeginFromCurrentState + animations:^{ + // resizing imagesView + CGRect imagesFrame = [_imagesView frame]; + imagesFrame.origin.y = [_messageView frame].origin.y; + imagesFrame.size.height = 0; + [_imagesView setFrame:imagesFrame]; + // resizing chatTable + CGRect tableViewFrame = [_tableController.tableView frame]; + tableViewFrame.size.height = imagesFrame.origin.y - tableViewFrame.origin.y; + [_tableController.tableView setFrame:tableViewFrame]; + } + completion:nil]; + if ([_messageField.text isEqualToString:@""]) + [_sendButton setEnabled:FALSE]; + } else { + [UIView animateWithDuration:0 + delay:0 + options:UIViewAnimationOptionBeginFromCurrentState + animations:^{ + // resizing imagesView + CGRect imagesFrame = [_imagesView frame]; + imagesFrame.origin.y = [_messageView frame].origin.y - heightDiff; + imagesFrame.size.height = heightDiff; + [_imagesView setFrame:imagesFrame]; + // resizing chatTable + CGRect tableViewFrame = [_tableController.tableView frame]; + tableViewFrame.size.height = imagesFrame.origin.y - tableViewFrame.origin.y; + [_tableController.tableView setFrame:tableViewFrame]; + } + completion:^(BOOL result){[_imagesCollectionView reloadData];}]; + } +} + @end diff --git a/Classes/ChatsListTableView.m b/Classes/ChatsListTableView.m index abeeeee96..ad78cb768 100644 --- a/Classes/ChatsListTableView.m +++ b/Classes/ChatsListTableView.m @@ -141,6 +141,10 @@ static int sorted_history_comparison(LinphoneChatRoom *to_insert, LinphoneChatRo while (sorted) { NSMutableDictionary *dict = [NSMutableDictionary dictionary]; LinphoneChatRoom *cr = sorted->data; + if (!cr) { + sorted = sorted->next; + continue; + } const LinphoneAddress *peer_address = linphone_chat_room_get_peer_address(cr); const LinphoneAddress *local_address = linphone_chat_room_get_local_address(cr); NSString *display; @@ -148,9 +152,17 @@ static int sorted_history_comparison(LinphoneChatRoom *to_insert, LinphoneChatRo forKey:@"peer"]; [dict setObject:[NSString stringWithUTF8String:linphone_address_as_string_uri_only(local_address)] forKey:@"local"]; - if (linphone_chat_room_get_conference_address(cr)) + if (linphone_chat_room_get_conference_address(cr)) { + if (!linphone_chat_room_get_subject(cr)) { + sorted = sorted->next; + continue; + } display = [NSString stringWithUTF8String:linphone_chat_room_get_subject(cr)]; - else { + } else { + if (!linphone_address_get_username(peer_address)) { + sorted = sorted->next; + continue; + } display = [NSString stringWithUTF8String:linphone_address_get_display_name(peer_address)?:linphone_address_get_username(peer_address)]; if ([FastAddressBook imageForAddress:peer_address]) [dict setObject:UIImageJPEGRepresentation([UIImage resizeImage:[FastAddressBook imageForAddress:peer_address] diff --git a/Classes/ImagePickerView.h b/Classes/ImagePickerView.h index 1f03bb5fd..bc02c7a15 100644 --- a/Classes/ImagePickerView.h +++ b/Classes/ImagePickerView.h @@ -21,7 +21,7 @@ @protocol ImagePickerDelegate -- (void)imagePickerDelegateImage:(UIImage *)image info:(NSDictionary *)info; +- (void)imagePickerDelegateImage:(UIImage *)image info:(NSString *)phAssetId; @end diff --git a/Classes/ImagePickerView.m b/Classes/ImagePickerView.m index c5ee92daf..fdf5f602c 100644 --- a/Classes/ImagePickerView.m +++ b/Classes/ImagePickerView.m @@ -20,7 +20,6 @@ #import #import #import -#import #import "ImagePickerView.h" #import "PhoneMainView.h" @@ -162,15 +161,42 @@ static UICompositeViewDescription *compositeDescription = nil; - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { [self dismiss]; - UIImage *image = [info objectForKey:UIImagePickerControllerEditedImage]; - if (image == nil) { - image = [info objectForKey:UIImagePickerControllerOriginalImage]; - } - if (image != nil && imagePickerDelegate != nil) { - [imagePickerDelegate imagePickerDelegateImage:image info:info]; - } + + NSURL *alassetURL = [info objectForKey:UIImagePickerControllerReferenceURL]; + PHFetchResult *phFetchResult = [PHAsset fetchAssetsWithALAssetURLs:@[alassetURL] options:nil]; + PHAsset *phasset = [phFetchResult firstObject]; + //PHAsset *phasset = [info objectForKey:UIImagePickerControllerPHAsset]; + UIImage *image = [info objectForKey:UIImagePickerControllerEditedImage] ? [info objectForKey:UIImagePickerControllerEditedImage] : [info objectForKey:UIImagePickerControllerOriginalImage]; + if (!phasset) { + __block PHObjectPlaceholder *placeHolder; + [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{ + PHAssetCreationRequest *request = [PHAssetCreationRequest creationRequestForAssetFromImage:image]; + placeHolder = [request placeholderForCreatedAsset]; + } completionHandler:^(BOOL success, NSError *error) { + if (success) { + LOGI(@"Image saved to [%@]", [placeHolder localIdentifier]); + [self passImageToDelegate:image PHAssetId:[placeHolder localIdentifier]]; + } else { + LOGE(@"Cannot save image data downloaded [%@]", [error localizedDescription]); + } + } + ]; + return; + } + [self passImageToDelegate:image PHAssetId:[phasset localIdentifier]]; } +- (void) passImageToDelegate:(UIImage *)image PHAssetId:(NSString *)assetId { + if (imagePickerDelegate != nil) { + [imagePickerDelegate imagePickerDelegateImage:image info:(NSString *)assetId]; + } +} +/* + if (imagePickerDelegate != nil) { + [imagePickerDelegate imagePickerDelegateImage:image info:(__bridge NSDictionary *)contextInfo]; + } +} +*/ - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker { [self dismiss]; } @@ -223,35 +249,57 @@ static UICompositeViewDescription *compositeDescription = nil; [PhoneMainView.instance changeCurrentView:view.compositeViewDescription]; } }; - - DTActionSheet *sheet = [[DTActionSheet alloc] initWithTitle:NSLocalizedString(@"Select the source", nil)]; - if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) { - [sheet addButtonWithTitle:NSLocalizedString(@"Camera", nil) - block:^() { - if([AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo] == AVAuthorizationStatusAuthorized ){ - if([PHPhotoLibrary authorizationStatus] != PHAuthorizationStatusDenied ){ - block(UIImagePickerControllerSourceTypeCamera); - }else{ - [[[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Photo's permission", nil) message:NSLocalizedString(@"Photo not authorized", nil) delegate:nil cancelButtonTitle:nil otherButtonTitles:@"Continue", nil] show]; - } - }else { - [[[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Camera's permission", nil) message:NSLocalizedString(@"Camera not authorized", nil) delegate:nil cancelButtonTitle:nil otherButtonTitles:@"Continue", nil] show]; - } - }]; - } - if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary]) { - [sheet addButtonWithTitle:NSLocalizedString(@"Photo library", nil) - block:^() { - if([PHPhotoLibrary authorizationStatus] != PHAuthorizationStatusDenied ){ - block(UIImagePickerControllerSourceTypePhotoLibrary); - }else{ - [[[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Photo's permission", nil) message:NSLocalizedString(@"Photo not authorized", nil) delegate:nil cancelButtonTitle:nil otherButtonTitles:@"Continue", nil] show]; - } - }]; - } - [sheet addCancelButtonWithTitle:NSLocalizedString(@"Cancel", nil) block:nil]; - - [sheet showInView:PhoneMainView.instance.view]; + if ([PHPhotoLibrary authorizationStatus] == PHAuthorizationStatusAuthorized) { + DTActionSheet *sheet = [[DTActionSheet alloc] initWithTitle:NSLocalizedString(@"Select the source", nil)]; + if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) { + [sheet addButtonWithTitle:NSLocalizedString(@"Camera", nil) + block:^() { + if([AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo] != AVAuthorizationStatusAuthorized ) { + [[[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Camera's permission", nil) message:NSLocalizedString(@"Camera not authorized", nil) delegate:nil cancelButtonTitle:nil otherButtonTitles:@"Continue", nil] show]; + return; + } + block(UIImagePickerControllerSourceTypeCamera); + }]; + } + if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary]) { + [sheet addButtonWithTitle:NSLocalizedString(@"Photo library", nil) + block:^() { + block(UIImagePickerControllerSourceTypePhotoLibrary); + }]; + } + [sheet addCancelButtonWithTitle:NSLocalizedString(@"Cancel", nil) block:nil]; + + [sheet showInView:PhoneMainView.instance.view]; + } else { + [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) { + dispatch_async(dispatch_get_main_queue(), ^{ + if ([PHPhotoLibrary authorizationStatus] == PHAuthorizationStatusAuthorized) { + DTActionSheet *sheet = [[DTActionSheet alloc] initWithTitle:NSLocalizedString(@"Select the source", nil)]; + if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) { + [sheet addButtonWithTitle:NSLocalizedString(@"Camera", nil) + block:^() { + if([AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo] != AVAuthorizationStatusAuthorized ) { + [[[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Camera's permission", nil) message:NSLocalizedString(@"Camera not authorized", nil) delegate:nil cancelButtonTitle:nil otherButtonTitles:@"Continue", nil] show]; + return; + } + block(UIImagePickerControllerSourceTypeCamera); + }]; + } + if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary]) { + [sheet addButtonWithTitle:NSLocalizedString(@"Photo library", nil) + block:^() { + block(UIImagePickerControllerSourceTypePhotoLibrary); + }]; + } + [sheet addCancelButtonWithTitle:NSLocalizedString(@"Cancel", nil) block:nil]; + + [sheet showInView:PhoneMainView.instance.view]; + } else { + [[[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Photo's permission", nil) message:NSLocalizedString(@"Photo not authorized", nil) delegate:nil cancelButtonTitle:nil otherButtonTitles:@"Continue", nil] show]; + } + }); + }]; + } } @end diff --git a/Classes/LinphoneManager.h b/Classes/LinphoneManager.h index c1eab0464..9dd0454de 100644 --- a/Classes/LinphoneManager.h +++ b/Classes/LinphoneManager.h @@ -20,7 +20,7 @@ #import #import #import -#import +#import #import #import @@ -208,6 +208,8 @@ typedef struct _LinphoneManagerSounds { - (void)checkNewVersion; +- (void)loadAvatar; + @property ProviderDelegate *providerDelegate; @property (readonly) BOOL isTesting; @@ -225,7 +227,6 @@ typedef struct _LinphoneManagerSounds { @property (nonatomic, assign) BOOL speakerEnabled; @property (nonatomic, assign) BOOL bluetoothAvailable; @property (nonatomic, assign) BOOL bluetoothEnabled; -@property (readonly) ALAssetsLibrary *photoLibrary; @property (readonly) NSString* contactSipField; @property (readonly,copy) NSString* contactFilter; @property (copy) void (^silentPushCompletion)(UIBackgroundFetchResult); @@ -238,5 +239,6 @@ typedef struct _LinphoneManagerSounds { @property NSDictionary *pushDict; @property(strong, nonatomic) OrderedDictionary *linphoneManagerAddressBookMap; @property (nonatomic, assign) BOOL contactsUpdated; +@property UIImage *avatar; @end diff --git a/Classes/LinphoneManager.m b/Classes/LinphoneManager.m index f67b607c0..bb6365458 100644 --- a/Classes/LinphoneManager.m +++ b/Classes/LinphoneManager.m @@ -264,7 +264,6 @@ struct codec_name_pref_table codec_pref_table[] = {{"speex", 8000, "speex_8k_pre _fileTransferDelegates = [[NSMutableArray alloc] init]; _linphoneManagerAddressBookMap = [[OrderedDictionary alloc] init]; pushCallIDs = [[NSMutableArray alloc] init]; - _photoLibrary = [[ALAssetsLibrary alloc] init]; _isTesting = [LinphoneManager isRunningTests]; [self renameDefaultSettings]; [self copyDefaultSettings]; @@ -289,6 +288,7 @@ struct codec_name_pref_table codec_pref_table[] = {{"speex", 8000, "speex_8k_pre } [self migrateFromUserPrefs]; + [self loadAvatar]; } return self; } @@ -1231,7 +1231,39 @@ static void linphone_iphone_popup_password_request(LinphoneCore *lc, LinphoneAut } content.sound = [UNNotificationSound soundNamed:@"msg.caf"]; content.categoryIdentifier = @"msg_cat"; - content.userInfo = @{@"from" : from, @"peer_addr" : peer_uri, @"local_addr" : local_uri, @"CallId" : callID}; + // save data to user info for rich notification content + NSMutableArray *msgs = [NSMutableArray array]; + bctbx_list_t *history = linphone_chat_room_get_history(room, 6); + while (history) { + NSMutableDictionary *msgData = [NSMutableDictionary dictionary]; + LinphoneChatMessage *msg = history->data; + const char *state = linphone_chat_message_state_to_string(linphone_chat_message_get_state(msg)); + bool_t isOutgoing = linphone_chat_message_is_outgoing(msg); + bool_t isFileTransfer = (linphone_chat_message_get_file_transfer_information(msg) != NULL); + const LinphoneAddress *fromAddress = linphone_chat_message_get_from_address(msg); + NSString *displayNameDate = [NSString stringWithFormat:@"%@ - %@", [LinphoneUtils timeToString:linphone_chat_message_get_time(msg) + withFormat:LinphoneDateChatBubble], + [FastAddressBook displayNameForAddress:fromAddress]]; + UIImage *fromImage = [UIImage resizeImage:[FastAddressBook imageForAddress:fromAddress] + withMaxWidth:200 + andMaxHeight:200]; + NSData *fromImageData = UIImageJPEGRepresentation(fromImage, 1); + [msgData setObject:[NSString stringWithUTF8String:state] forKey:@"state"]; + [msgData setObject:displayNameDate forKey:@"displayNameDate"]; + [msgData setObject:[NSNumber numberWithBool:isFileTransfer] forKey:@"isFileTransfer"]; + [msgData setObject:fromImageData forKey:@"fromImageData"]; + if (isFileTransfer) { + LinphoneContent *file = linphone_chat_message_get_file_transfer_information(msg); + const char *filename = linphone_content_get_name(file); + [msgData setObject:[NSString stringWithUTF8String:filename] forKey:@"msg"]; + } else { + [msgData setObject:[UIChatBubbleTextCell TextMessageForChat:msg] forKey:@"msg"]; + } + [msgData setObject:[NSNumber numberWithBool:isOutgoing] forKey:@"isOutgoing"]; + [msgs addObject:msgData]; + history = bctbx_list_next(history); + } + content.userInfo = @{@"from" : from, @"peer_addr" : peer_uri, @"local_addr" : local_uri, @"CallId" : callID, @"msgs" : msgs}; content.accessibilityLabel = @"Message notif"; UNNotificationRequest *req = [UNNotificationRequest requestWithIdentifier:@"call_request" content:content trigger:NULL]; [[UNUserNotificationCenter currentNotificationCenter] @@ -2888,20 +2920,19 @@ static int comp_call_state_paused(const LinphoneCall *call, const void *param) { } + (void)setValueInMessageAppData:(id)value forKey:(NSString *)key inMessage:(LinphoneChatMessage *)msg { + NSMutableDictionary *appDataDict = [NSMutableDictionary dictionary]; + const char *appData = linphone_chat_message_get_appdata(msg); + if (appData) { + appDataDict = [NSJSONSerialization JSONObjectWithData:[NSData dataWithBytes:appData length:strlen(appData)] + options:NSJSONReadingMutableContainers + error:nil]; + } - NSMutableDictionary *appDataDict = [NSMutableDictionary dictionary]; - const char *appData = linphone_chat_message_get_appdata(msg); - if (appData) { - appDataDict = [NSJSONSerialization JSONObjectWithData:[NSData dataWithBytes:appData length:strlen(appData)] - options:NSJSONReadingMutableContainers - error:nil]; - } + [appDataDict setValue:value forKey:key]; - [appDataDict setValue:value forKey:key]; - - NSData *data = [NSJSONSerialization dataWithJSONObject:appDataDict options:0 error:nil]; - NSString *appdataJSON = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; - linphone_chat_message_set_appdata(msg, [appdataJSON UTF8String]); + NSData *data = [NSJSONSerialization dataWithJSONObject:appDataDict options:0 error:nil]; + NSString *appdataJSON = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + linphone_chat_message_set_appdata(msg, [appdataJSON UTF8String]); } #pragma mark - LPConfig Functions @@ -3078,4 +3109,33 @@ static int comp_call_state_paused(const LinphoneCall *call, const void *param) { const char *curVersionCString = [curVersion cStringUsingEncoding:NSUTF8StringEncoding]; linphone_core_check_for_update(theLinphoneCore, curVersionCString); } + +- (void)loadAvatar { + NSString *assetId = [self lpConfigStringForKey:@"avatar"]; + __block UIImage *ret = nil; + if (assetId) { + PHFetchResult *assets = [PHAsset fetchAssetsWithLocalIdentifiers:[NSArray arrayWithObject:assetId] options:nil]; + if (![assets firstObject]) { + LOGE(@"Can't fetch avatar image."); + } + PHAsset *asset = [assets firstObject]; + // load avatar synchronously so that we can return UIIMage* directly - since we are + // only using thumbnail, it must be pretty fast to fetch even without cache. + PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init]; + options.synchronous = TRUE; + [[PHImageManager defaultManager] requestImageForAsset:asset targetSize:PHImageManagerMaximumSize contentMode:PHImageContentModeDefault options:options + resultHandler:^(UIImage *image, NSDictionary * info) { + if (image) + ret = [UIImage UIImageThumbnail:image thumbSize:150]; + else + LOGE(@"Can't read avatar"); + }]; + } + + if (!ret) { + ret = [UIImage imageNamed:@"avatar.png"]; + } + _avatar = ret; +} + @end diff --git a/Classes/LinphoneUI/Base.lproj/UIChatBubblePhotoCell.xib b/Classes/LinphoneUI/Base.lproj/UIChatBubblePhotoCell.xib index 65e3aef60..50403d66f 100644 --- a/Classes/LinphoneUI/Base.lproj/UIChatBubblePhotoCell.xib +++ b/Classes/LinphoneUI/Base.lproj/UIChatBubblePhotoCell.xib @@ -1,5 +1,5 @@ - + @@ -21,12 +21,14 @@ + + @@ -35,19 +37,19 @@ - - + + - - + + - - + + - + @@ -55,7 +57,7 @@ - diff --git a/Classes/LinphoneUI/UILoadingImageView.h b/Classes/LinphoneUI/UILoadingImageView.h index be8d0e738..d4caa1794 100644 --- a/Classes/LinphoneUI/UILoadingImageView.h +++ b/Classes/LinphoneUI/UILoadingImageView.h @@ -28,7 +28,7 @@ - (BOOL)isLoading; - (void)stopLoading; -@property(nonatomic, strong) ALAsset *fullImageUrl; +@property(nonatomic, strong) PHAsset *asset; @property (nonatomic, readonly) IBOutlet UIActivityIndicatorView *waitIndicatorView; @end diff --git a/Classes/PhoneMainView.m b/Classes/PhoneMainView.m index 0a0bfd3d8..366e65911 100644 --- a/Classes/PhoneMainView.m +++ b/Classes/PhoneMainView.m @@ -923,6 +923,8 @@ static RootViewManager *rootViewManagerInstance = nil; linphone_chat_room_remove_callbacks(view.chatRoom, view.chatRoomCbs); view.chatRoomCbs = NULL; + if (view.chatRoom != cr) + [view clearMessageView]; view.chatRoom = cr; self.currentRoom = view.chatRoom; if (PhoneMainView.instance.currentView == view.compositeViewDescription) diff --git a/Classes/SideMenuView.m b/Classes/SideMenuView.m index f898c9e60..8a57f81d8 100644 --- a/Classes/SideMenuView.m +++ b/Classes/SideMenuView.m @@ -104,7 +104,7 @@ #pragma mark - Image picker delegate -- (void)imagePickerDelegateImage:(UIImage *)image info:(NSDictionary *)info { +- (void)imagePickerDelegateImage:(UIImage *)image info:(NSString *)phAssetId { // When getting image from the camera, it may be 90° rotated due to orientation // (image.imageOrientation = UIImageOrientationRight). Just rotate it to be face up. if (image.imageOrientation != UIImageOrientationUp) { @@ -113,6 +113,10 @@ image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); } + + [LinphoneManager.instance lpConfigSetString:phAssetId forKey:@"avatar"]; + _avatarImage.image = [LinphoneUtils selfAvatar]; + [LinphoneManager.instance loadAvatar]; // Dismiss popover on iPad if (IPAD) { @@ -120,27 +124,6 @@ } else { [PhoneMainView.instance.mainViewController hideSideMenu:NO]; } - - NSURL *url = [info valueForKey:UIImagePickerControllerReferenceURL]; - - // taken from camera, must be saved to device first - if (!url) { - [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]); - } else { - LOGI(@"Image saved to [%@]", [assetURL absoluteString]); - } - [LinphoneManager.instance lpConfigSetString:assetURL.absoluteString forKey:@"avatar"]; - _avatarImage.image = [LinphoneUtils selfAvatar]; - }]; - } else { - [LinphoneManager.instance lpConfigSetString:url.absoluteString forKey:@"avatar"]; - _avatarImage.image = [LinphoneUtils selfAvatar]; - } } @end diff --git a/Classes/Utils/FileTransferDelegate.h b/Classes/Utils/FileTransferDelegate.h index 10504b144..867285eef 100644 --- a/Classes/Utils/FileTransferDelegate.h +++ b/Classes/Utils/FileTransferDelegate.h @@ -12,11 +12,13 @@ @interface FileTransferDelegate : NSObject -- (void)upload:(UIImage *)image withURL:(NSURL *)url forChatRoom:(LinphoneChatRoom *)chatRoom withQuality:(float)quality; -- (void)uploadFile:(NSData *)data forChatRoom:(LinphoneChatRoom *)chatRoom withUrl:(NSURL *)url; +- (void)upload:(UIImage *)image withassetId:(NSString *)phAssetId forChatRoom:(LinphoneChatRoom *)chatRoom withQuality:(float)quality; +- (void)uploadFile:(NSData *)data forChatRoom:(LinphoneChatRoom *)chatRoom withName:(NSString *)name; +- (void)uploadVideo:(NSData *)data withassetId:(NSString *)phAssetId forChatRoom:(LinphoneChatRoom *)chatRoom; - (void)cancel; - (BOOL)download:(LinphoneChatMessage *)message; - (void)stopAndDestroy; @property() LinphoneChatMessage *message; +@property() NSString *text; @end diff --git a/Classes/Utils/FileTransferDelegate.m b/Classes/Utils/FileTransferDelegate.m index 7cd878e71..4b2fd6915 100644 --- a/Classes/Utils/FileTransferDelegate.m +++ b/Classes/Utils/FileTransferDelegate.m @@ -75,60 +75,117 @@ static void linphone_iphone_file_transfer_recv(LinphoneChatMessage *message, con // chat bubble is aware of the fact that image is being saved to device [LinphoneManager setValueInMessageAppData:@"saving..." forKey:@"localimage" inMessage:message]; - [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]); - [LinphoneManager setValueInMessageAppData:nil forKey:@"localimage" inMessage:message]; - UIAlertController *errView = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Transfer error", nil) - message:NSLocalizedString(@"Cannot write image to photo library", - nil) - preferredStyle:UIAlertControllerStyleAlert]; - - UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:@"OK" - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) {}]; - - [errView addAction:defaultAction]; - [PhoneMainView.instance presentViewController:errView animated:YES completion:nil]; - } else { - LOGI(@"Image saved to [%@]", [assetURL absoluteString]); - [LinphoneManager setValueInMessageAppData:[assetURL absoluteString] - forKey:@"localimage" - inMessage:message]; - } - [NSNotificationCenter.defaultCenter - postNotificationName:kLinphoneFileTransferRecvUpdate - object:thiz - userInfo:@{ - @"state" : @(LinphoneChatMessageStateDelivered), // we dont want to - // trigger - // FileTransferDone here - @"image" : image, - @"progress" : @(1.f), - }]; - - [thiz stopAndDestroy]; - CFRelease((__bridge CFTypeRef)thiz); + __block PHObjectPlaceholder *placeHolder; + [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{ + PHAssetCreationRequest *request = [PHAssetCreationRequest creationRequestForAssetFromImage:image]; + placeHolder = [request placeholderForCreatedAsset]; + } completionHandler:^(BOOL success, NSError *error) { + dispatch_async(dispatch_get_main_queue(), ^{ + if (error) { + LOGE(@"Cannot save image data downloaded [%@]", [error localizedDescription]); + [LinphoneManager setValueInMessageAppData:nil forKey:@"localimage" inMessage:message]; + UIAlertController *errView = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Transfer error", nil) + message:NSLocalizedString(@"Cannot write image to photo library", + nil) + preferredStyle:UIAlertControllerStyleAlert]; + + UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:@"OK" + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) {}]; + + [errView addAction:defaultAction]; + [PhoneMainView.instance presentViewController:errView animated:YES completion:nil]; + } else { + LOGI(@"Image saved to [%@]", [placeHolder localIdentifier]); + [LinphoneManager setValueInMessageAppData:[placeHolder localIdentifier] + forKey:@"localimage" + inMessage:message]; + } + [NSNotificationCenter.defaultCenter + postNotificationName:kLinphoneFileTransferRecvUpdate + object:thiz + userInfo:@{ + @"state" : @(LinphoneChatMessageStateDelivered), // we dont want to + // trigger + // FileTransferDone here + @"image" : image, + @"progress" : @(1.f), }]; - } else { + + [thiz stopAndDestroy]; + CFRelease((__bridge CFTypeRef)thiz); + }); + }]; + } else if([fileType isEqualToString:@"video"]) { + CFBridgingRetain(thiz); [[LinphoneManager.instance fileTransferDelegates] removeObject:thiz]; + NSString *name =[NSString stringWithUTF8String:linphone_content_get_name(content)]; + NSString *filePath = [[LinphoneManager cacheDirectory] stringByAppendingPathComponent:name]; + [[NSFileManager defaultManager] createFileAtPath:filePath + contents:thiz.data + attributes:nil]; + // until image is properly saved, keep a reminder on it so that the + // chat bubble is aware of the fact that image is being saved to device + [LinphoneManager setValueInMessageAppData:@"saving..." forKey:@"localvideo" inMessage:message]; - NSString *key = [fileType isEqualToString:@"file"] ? @"localfile" : @"localvideo"; + __block PHObjectPlaceholder *placeHolder; + [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{ + PHAssetCreationRequest *request = [PHAssetCreationRequest creationRequestForAssetFromVideoAtFileURL:[NSURL fileURLWithPath:filePath]]; + placeHolder = [request placeholderForCreatedAsset]; + } completionHandler:^(BOOL success, NSError * _Nullable error) { + dispatch_async(dispatch_get_main_queue(), ^{ + if (error) { + LOGE(@"Cannot save video data downloaded [%@]", [error localizedDescription]); + [LinphoneManager setValueInMessageAppData:nil forKey:@"localvideo" inMessage:message]; + UIAlertController *errView = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Transfer error", nil) + message:NSLocalizedString(@"Cannot write video to photo library", + nil) + preferredStyle:UIAlertControllerStyleAlert]; + + UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:@"OK" + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) {}]; + + [errView addAction:defaultAction]; + [PhoneMainView.instance presentViewController:errView animated:YES completion:nil]; + } else { + LOGI(@"video saved to [%@]", [placeHolder localIdentifier]); + [LinphoneManager setValueInMessageAppData:[placeHolder localIdentifier] + forKey:@"localvideo" + inMessage:message]; + } + [NSNotificationCenter.defaultCenter + postNotificationName:kLinphoneFileTransferRecvUpdate + object:thiz + userInfo:@{ + @"state" : @(LinphoneChatMessageStateDelivered), // we dont want to + // trigger + // FileTransferDone here + @"progress" : @(1.f), + }]; + + [thiz stopAndDestroy]; + CFRelease((__bridge CFTypeRef)thiz); + }); + }]; + + } else { + [[LinphoneManager.instance fileTransferDelegates] removeObject:thiz]; + NSString *key = @"localfile" ; NSString *name =[NSString stringWithUTF8String:linphone_content_get_name(content)]; [LinphoneManager setValueInMessageAppData:@"saving..." forKey:key inMessage:message]; //write file to path dispatch_async(dispatch_get_main_queue(), ^{ - NSString *filePath = [LinphoneManager documentFile:name]; + NSString *filePath = [[LinphoneManager cacheDirectory] stringByAppendingPathComponent:name]; + //NSString *filePath = [LinphoneManager documentFile:name]; [[NSFileManager defaultManager] createFileAtPath:filePath contents:thiz.data attributes:nil]; [LinphoneManager setValueInMessageAppData:name forKey:key inMessage:message]; + [LinphoneManager setValueInMessageAppData:filePath forKey:@"cachedfile" inMessage:message]; [NSNotificationCenter.defaultCenter postNotificationName:kLinphoneFileTransferRecvUpdate @@ -205,14 +262,15 @@ static LinphoneBuffer *linphone_iphone_file_transfer_send(LinphoneChatMessage *m linphone_content_set_subtype(content, [subtype UTF8String]); linphone_content_set_name(content, [name UTF8String]); linphone_content_set_size(content, _data.length); - _message = linphone_chat_room_create_file_transfer_message(chatRoom, content); + //linphone_chat_message_add_text_content(_message, [_text UTF8String]); linphone_content_unref(content); linphone_chat_message_cbs_set_file_transfer_send(linphone_chat_message_get_callbacks(_message), linphone_iphone_file_transfer_send); // internal url is saved in the appdata for display and later save + LOGE(@"nnnn %@ %@",key, keyData); [LinphoneManager setValueInMessageAppData:keyData forKey:key inMessage:_message]; [LinphoneManager setValueInMessageAppData:qualityData forKey:@"uploadQuality" inMessage:_message]; @@ -224,26 +282,24 @@ static LinphoneBuffer *linphone_iphone_file_transfer_send(LinphoneChatMessage *m } } -- (void)upload:(UIImage *)image withURL:(NSURL *)url forChatRoom:(LinphoneChatRoom *)chatRoom withQuality:(float)quality { +- (void)upload:(UIImage *)image withassetId:(NSString *)phAssetId forChatRoom:(LinphoneChatRoom *)chatRoom withQuality:(float)quality { NSString *name = [NSString stringWithFormat:@"%li-%f.jpg", (long)image.hash, [NSDate timeIntervalSinceReferenceDate]]; - if (url) - [self uploadData:UIImageJPEGRepresentation(image, quality) forChatRoom:chatRoom type:@"image" subtype:@"jpeg" name:name key:@"localimage" keyData:[url absoluteString] qualityData:[NSNumber numberWithFloat:quality]]; + if (phAssetId) + [self uploadData:UIImageJPEGRepresentation(image, quality) forChatRoom:chatRoom type:@"image" subtype:@"jpeg" name:name key:@"localimage" keyData:phAssetId qualityData:[NSNumber numberWithFloat:quality]]; else [self uploadData:UIImageJPEGRepresentation(image, quality) forChatRoom:chatRoom type:@"image" subtype:@"jpeg" name:name key:@"localimage" keyData:nil qualityData:nil]; } -- (void)uploadFile:(NSData *)data forChatRoom:(LinphoneChatRoom *)chatRoom withUrl:(NSURL *)url { - NSString *name = [url lastPathComponent]; - //save file to Documents - NSString *filePath = [LinphoneManager documentFile:name]; - [[NSFileManager defaultManager] createFileAtPath:filePath - contents:[NSMutableData dataWithData:data] - attributes:nil]; - - if ([[url pathExtension] isEqualToString:@"MOV"]) - [self uploadData:data forChatRoom:chatRoom type:nil subtype:nil name:name key:@"localvideo" keyData:name qualityData:nil]; +- (void)uploadVideo:(NSData *)data withassetId:(NSString *)phAssetId forChatRoom:(LinphoneChatRoom *)chatRoom { + NSString *name = [NSString stringWithFormat:@"IMG-%f.MOV", [NSDate timeIntervalSinceReferenceDate]]; + if (phAssetId) + [self uploadData:data forChatRoom:chatRoom type:@"video" subtype:nil name:name key:@"localvideo" keyData:phAssetId qualityData:nil]; else - [self uploadData:data forChatRoom:chatRoom type:@"file" subtype:nil name:name key:@"localfile" keyData:name qualityData:nil]; + [self uploadData:data forChatRoom:chatRoom type:@"video" subtype:nil name:name key:@"localvideo" keyData:@"ending..." qualityData:nil]; +} + +- (void)uploadFile:(NSData *)data forChatRoom:(LinphoneChatRoom *)chatRoom withName:(NSString *)name { + [self uploadData:data forChatRoom:chatRoom type:@"file" subtype:nil name:name key:@"localfile" keyData:name qualityData:nil]; } diff --git a/Classes/Utils/Utils.h b/Classes/Utils/Utils.h index ced7d6937..aa3ceeda7 100644 --- a/Classes/Utils/Utils.h +++ b/Classes/Utils/Utils.h @@ -49,6 +49,8 @@ typedef enum { + (NSString *)durationToString:(int)duration; + (NSString *)intervalToString:(NSTimeInterval)interval ; ++ (NSMutableDictionary *)photoAssetsDictionary; + @end @interface NSNumber (HumanReadableSize) @@ -102,6 +104,16 @@ typedef enum { @end +@interface UIImage (ResizeAndThumbnail) + ++ (UIImage *)UIImageThumbnail:(UIImage *)image thumbSize:(CGFloat) tbSize; + ++ (UIImage *)UIImageResize:(UIImage *)image toSize:(CGSize) newSize; + ++ (CGImageRef)resizeCGImage:(CGImageRef)image toWidth:(int)width andHeight:(int)height; + +@end + /* Use that macro when you want to invoke a custom initialisation method on your class, whatever is using it (xib, source code, etc., tableview cell) */ #define INIT_WITH_COMMON_C \ diff --git a/Classes/Utils/Utils.m b/Classes/Utils/Utils.m index 42002b4fc..089902852 100644 --- a/Classes/Utils/Utils.m +++ b/Classes/Utils/Utils.m @@ -32,36 +32,10 @@ @implementation LinphoneUtils + (BOOL)hasSelfAvatar { - return [NSURL URLWithString:[LinphoneManager.instance lpConfigStringForKey:@"avatar"]] != nil; + return [LinphoneManager.instance lpConfigStringForKey:@"avatar"] != nil; } + (UIImage *)selfAvatar { - NSURL *url = [NSURL URLWithString:[LinphoneManager.instance lpConfigStringForKey:@"avatar"]]; - __block UIImage *ret = nil; - if (url) { - __block NSConditionLock *photoLock = [[NSConditionLock alloc] initWithCondition:1]; - // load avatar synchronously so that we can return UIIMage* directly - since we are - // only using thumbnail, it must be pretty fast to fetch even without cache. - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [LinphoneManager.instance.photoLibrary assetForURL:url - resultBlock:^(ALAsset *asset) { - ret = [[UIImage alloc] initWithCGImage:[asset thumbnail]]; - [photoLock lock]; - [photoLock unlockWithCondition:0]; - } - failureBlock:^(NSError *error) { - LOGE(@"Can't read avatar"); - [photoLock lock]; - [photoLock unlockWithCondition:0]; - }]; - }); - [photoLock lockWhenCondition:0]; - [photoLock unlock]; - } - - if (!ret) { - ret = [UIImage imageNamed:@"avatar.png"]; - } - return ret; + return [LinphoneManager.instance avatar]; } + (NSString *)durationToString:(int)duration { @@ -81,6 +55,41 @@ return [formatter stringFromTimeInterval:interval]; } + ++ (NSMutableDictionary *)photoAssetsDictionary { + NSMutableDictionary *assetDict = [NSMutableDictionary dictionary]; + + PHFetchOptions *options = [[PHFetchOptions alloc] init]; + [options setIncludeHiddenAssets:YES]; + [options setIncludeAllBurstAssets:YES]; + + PHFetchResult *fetchRes = [PHAsset fetchAssetsWithOptions:options]; + + for (PHAsset *asset in fetchRes) { + NSString *key = [asset valueForKey:@"filename"]; + [assetDict setObject:asset forKey:[[key componentsSeparatedByString:@"."] firstObject]]; + } + + return assetDict; +} + +/*+ (NSMutableDictionary *)videoAssetsDictionary { + NSMutableDictionary *assetDict = [NSMutableDictionary dictionary]; + + PHFetchOptions *options = [[PHFetchOptions alloc] init]; + [options setIncludeHiddenAssets:YES]; + [options setIncludeAllBurstAssets:YES]; + + PHFetchResult *fetchRes = [PHAsset fetchAssetsWithMediaType:PHAssetMediaTypeVideo options:options]; + + for (PHAsset *asset in fetchRes) { + NSString *key = [asset valueForKey:@"filename"]; + [assetDict setObject:asset forKey:[[key componentsSeparatedByString:@"."] firstObject]]; + } + + return assetDict; +}*/ + + (NSString *)timeToString:(time_t)time withFormat:(LinphoneDateFormat)format { NSString *formatstr; NSDate *todayDate = [[NSDate alloc] init]; @@ -777,3 +786,91 @@ } @end + +@implementation UIImage (ResizeAndThumbnail) + ++ (UIImage *)UIImageThumbnail:(UIImage *)image thumbSize:(CGFloat) tbSize { + // Create a thumbnail version of the image for the event object. + CGSize size = image.size; + CGSize croppedSize; + CGFloat offsetX = 0.0; + CGFloat offsetY = 0.0; + CGFloat actualTbSize = MAX(tbSize, MAX(size.height, size.width)); + // check the size of the image, we want to make it + // a square with sides the size of the smallest end + if (size.width > size.height) { + offsetX = (size.height - size.width) / 2; + croppedSize = CGSizeMake(size.height, size.height); + } else { + offsetY = (size.width - size.height) / 2; + croppedSize = CGSizeMake(size.width, size.width); + } + + // Crop the image before resize + CGRect clippedRect = CGRectMake(offsetX * -1, + offsetY * -1, + croppedSize.width, + croppedSize.height); + CGImageRef imageRef = CGImageCreateWithImageInRect([image CGImage], + clippedRect); + + UIImage *cropped = [UIImage imageWithCGImage:imageRef]; + CGImageRelease(imageRef); + // Done cropping + + // Resize the image + CGRect rect = CGRectMake(0, 0, actualTbSize, actualTbSize); + + UIGraphicsBeginImageContext(rect.size); + [cropped drawInRect:rect]; + UIImage *thumbnail = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + // Done Resizing + + return thumbnail; +} + + ++ (UIImage *)UIImageResize:(UIImage *)image toSize:(CGSize) newSize { + CGImageRef newImage = [image CGImage]; + CGSize originalSize = [image size]; + float originalAspectRatio = originalSize.width / originalSize.height; + // We resize in width and crop in height + if (originalSize.width > newSize.width) { + int height = newSize.width / originalAspectRatio; + newImage = [UIImage resizeCGImage:newImage toWidth:newSize.width andHeight:height]; + originalSize.height = height; + } + CGRect cropRect = CGRectMake(0, 0, newSize.width, newSize.height); + if (newSize.height < originalSize.height) cropRect.origin.y = (originalSize.height - newSize.height)/2; + newImage = CGImageCreateWithImageInRect(newImage, cropRect); + + + UIImage *cropped = [UIImage imageWithCGImage:newImage]; + CGImageRelease(newImage); + return cropped; +} + ++ (CGImageRef)resizeCGImage:(CGImageRef)image toWidth:(int)width andHeight:(int)height { + // create context, keeping original image properties + CGColorSpaceRef colorspace = CGImageGetColorSpace(image); + CGContextRef context = CGBitmapContextCreate(NULL, width, height, + CGImageGetBitsPerComponent(image), + CGImageGetBytesPerRow(image), + colorspace, + CGImageGetAlphaInfo(image)); + CGColorSpaceRelease(colorspace); + + if(context == NULL) + return nil; + + // draw image to context (resizing it) + CGContextDrawImage(context, CGRectMake(0, 0, width, height), image); + // extract resulting image from context + CGImageRef imgRef = CGBitmapContextCreateImage(context); + CGContextRelease(context); + + return imgRef; +} + +@end diff --git a/Resources/images/delete_img.png b/Resources/images/delete_img.png new file mode 100644 index 000000000..b9aaa86c9 Binary files /dev/null and b/Resources/images/delete_img.png differ diff --git a/linphone.xcodeproj/project.pbxproj b/linphone.xcodeproj/project.pbxproj index 11dafdd79..03c8f4e3a 100755 --- a/linphone.xcodeproj/project.pbxproj +++ b/linphone.xcodeproj/project.pbxproj @@ -63,12 +63,23 @@ 570742671D5A63DB004B9C84 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 570742661D5A63DB004B9C84 /* StoreKit.framework */; }; 5E10ED4820D00630002BF6FE /* avatar.png in Resources */ = {isa = PBXBuildFile; fileRef = 633FEBE61D3CD5570014B822 /* avatar.png */; }; 5E10ED4920D006C9002BF6FE /* chat_group_avatar.png in Resources */ = {isa = PBXBuildFile; fileRef = 8C2A81941F87B8000012A66B /* chat_group_avatar.png */; }; + 5E223A9920E244B400D06A36 /* NotificationTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E223A9820E244B400D06A36 /* NotificationTableViewCell.m */; }; 5E31290120D7A37E00CF3AAE /* NotificationCenter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5EF0C33820C806A5005081B0 /* NotificationCenter.framework */; }; 5E31290520D7A37E00CF3AAE /* TodayViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E31290420D7A37E00CF3AAE /* TodayViewController.m */; }; 5E31290820D7A37E00CF3AAE /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5E31290620D7A37E00CF3AAE /* MainInterface.storyboard */; }; 5E31290C20D7A37E00CF3AAE /* latestChatroomsWidget.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 5E31290020D7A37E00CF3AAE /* latestChatroomsWidget.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 5E31291320D7AAA100CF3AAE /* avatar.png in Resources */ = {isa = PBXBuildFile; fileRef = 633FEBE61D3CD5570014B822 /* avatar.png */; }; 5E31291A20D7AAAD00CF3AAE /* chat_group_avatar.png in Resources */ = {isa = PBXBuildFile; fileRef = 8C2A81941F87B8000012A66B /* chat_group_avatar.png */; }; + 5E32944520E4C29000BBA896 /* chat_delivered.png in Resources */ = {isa = PBXBuildFile; fileRef = 244523AC1E8266CC0037A187 /* chat_delivered.png */; }; + 5E32944C20E4C29300BBA896 /* chat_error.png in Resources */ = {isa = PBXBuildFile; fileRef = 244523AD1E8266CC0037A187 /* chat_error.png */; }; + 5E32944D20E4C29700BBA896 /* chat_read.png in Resources */ = {isa = PBXBuildFile; fileRef = 244523AE1E8266CC0037A187 /* chat_read.png */; }; + 5E3391EC20E387E000F66299 /* color_A.png in Resources */ = {isa = PBXBuildFile; fileRef = 633FEC701D3CD5570014B822 /* color_A.png */; }; + 5E3391F320E387E700F66299 /* color_D.png in Resources */ = {isa = PBXBuildFile; fileRef = 633FEC721D3CD5570014B822 /* color_D.png */; }; + 5E58962420DCE5700030868C /* UserNotifications.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8C73477B1D9BA3A00022EE8C /* UserNotifications.framework */; }; + 5E58962620DCE5700030868C /* UserNotificationsUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5E58962520DCE5700030868C /* UserNotificationsUI.framework */; }; + 5E58962A20DCE5700030868C /* NotificationViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E58962920DCE5700030868C /* NotificationViewController.m */; }; + 5E58962D20DCE5700030868C /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5E58962B20DCE5700030868C /* MainInterface.storyboard */; }; + 5E58963120DCE5710030868C /* richNotifications.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 5E58962320DCE5700030868C /* richNotifications.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 5EEE8F9B20C80C23006E4176 /* NotificationCenter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5EF0C33820C806A5005081B0 /* NotificationCenter.framework */; }; 5EEE8F9F20C80C23006E4176 /* TodayViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5EEE8F9E20C80C23006E4176 /* TodayViewController.m */; }; 5EEE8FA220C80C23006E4176 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5EEE8FA020C80C23006E4176 /* MainInterface.storyboard */; }; @@ -730,6 +741,9 @@ 8CF25D9D1F9F76BD00BEA0C1 /* chat_group_informations.png in Resources */ = {isa = PBXBuildFile; fileRef = 8CF25D9B1F9F76BC00BEA0C1 /* chat_group_informations.png */; }; 8CF25D9E1F9F76BD00BEA0C1 /* chat_group_informations@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 8CF25D9C1F9F76BD00BEA0C1 /* chat_group_informations@2x.png */; }; C90FAA7915AF54E6002091CB /* HistoryDetailsView.m in Sources */ = {isa = PBXBuildFile; fileRef = C90FAA7715AF54E6002091CB /* HistoryDetailsView.m */; }; + CF15F21E20E4F9A3008B1DE6 /* UIImageViewDeletable.m in Sources */ = {isa = PBXBuildFile; fileRef = CF15F21C20E4F9A3008B1DE6 /* UIImageViewDeletable.m */; }; + CF15F21F20E4F9A3008B1DE6 /* UIImageViewDeletable.xib in Resources */ = {isa = PBXBuildFile; fileRef = CF15F21D20E4F9A3008B1DE6 /* UIImageViewDeletable.xib */; }; + CFBD7A2A20E504AE007C5286 /* delete_img.png in Resources */ = {isa = PBXBuildFile; fileRef = CFBD7A2320E504AD007C5286 /* delete_img.png */; }; D306459E1611EC2A00BB571E /* UILoadingImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = D306459D1611EC2900BB571E /* UILoadingImageView.m */; }; D3128FE115AABC7E00A2147A /* ContactDetailsView.m in Sources */ = {isa = PBXBuildFile; fileRef = D3128FDF15AABC7E00A2147A /* ContactDetailsView.m */; }; D31AAF5E159B3919002C6B02 /* CallPausedTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = D31AAF5D159B3919002C6B02 /* CallPausedTableView.m */; }; @@ -831,6 +845,13 @@ remoteGlobalIDString = 5E3128FF20D7A37E00CF3AAE; remoteInfo = latestChatroomsWidget; }; + 5E58962F20DCE5710030868C /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 29B97313FDCFA39411CA2CEA /* Project object */; + proxyType = 1; + remoteGlobalIDString = 5E58962220DCE5700030868C; + remoteInfo = richNotifications; + }; 5EEE8FA420C80C23006E4176 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 29B97313FDCFA39411CA2CEA /* Project object */; @@ -934,6 +955,7 @@ 61AE365620C00B370089D9D3 /* linphoneExtension.appex in Embed App Extensions */, 5E31290C20D7A37E00CF3AAE /* latestChatroomsWidget.appex in Embed App Extensions */, 5EEE8FA620C80C23006E4176 /* latestCallsWidget.appex in Embed App Extensions */, + 5E58963120DCE5710030868C /* richNotifications.appex in Embed App Extensions */, ); name = "Embed App Extensions"; runOnlyForDeploymentPostprocessing = 0; @@ -1075,12 +1097,20 @@ 570742601D5A09B8004B9C84 /* ShopView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ShopView.h; sourceTree = ""; }; 570742631D5A1860004B9C84 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/ShopView.strings; sourceTree = ""; }; 570742661D5A63DB004B9C84 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; }; + 5E223A9720E244B400D06A36 /* NotificationTableViewCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NotificationTableViewCell.h; sourceTree = ""; }; + 5E223A9820E244B400D06A36 /* NotificationTableViewCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NotificationTableViewCell.m; sourceTree = ""; }; 5E30780420D8F00D00256DAE /* latestChatroomsWidget.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = latestChatroomsWidget.entitlements; sourceTree = ""; }; 5E31290020D7A37E00CF3AAE /* latestChatroomsWidget.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = latestChatroomsWidget.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 5E31290320D7A37E00CF3AAE /* TodayViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TodayViewController.h; sourceTree = ""; }; 5E31290420D7A37E00CF3AAE /* TodayViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TodayViewController.m; sourceTree = ""; }; 5E31290720D7A37E00CF3AAE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = ""; }; 5E31290920D7A37E00CF3AAE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 5E58962320DCE5700030868C /* richNotifications.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = richNotifications.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + 5E58962520DCE5700030868C /* UserNotificationsUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UserNotificationsUI.framework; path = System/Library/Frameworks/UserNotificationsUI.framework; sourceTree = SDKROOT; }; + 5E58962820DCE5700030868C /* NotificationViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NotificationViewController.h; sourceTree = ""; }; + 5E58962920DCE5700030868C /* NotificationViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NotificationViewController.m; sourceTree = ""; }; + 5E58962C20DCE5700030868C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = ""; }; + 5E58962E20DCE5710030868C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 5EEE8F9A20C80C23006E4176 /* latestCallsWidget.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = latestCallsWidget.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 5EEE8F9D20C80C23006E4176 /* TodayViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TodayViewController.h; sourceTree = ""; }; 5EEE8F9E20C80C23006E4176 /* TodayViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TodayViewController.m; sourceTree = ""; }; @@ -1829,6 +1859,10 @@ C90FAA7615AF54E6002091CB /* HistoryDetailsView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HistoryDetailsView.h; sourceTree = ""; }; C90FAA7715AF54E6002091CB /* HistoryDetailsView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HistoryDetailsView.m; sourceTree = ""; }; C9B3A6FD15B485DB006F52EE /* Utils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Utils.h; path = Utils/Utils.h; sourceTree = ""; }; + CF15F21B20E4F9A3008B1DE6 /* UIImageViewDeletable.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UIImageViewDeletable.h; sourceTree = ""; }; + CF15F21C20E4F9A3008B1DE6 /* UIImageViewDeletable.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = UIImageViewDeletable.m; sourceTree = ""; }; + CF15F21D20E4F9A3008B1DE6 /* UIImageViewDeletable.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = UIImageViewDeletable.xib; sourceTree = ""; }; + CFBD7A2320E504AD007C5286 /* delete_img.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = delete_img.png; sourceTree = ""; }; D306459C1611EC2900BB571E /* UILoadingImageView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UILoadingImageView.h; sourceTree = ""; }; D306459D1611EC2900BB571E /* UILoadingImageView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UILoadingImageView.m; sourceTree = ""; }; D3128FDE15AABC7E00A2147A /* ContactDetailsView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ContactDetailsView.h; sourceTree = ""; }; @@ -2068,6 +2102,15 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 5E58962020DCE5700030868C /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 5E58962620DCE5700030868C /* UserNotificationsUI.framework in Frameworks */, + 5E58962420DCE5700030868C /* UserNotifications.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 5EEE8F9720C80C23006E4176 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -2282,6 +2325,7 @@ 61AE364B20C00B370089D9D3 /* linphoneExtension.appex */, 5EEE8F9A20C80C23006E4176 /* latestCallsWidget.appex */, 5E31290020D7A37E00CF3AAE /* latestChatroomsWidget.appex */, + 5E58962320DCE5700030868C /* richNotifications.appex */, ); name = Products; sourceTree = ""; @@ -2289,6 +2333,9 @@ 2214EB7012F84668002A5394 /* LinphoneUI */ = { isa = PBXGroup; children = ( + CF15F21B20E4F9A3008B1DE6 /* UIImageViewDeletable.h */, + CF15F21C20E4F9A3008B1DE6 /* UIImageViewDeletable.m */, + CF15F21D20E4F9A3008B1DE6 /* UIImageViewDeletable.xib */, 63F1DF421BCE618E00EDED90 /* UIAddressTextField.h */, 63F1DF431BCE618E00EDED90 /* UIAddressTextField.m */, 63C441C11BBC23ED0053DC5E /* UIAssistantTextField.h */, @@ -2399,6 +2446,7 @@ 61AE364C20C00B370089D9D3 /* linphoneExtension */, 5EEE8F9C20C80C23006E4176 /* latestCallsWidget */, 5E31290220D7A37E00CF3AAE /* latestChatroomsWidget */, + 5E58962720DCE5700030868C /* richNotifications */, 29B97323FDCFA39411CA2CEA /* Frameworks */, F0938158188E629800A55DFA /* iTunesArtwork */, 63058A0C1B4E821E00EFAE36 /* LiblinphoneTester */, @@ -2490,6 +2538,7 @@ 2264B6D111200342002C2C53 /* SystemConfiguration.framework */, 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */, 5EF0C33820C806A5005081B0 /* NotificationCenter.framework */, + 5E58962520DCE5700030868C /* UserNotificationsUI.framework */, ); name = Frameworks; sourceTree = ""; @@ -2506,6 +2555,19 @@ path = latestChatroomsWidget; sourceTree = ""; }; + 5E58962720DCE5700030868C /* richNotifications */ = { + isa = PBXGroup; + children = ( + 5E58962820DCE5700030868C /* NotificationViewController.h */, + 5E58962920DCE5700030868C /* NotificationViewController.m */, + 5E58962B20DCE5700030868C /* MainInterface.storyboard */, + 5E58962E20DCE5710030868C /* Info.plist */, + 5E223A9720E244B400D06A36 /* NotificationTableViewCell.h */, + 5E223A9820E244B400D06A36 /* NotificationTableViewCell.m */, + ); + path = richNotifications; + sourceTree = ""; + }; 5EEE8F9C20C80C23006E4176 /* latestCallsWidget */ = { isa = PBXGroup; children = ( @@ -2632,6 +2694,7 @@ 633FEBE11D3CD5570014B822 /* images */ = { isa = PBXGroup; children = ( + CFBD7A2320E504AD007C5286 /* delete_img.png */, 24BFAA9B209B062F004F47A7 /* callkit_logo.png */, 24BFAA99209B062E004F47A7 /* contacts_sip_default.png */, 24BFAA94209B062C004F47A7 /* contacts_sip_default@2x.png */, @@ -3325,6 +3388,7 @@ 61AE365520C00B370089D9D3 /* PBXTargetDependency */, 5EEE8FA520C80C23006E4176 /* PBXTargetDependency */, 5E31290B20D7A37E00CF3AAE /* PBXTargetDependency */, + 5E58963020DCE5710030868C /* PBXTargetDependency */, ); name = linphone; productName = linphone; @@ -3348,6 +3412,23 @@ productReference = 5E31290020D7A37E00CF3AAE /* latestChatroomsWidget.appex */; productType = "com.apple.product-type.app-extension"; }; + 5E58962220DCE5700030868C /* richNotifications */ = { + isa = PBXNativeTarget; + buildConfigurationList = 5E58963C20DCE5710030868C /* Build configuration list for PBXNativeTarget "richNotifications" */; + buildPhases = ( + 5E58961F20DCE5700030868C /* Sources */, + 5E58962020DCE5700030868C /* Frameworks */, + 5E58962120DCE5700030868C /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = richNotifications; + productName = richNotifications; + productReference = 5E58962320DCE5700030868C /* richNotifications.appex */; + productType = "com.apple.product-type.app-extension"; + }; 5EEE8F9920C80C23006E4176 /* latestCallsWidget */ = { isa = PBXNativeTarget; buildConfigurationList = 5EEE8FA720C80C24006E4176 /* Build configuration list for PBXNativeTarget "latestCallsWidget" */; @@ -3462,6 +3543,9 @@ com.apple.Push = { enabled = 1; }; + com.apple.iCloud = { + enabled = 0; + }; }; }; 5E3128FF20D7A37E00CF3AAE = { @@ -3474,6 +3558,11 @@ }; }; }; + 5E58962220DCE5700030868C = { + CreatedOnToolsVersion = 9.4; + DevelopmentTeam = Z2V957B3D6; + ProvisioningStyle = Automatic; + }; 5EEE8F9920C80C23006E4176 = { CreatedOnToolsVersion = 9.4; DevelopmentTeam = Z2V957B3D6; @@ -3548,6 +3637,7 @@ 61AE364A20C00B370089D9D3 /* linphoneExtension */, 5EEE8F9920C80C23006E4176 /* latestCallsWidget */, 5E3128FF20D7A37E00CF3AAE /* latestChatroomsWidget */, + 5E58962220DCE5700030868C /* richNotifications */, ); }; /* End PBXProject section */ @@ -3691,6 +3781,7 @@ 633FEE401D3CD5590014B822 /* contacts_all_selected.png in Resources */, 633FEE0C1D3CD5590014B822 /* chat_attachment_disabled.png in Resources */, 633FEF001D3CD55A0014B822 /* options_default.png in Resources */, + CF15F21F20E4F9A3008B1DE6 /* UIImageViewDeletable.xib in Resources */, 633FEE951D3CD55A0014B822 /* micro_default@2x.png in Resources */, 633FEE6A1D3CD5590014B822 /* footer_dialer_default.png in Resources */, 633FEEC91D3CD55A0014B822 /* numpad_5~ipad.png in Resources */, @@ -3822,6 +3913,7 @@ 633FEED71D3CD55A0014B822 /* numpad_7_over~ipad.png in Resources */, 633FEF2A1D3CD55A0014B822 /* route_earpiece_disabled.png in Resources */, 633FEDDB1D3CD5590014B822 /* call_start_body_disabled@2x.png in Resources */, + CFBD7A2A20E504AE007C5286 /* delete_img.png in Resources */, 633FEDFD1D3CD5590014B822 /* camera_switch_default@2x.png in Resources */, 633FEEC51D3CD55A0014B822 /* numpad_5_over.png in Resources */, 633FEE721D3CD5590014B822 /* history_all_default.png in Resources */, @@ -4159,6 +4251,19 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 5E58962120DCE5700030868C /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 5E32944C20E4C29300BBA896 /* chat_error.png in Resources */, + 5E32944D20E4C29700BBA896 /* chat_read.png in Resources */, + 5E32944520E4C29000BBA896 /* chat_delivered.png in Resources */, + 5E3391F320E387E700F66299 /* color_D.png in Resources */, + 5E58962D20DCE5700030868C /* MainInterface.storyboard in Resources */, + 5E3391EC20E387E000F66299 /* color_A.png in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 5EEE8F9820C80C23006E4176 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -4278,6 +4383,7 @@ 1D60589B0D05DD56006BFB54 /* main.m in Sources */, 8CD99A3C2090B9FA008A7CDA /* ChatConversationImdnView.m in Sources */, 1D3623260D0F684500981E51 /* LinphoneAppDelegate.m in Sources */, + CF15F21E20E4F9A3008B1DE6 /* UIImageViewDeletable.m in Sources */, 22F2508E107141E100AC9B3F /* DialerView.m in Sources */, 633756451B67D2B200E21BAD /* SideMenuView.m in Sources */, 8CD99A422090CE6F008A7CDA /* UIChatConversationImdnTableViewCell.m in Sources */, @@ -4407,6 +4513,15 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 5E58961F20DCE5700030868C /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 5E223A9920E244B400D06A36 /* NotificationTableViewCell.m in Sources */, + 5E58962A20DCE5700030868C /* NotificationViewController.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 5EEE8F9620C80C23006E4176 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -4466,6 +4581,11 @@ target = 5E3128FF20D7A37E00CF3AAE /* latestChatroomsWidget */; targetProxy = 5E31290A20D7A37E00CF3AAE /* PBXContainerItemProxy */; }; + 5E58963020DCE5710030868C /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 5E58962220DCE5700030868C /* richNotifications */; + targetProxy = 5E58962F20DCE5710030868C /* PBXContainerItemProxy */; + }; 5EEE8FA520C80C23006E4176 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 5EEE8F9920C80C23006E4176 /* latestCallsWidget */; @@ -4512,6 +4632,14 @@ name = MainInterface.storyboard; sourceTree = ""; }; + 5E58962B20DCE5700030868C /* MainInterface.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 5E58962C20DCE5700030868C /* Base */, + ); + name = MainInterface.storyboard; + sourceTree = ""; + }; 5EEE8FA020C80C23006E4176 /* MainInterface.storyboard */ = { isa = PBXVariantGroup; children = ( @@ -5590,6 +5718,178 @@ }; name = DistributionAdhoc; }; + 5E58963220DCE5710030868C /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = Z2V957B3D6; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + INFOPLIST_FILE = richNotifications/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + MTL_ENABLE_DEBUG_INFO = YES; + PRODUCT_BUNDLE_IDENTIFIER = org.linphone.phone.richNotifications; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = ""; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 5E58963320DCE5710030868C /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = Z2V957B3D6; + ENABLE_NS_ASSERTIONS = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + INFOPLIST_FILE = richNotifications/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_BUNDLE_IDENTIFIER = org.linphone.phone.richNotifications; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = ""; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 5E58963420DCE5710030868C /* Distribution */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = Z2V957B3D6; + ENABLE_NS_ASSERTIONS = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + INFOPLIST_FILE = richNotifications/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_BUNDLE_IDENTIFIER = org.linphone.phone.richNotifications; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = ""; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Distribution; + }; + 5E58963520DCE5710030868C /* DistributionAdhoc */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = Z2V957B3D6; + ENABLE_NS_ASSERTIONS = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + INFOPLIST_FILE = richNotifications/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_BUNDLE_IDENTIFIER = org.linphone.phone.richNotifications; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = ""; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = DistributionAdhoc; + }; 5EEE8FA820C80C24006E4176 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -6595,6 +6895,17 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Debug; }; + 5E58963C20DCE5710030868C /* Build configuration list for PBXNativeTarget "richNotifications" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 5E58963220DCE5710030868C /* Debug */, + 5E58963320DCE5710030868C /* Release */, + 5E58963420DCE5710030868C /* Distribution */, + 5E58963520DCE5710030868C /* DistributionAdhoc */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; 5EEE8FA720C80C24006E4176 /* Build configuration list for PBXNativeTarget "latestCallsWidget" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/linphoneExtension/ShareViewController.m b/linphoneExtension/ShareViewController.m index ae71c00c3..1f656a9bc 100644 --- a/linphoneExtension/ShareViewController.m +++ b/linphoneExtension/ShareViewController.m @@ -20,32 +20,35 @@ static NSString* groupName = @"group.belledonne-communications.linphone"; - (void)didSelectPost { // This is called after the user selects Post. Do the upload of contentText and/or NSExtensionContext attachments. - + BOOL support = TRUE; // Inform the host that we're done, so it un-blocks its UI. Note: Alternatively you could call super's -didSelectPost, which will similarly complete the extension context. for (NSExtensionItem *item in self.extensionContext.inputItems) { for (NSItemProvider *provider in item.attachments) { NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:groupName]; - + // TODO: Use [provider registeredTypeIdentifiersWithFileOptions:0]; to get all type identifiers of the provider instead of this if/else if structure + support = TRUE; if ([provider hasItemConformingToTypeIdentifier:@"public.jpeg"]) { - [self loadItem:provider typeIdentifier:@"public.jpeg" defaults:defaults key:@"img"]; + [self loadItem:provider typeIdentifier:@"public.jpeg" defaults:defaults]; + } else if ([provider hasItemConformingToTypeIdentifier:@"com.compuserve.gif"]) { + [self loadItem:provider typeIdentifier:@"com.compuserve.gif" defaults:defaults]; } else if ([provider hasItemConformingToTypeIdentifier:@"public.url"]) { - [self loadItem:provider typeIdentifier:@"public.url" defaults:defaults key:@"web"]; + [self loadItem:provider typeIdentifier:@"public.url" defaults:defaults]; } else if ([provider hasItemConformingToTypeIdentifier:@"public.movie"]) { - [self loadItem:provider typeIdentifier:@"public.movie" defaults:defaults key:@"mov"]; - } else if ([provider hasItemConformingToTypeIdentifier:@"public.plain-text"]) { - [self loadItem:provider typeIdentifier:@"public.plain-text" defaults:defaults key:@"text"]; - } else if ([provider hasItemConformingToTypeIdentifier:@"com.adobe.pdf"]) { - [self loadItem:provider typeIdentifier:@"com.adobe.pdf" defaults:defaults key:@"web"]; - } - /*else if ([provider hasItemConformingToTypeIdentifier:@"public.png"]) { - [self loadItem:provider typeIdentifier:@"public.png" defaults:defaults key:@"img"]; - }*/ - else{ + [self loadItem:provider typeIdentifier:@"public.movie" defaults:defaults]; + } else if ([provider hasItemConformingToTypeIdentifier:@"com.apple.mapkit.map-item"]) { + [self loadItem:provider typeIdentifier:@"com.apple.mapkit.map-item" defaults:defaults]; + } else if ([provider hasItemConformingToTypeIdentifier:@"com.adobe.pdf"]) { + [self loadItem:provider typeIdentifier:@"com.adobe.pdf" defaults:defaults]; + } else if ([provider hasItemConformingToTypeIdentifier:@"public.png"]) { + [self loadItem:provider typeIdentifier:@"public.png" defaults:defaults]; + } else{ NSLog(@"Unkown itemprovider = %@", provider); - [self.extensionContext completeRequestReturningItems:@[] completionHandler:nil]; + support = false; } } } + if (!support) + [self.extensionContext completeRequestReturningItems:@[] completionHandler:nil]; } - (NSArray *)configurationItems { @@ -53,46 +56,57 @@ static NSString* groupName = @"group.belledonne-communications.linphone"; return @[]; } -- (void)loadItem:(NSItemProvider *)provider typeIdentifier:(NSString *)typeIdentifier defaults:(NSUserDefaults *)defaults key:(NSString *)key { +- (void)loadItem:(NSItemProvider *)provider typeIdentifier:(NSString *)typeIdentifier defaults:(NSUserDefaults *)defaults +{ [provider loadItemForTypeIdentifier:typeIdentifier options:nil completionHandler:^(id _Nullable item, NSError * _Null_unspecified error) { + /*if([(NSObject*)item isKindOfClass:[NSDictionary class]]) { + NSDictionary *dico = (NSDictionary *)item; + if (dico) { + return; + } + }*/ if([(NSObject*)item isKindOfClass:[NSURL class]]) { - NSData *nsData = [NSData dataWithContentsOfURL:(NSURL*)item]; + NSURL *url = (NSURL *)item; + NSData *nsData = [NSData dataWithContentsOfURL:url]; + if (nsData) { - NSDictionary *dict = @{@"nsData" : nsData, - @"url" : [(NSURL*)item absoluteString]}; - [defaults setObject:dict forKey:key]; + NSString *imgPath = url.path; + NSString *filename = [imgPath lastPathComponent]; + if([imgPath containsString:@"var/mobile/Media/PhotoData"]) { + // We get the corresponding PHAsset identifier so we can display the image in the app without having to duplicate it. + NSDictionary *dict = @{@"nsData" : nsData, + @"url" : filename}; + [defaults setObject:dict forKey:@"photoData"]; + } else if ([imgPath containsString:@"var/mobile/Library/Mobile Documents/com~apple~CloudDocs"]) { + // shared files from icloud drive + NSDictionary *dict = @{@"nsData" : nsData, + @"url" : filename}; + [defaults setObject:dict forKey:@"icloudData"]; + }else { + //Others + NSDictionary *dict = @{@"url" : [url absoluteString]}; + [defaults setObject:dict forKey:@"url"]; + } } else { NSLog(@"NSExtensionItem Error, provider = %@", provider); [self.extensionContext completeRequestReturningItems:@[] completionHandler:nil]; } - } - else if ([(NSObject*)item isKindOfClass:[UIImage class]]) { - NSData *imgData = UIImagePNGRepresentation((UIImage*)item); - if (imgData) { - NSDictionary *dict = @{@"nsData" : imgData, - }; - [defaults setObject:dict forKey:key]; - } else { - NSLog(@"NSExtensionItem Error, provider = %@", provider); - [self.extensionContext completeRequestReturningItems:@[] completionHandler:nil]; + + UIResponder *responder = self; + while (responder != nil) { + if ([responder respondsToSelector:@selector(openURL:)]) { + [responder performSelector:@selector(openURL:) + withObject:[NSURL URLWithString:@"message-linphone://" ]]; + [self.extensionContext completeRequestReturningItems:@[] completionHandler:nil]; + break; + } + responder = [responder nextResponder]; } + [defaults synchronize]; + } else { + //share text + NSLog(@"Unsupported provider = %@", provider); } - else { - NSDictionary *dict = @{@"name" : self.contentText}; - [defaults setObject:dict forKey:key]; - } - - UIResponder *responder = self; - while (responder != nil) { - if ([responder respondsToSelector:@selector(openURL:)]) { - [responder performSelector:@selector(openURL:) - withObject:[NSURL URLWithString:@"message-linphone://" ]]; - [self.extensionContext completeRequestReturningItems:@[] completionHandler:nil]; - break; - } - responder = [responder nextResponder]; - } - [defaults synchronize]; }]; } diff --git a/richNotifications/Base.lproj/MainInterface.storyboard b/richNotifications/Base.lproj/MainInterface.storyboard new file mode 100644 index 000000000..f50b1b59d --- /dev/null +++ b/richNotifications/Base.lproj/MainInterface.storyboard @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/richNotifications/Info.plist b/richNotifications/Info.plist new file mode 100644 index 000000000..f6e516e4d --- /dev/null +++ b/richNotifications/Info.plist @@ -0,0 +1,40 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + richNotifications + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + XPC! + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + NSExtension + + NSExtensionAttributes + + UNNotificationExtensionDefaultContentHidden + + UNNotificationExtensionCategory + msg_cat + UNNotificationExtensionInitialContentSizeRatio + 1 + + NSExtensionMainStoryboard + MainInterface + NSExtensionPointIdentifier + com.apple.usernotifications.content-extension + + + diff --git a/richNotifications/NotificationTableViewCell.h b/richNotifications/NotificationTableViewCell.h new file mode 100644 index 000000000..77b74e1d3 --- /dev/null +++ b/richNotifications/NotificationTableViewCell.h @@ -0,0 +1,24 @@ +// +// NotificationTableViewCell.h +// richNotifications +// +// Created by David Idmansour on 26/06/2018. +// + +#import + +@interface NotificationTableViewCell : UITableViewCell +@property (weak, nonatomic) IBOutlet UIImageView *contactImage; +@property (weak, nonatomic) IBOutlet UILabel *nameDate; +@property (strong, nonatomic) IBOutlet UITextView *msgText; +@property (weak, nonatomic) IBOutlet UILabel *imdm; +@property (weak, nonatomic) IBOutlet UIImageView *background; +@property (weak, nonatomic) IBOutlet UIImageView *bottomBarColor; +@property (weak, nonatomic) IBOutlet UIImageView *imdmImage; +@property BOOL isOutgoing; +@property float width; +@property float height; + +- (CGSize)ViewSizeForMessage:(NSString *)chat withWidth:(int)width; + +@end diff --git a/richNotifications/NotificationTableViewCell.m b/richNotifications/NotificationTableViewCell.m new file mode 100644 index 000000000..26fe0f188 --- /dev/null +++ b/richNotifications/NotificationTableViewCell.m @@ -0,0 +1,94 @@ +// +// NotificationTableViewCell.m +// richNotifications +// +// Created by David Idmansour on 26/06/2018. +// + +#import "NotificationTableViewCell.h" + +@implementation NotificationTableViewCell + +- (void)awakeFromNib { + [super awakeFromNib]; + // Initialization code + _contactImage.layer.cornerRadius = _contactImage.frame.size.height / 2; + _contactImage.clipsToBounds = YES; + [self.contentView sendSubviewToBack:_background]; +} + +- (void)setSelected:(BOOL)selected animated:(BOOL)animated { + [super setSelected:selected animated:animated]; + + // Configure the view for the selected state +} + +- (void)layoutSubviews { + [super layoutSubviews]; + CGRect bubbleFrame = self.contentView.frame; + int originX; + + bubbleFrame.size = CGSizeMake(_width, _height); + + originX = (_isOutgoing ? self.frame.size.width - bubbleFrame.size.width - 5 : 5); + + bubbleFrame.origin.x = originX; + self.contentView.frame = bubbleFrame; + + _msgText.textContainerInset = UIEdgeInsetsZero; + _msgText.textContainer.lineFragmentPadding = 0; +} + +#pragma mark - Bubble size computing + +- (CGSize)computeBoundingBox:(NSString *)text size:(CGSize)size font:(UIFont *)font { + if (!text || text.length == 0) + return CGSizeMake(0, 0); + + return [text boundingRectWithSize:size + options:(NSStringDrawingUsesLineFragmentOrigin | + NSStringDrawingTruncatesLastVisibleLine | NSStringDrawingUsesFontLeading) + attributes:@{ + NSFontAttributeName : font + } + context:nil].size; +} + +static const CGFloat CELL_MIN_HEIGHT = 60.0f; +static const CGFloat CELL_MIN_WIDTH = 190.0f; +static const CGFloat CELL_MESSAGE_X_MARGIN = 78 + 10.0f; +static const CGFloat CELL_MESSAGE_Y_MARGIN = 52; // 44; + +- (CGSize)ViewHeightForMessage:(NSString *)messageText withWidth:(int)width { + static UIFont *messageFont = nil; + + if (!messageFont) { + messageFont = _msgText.font; + } + CGSize size; + size = [self computeBoundingBox:messageText + size:CGSizeMake(width - CELL_MESSAGE_X_MARGIN - 4, CGFLOAT_MAX) + font:messageFont]; + + size.width = MAX(size.width + CELL_MESSAGE_X_MARGIN, CELL_MIN_WIDTH); + size.height = MAX(size.height + CELL_MESSAGE_Y_MARGIN, CELL_MIN_HEIGHT); + return size; +} +- (CGSize)ViewSizeForMessage:(NSString *)chat withWidth:(int)width { + static UIFont *dateFont = nil; + static CGSize dateViewSize; + + if (!dateFont) { + dateFont = _nameDate.font; + dateViewSize = _nameDate.frame.size; + dateViewSize.width = CGFLOAT_MAX; + } + + CGSize messageSize = [self ViewHeightForMessage:chat withWidth:width]; + CGSize dateSize = [self computeBoundingBox:_nameDate.text size:dateViewSize font:dateFont]; + messageSize.width = MAX(MAX(messageSize.width, MIN(dateSize.width + CELL_MESSAGE_X_MARGIN, width)), CELL_MIN_WIDTH); + + return messageSize; +} + +@end diff --git a/richNotifications/NotificationViewController.h b/richNotifications/NotificationViewController.h new file mode 100644 index 000000000..f96beffbd --- /dev/null +++ b/richNotifications/NotificationViewController.h @@ -0,0 +1,12 @@ +// +// NotificationViewController.h +// richNotifications +// +// Created by David Idmansour on 22/06/2018. +// + +#import + +@interface NotificationViewController : UITableViewController + +@end diff --git a/richNotifications/NotificationViewController.m b/richNotifications/NotificationViewController.m new file mode 100644 index 000000000..69aa955a8 --- /dev/null +++ b/richNotifications/NotificationViewController.m @@ -0,0 +1,110 @@ +// +// NotificationViewController.m +// richNotifications +// +// Created by David Idmansour on 22/06/2018. +// + +#import "NotificationViewController.h" +#import "NotificationTableViewCell.h" +#import +#import + +@interface NotificationViewController () + +@end + +@implementation NotificationViewController { + @private + NSMutableArray *msgs; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + self.tableView.scrollEnabled = TRUE; + // Do any required interface initialization here. +} + +- (void)didReceiveNotification:(UNNotification *)notification { +// static float initialHeight = -1; +// if (initialHeight < 0) +// initialHeight = self.tableView.frame.size.height; + if (msgs) + [msgs addObject:[((NSArray *)[[[[notification request] content] userInfo] objectForKey:@"msgs"]) lastObject]]; + else + msgs = [NSMutableArray arrayWithArray:[[[[notification request] content] userInfo] objectForKey:@"msgs"]]; + [self.tableView reloadData]; + [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:msgs.count - 1 inSection:0] atScrollPosition:UITableViewScrollPositionBottom animated:YES]; +// float height = 0; +// for (int i = 0 ; i < self->msgs.count ; i++) { +// height += [self tableView:self.tableView heightForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:0]]; +// } +// if (height > initialHeight) { +// CGRect frame = self.tableView.frame; +// frame.size.height = height; +// frame.origin = CGPointMake(0, 0); +// self.tableView.frame = frame; +// self.tableView.bounds = frame; +// self.preferredContentSize = CGSizeMake(self.preferredContentSize.width, height); +// } +} + +#pragma mark - UITableViewDataSource Functions + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return 1; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return msgs.count; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + BOOL isOutgoing = ((NSNumber *)[msgs[indexPath.row] objectForKey:@"isOutgoing"]).boolValue; + NSString *display = ((NSString *)[msgs[indexPath.row] objectForKey:@"displayNameDate"]); + NSString *msgText = ((NSString *)[msgs[indexPath.row] objectForKey:@"msg"]); + NSString *imdm = ((NSString *)[msgs[indexPath.row] objectForKey:@"state"]); + NSData *imageData = [msgs[indexPath.row] objectForKey:@"fromImageData"]; + NotificationTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"notificationCell" forIndexPath:indexPath]; + + cell.background.image = cell.bottomBarColor.image = [UIImage imageNamed:isOutgoing ? @"color_A" : @"color_D.png"]; + cell.contactImage.image = [UIImage imageWithData:imageData]; + cell.nameDate.text = display; + cell.msgText.text = msgText; + cell.isOutgoing = isOutgoing; + CGSize size = [cell ViewSizeForMessage:msgText withWidth:self.view.bounds.size.width - 10]; + cell.width = size.width; + cell.height = size.height; + cell.nameDate.textColor = [UIColor colorWithPatternImage:cell.background.image]; + cell.msgText.textColor = [UIColor darkGrayColor]; + cell.imdm.hidden = cell.imdmImage.hidden = !isOutgoing; + + if ([imdm isEqualToString:@"LinphoneChatMessageStateDelivered"] || [imdm isEqualToString:@"LinphoneChatMessageStateDeliveredToUser"]) { + cell.imdm.text = NSLocalizedString(@"Delivered", nil); + cell.imdm.textColor = [UIColor grayColor]; + cell.imdmImage.image = [UIImage imageNamed:@"chat_delivered.png"]; + } else if ([imdm isEqualToString:@"LinphoneChatMessageStateDisplayed"]) { + cell.imdm.text = NSLocalizedString(@"Read", nil); + cell.imdm.textColor = [UIColor colorWithRed:(24 / 255.0) green:(167 / 255.0) blue:(175 / 255.0) alpha:1.0]; + cell.imdmImage.image = [UIImage imageNamed:@"chat_read.png"]; + } else if ([imdm isEqualToString:@"LinphoneChatMessageStateNotDelivered"] || [imdm isEqualToString:@"LinphoneChatMessageStateFileTransferError"]) { + cell.imdm.text = NSLocalizedString(@"Error", nil); + cell.imdm.textColor = [UIColor redColor]; + cell.imdmImage.image = [UIImage imageNamed:@"chat_error.png"]; + } else + cell.imdm.hidden = YES; + + return cell; +} + +#pragma mark - UITableViewDelegate Functions + +- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { + NotificationTableViewCell *cell = [[NotificationTableViewCell alloc] init]; + cell.msgText = [[UITextView alloc] init]; + cell.msgText.text = ((NSString *)[msgs[indexPath.row] objectForKey:@"msg"]); + cell.msgText.font = [UIFont systemFontOfSize:17]; + return [cell ViewSizeForMessage:cell.msgText.text withWidth:self.view.bounds.size.width - 10].height + 5; +} + +@end