From bcf19d674292dceb1d6449df7d39b5f2a496954b Mon Sep 17 00:00:00 2001 From: Christophe Deschamps Date: Wed, 21 Jul 2021 10:11:43 +0200 Subject: [PATCH 01/12] SDK Update 5.1.0-alpha.11+950fc62 --- Podfile | 2 +- linphone.xcodeproj/project.pbxproj | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Podfile b/Podfile index 8dfe253ea..d5ec02b57 100644 --- a/Podfile +++ b/Podfile @@ -5,7 +5,7 @@ source "https://github.com/CocoaPods/Specs.git" def all_pods if ENV['PODFILE_PATH'].nil? - pod 'linphone-sdk', '~> 5.0.0-alpha' + pod 'linphone-sdk', '~> 5.1.0-alpha.11+950fc62' else pod 'linphone-sdk', :path => ENV['PODFILE_PATH'] # local sdk end diff --git a/linphone.xcodeproj/project.pbxproj b/linphone.xcodeproj/project.pbxproj index 9d049a72a..c1a97bd18 100644 --- a/linphone.xcodeproj/project.pbxproj +++ b/linphone.xcodeproj/project.pbxproj @@ -109,7 +109,7 @@ 61AE364F20C00B370089D9D3 /* ShareViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 61AE364E20C00B370089D9D3 /* ShareViewController.m */; }; 61AE365220C00B370089D9D3 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 61AE365020C00B370089D9D3 /* MainInterface.storyboard */; }; 61AE365620C00B370089D9D3 /* linphoneExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 61AE364B20C00B370089D9D3 /* linphoneExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - 61AEBEA321906AFC00F35E7F /* (null) in Frameworks */ = {isa = PBXBuildFile; }; + 61AEBEA321906AFC00F35E7F /* BuildFile in Frameworks */ = {isa = PBXBuildFile; }; 61AEBEBD2191990A00F35E7F /* DevicesListView.m in Sources */ = {isa = PBXBuildFile; fileRef = 61AEBEBC2191990A00F35E7F /* DevicesListView.m */; }; 61AEBEBF2191991F00F35E7F /* DevicesListView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 61AEBEBE2191991F00F35E7F /* DevicesListView.xib */; }; 61AEBEC62191E47500F35E7F /* chevron_list_close.png in Resources */ = {isa = PBXBuildFile; fileRef = 61AEBEC52191E47500F35E7F /* chevron_list_close.png */; }; @@ -622,7 +622,7 @@ 63E27A321C4FECD000D332AE /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 63E27A311C4FECD000D332AE /* LaunchScreen.xib */; }; 63E27A521C50EDB000D332AE /* hold.mkv in Resources */ = {isa = PBXBuildFile; fileRef = 63E27A511C50EB2700D332AE /* hold.mkv */; }; 63E59A3F1ADE70D900646FB3 /* InAppProductsManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 63E59A3E1ADE70D900646FB3 /* InAppProductsManager.m */; }; - 63E802DB1C625AEF000D5509 /* (null) in Resources */ = {isa = PBXBuildFile; }; + 63E802DB1C625AEF000D5509 /* BuildFile in Resources */ = {isa = PBXBuildFile; }; 63EC8D391D7438660066547B /* AssistantLinkView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 63EC8D3B1D7438660066547B /* AssistantLinkView.xib */; }; 63F1DF441BCE618E00EDED90 /* UIAddressTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = 63F1DF431BCE618E00EDED90 /* UIAddressTextField.m */; }; 63F1DF4B1BCE983200EDED90 /* CallConferenceTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 63F1DF4A1BCE983200EDED90 /* CallConferenceTableView.m */; }; @@ -1951,7 +1951,7 @@ files = ( 61DD7E1F2372E88F001BDD01 /* CoreLocation.framework in Frameworks */, 6180D6FE21EE41A800AD9CB6 /* QuickLook.framework in Frameworks */, - 61AEBEA321906AFC00F35E7F /* (null) in Frameworks */, + 61AEBEA321906AFC00F35E7F /* BuildFile in Frameworks */, D37DC7181594AF3400B2A5EB /* MessageUI.framework in Frameworks */, 61F1997520C6B1D5006B069A /* AVKit.framework in Frameworks */, 249660951FD6A35F001D55AA /* Photos.framework in Frameworks */, @@ -2285,7 +2285,7 @@ path = LinphoneUI; sourceTree = ""; }; - 29B97314FDCFA39411CA2CEA /* CustomTemplate */ = { + 29B97314FDCFA39411CA2CEA = { isa = PBXGroup; children = ( 8C23BCB71D82AAC3005F19BB /* linphone.entitlements */, @@ -3357,7 +3357,7 @@ fr, hu, ); - mainGroup = 29B97314FDCFA39411CA2CEA /* CustomTemplate */; + mainGroup = 29B97314FDCFA39411CA2CEA; productRefGroup = 19C28FACFE9D520D11CA2CBB /* Products */; projectDirPath = ""; projectRoot = ""; @@ -3418,7 +3418,7 @@ 633FEED41D3CD55A0014B822 /* numpad_7_default@2x.png in Resources */, 633FEEE01D3CD55A0014B822 /* numpad_8_over~ipad@2x.png in Resources */, 633FEDDC1D3CD5590014B822 /* call_start_body_disabled~ipad.png in Resources */, - 63E802DB1C625AEF000D5509 /* (null) in Resources */, + 63E802DB1C625AEF000D5509 /* BuildFile in Resources */, 633FEE2E1D3CD5590014B822 /* color_F.png in Resources */, 633FEDC51D3CD5590014B822 /* call_hangup_disabled@2x.png in Resources */, 633FEEDF1D3CD55A0014B822 /* numpad_8_over~ipad.png in Resources */, @@ -4949,7 +4949,7 @@ "-DENABLE_QRCODE=TRUE", "-DENABLE_SMS_INVITE=TRUE", "$(inherited)", - "-DLINPHONE_SDK_VERSION=\\\"5.0.0-alpha.109+40dd0cf\\\"", + "-DLINPHONE_SDK_VERSION=\\\"5.1.0-alpha.11+950fc62\\\"", ); OTHER_SWIFT_FLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = org.linphone.phone; @@ -5072,7 +5072,7 @@ "-DENABLE_QRCODE=TRUE", "-DENABLE_SMS_INVITE=TRUE", "$(inherited)", - "-DLINPHONE_SDK_VERSION=\\\"5.0.0-alpha.109+40dd0cf\\\"", + "-DLINPHONE_SDK_VERSION=\\\"5.1.0-alpha.11+950fc62\\\"", ); OTHER_SWIFT_FLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = org.linphone.phone; @@ -5194,7 +5194,7 @@ "-DENABLE_QRCODE=TRUE", "-DENABLE_SMS_INVITE=TRUE", "$(inherited)", - "-DLINPHONE_SDK_VERSION=\\\"5.0.0-alpha.109+40dd0cf\\\"", + "-DLINPHONE_SDK_VERSION=\\\"5.1.0-alpha.11+950fc62\\\"", ); OTHER_SWIFT_FLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = org.linphone.phone; @@ -5315,7 +5315,7 @@ "-DENABLE_QRCODE=TRUE", "-DENABLE_SMS_INVITE=TRUE", "$(inherited)", - "-DLINPHONE_SDK_VERSION=\\\"5.0.0-alpha.109+40dd0cf\\\"", + "-DLINPHONE_SDK_VERSION=\\\"5.1.0-alpha.11+950fc62\\\"", ); OTHER_SWIFT_FLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = org.linphone.phone; From b66c3ad9164078721afcf5a01820e4f1bc9e1e6d Mon Sep 17 00:00:00 2001 From: Christophe Deschamps Date: Fri, 23 Jul 2021 12:06:05 +0200 Subject: [PATCH 02/12] Audio chat messages --- Classes/AppManager.swift | 12 + Classes/Base.lproj/ChatConversationView.xib | 103 ++++- Classes/CallManager.swift | 19 + Classes/ChatConversationTableView.h | 6 +- Classes/ChatConversationView.h | 26 ++ Classes/ChatConversationView.m | 402 ++++++++++++++++-- Classes/FirstLoginView.m | 2 +- Classes/LinphoneManager.h | 2 + Classes/LinphoneManager.m | 4 +- .../Base.lproj/UIChatBubblePhotoCell.xib | 53 ++- Classes/LinphoneUI/UIChatBubblePhotoCell.h | 10 + Classes/LinphoneUI/UIChatBubblePhotoCell.m | 133 +++++- Classes/LinphoneUI/UIChatBubbleTextCell.h | 4 + Classes/LinphoneUI/UIChatBubbleTextCell.m | 125 +++++- Classes/PhoneMainView.m | 2 +- Classes/Utils/FileTransferDelegate.h | 4 +- Classes/Utils/FileTransferDelegate.m | 15 +- Classes/Utils/Utils.h | 2 +- Classes/Utils/Utils.m | 6 +- Resources/en.lproj/Localizable.strings | Bin 52514 -> 52874 bytes Resources/fr.lproj/Localizable.strings | Bin 57096 -> 57484 bytes Resources/images/vr_off.png | Bin 0 -> 12846 bytes Resources/images/vr_on.png | Bin 0 -> 15718 bytes Resources/images/vr_pause.png | Bin 0 -> 6392 bytes Resources/images/vr_play.png | Bin 0 -> 7003 bytes Resources/images/vr_stop.png | Bin 0 -> 5528 bytes Resources/images/vr_wave.png | Bin 0 -> 15224 bytes linphone.xcodeproj/project.pbxproj | 36 +- 28 files changed, 868 insertions(+), 98 deletions(-) create mode 100644 Resources/images/vr_off.png create mode 100644 Resources/images/vr_on.png create mode 100644 Resources/images/vr_pause.png create mode 100644 Resources/images/vr_play.png create mode 100644 Resources/images/vr_stop.png create mode 100644 Resources/images/vr_wave.png diff --git a/Classes/AppManager.swift b/Classes/AppManager.swift index d1987e746..9a86a02e9 100644 --- a/Classes/AppManager.swift +++ b/Classes/AppManager.swift @@ -64,4 +64,16 @@ enum NetworkType: Int { //The recording prefix is used to identify recordings in the cache directory. //We will use name_dayName-day-monthName-year to separate recordings by days, then hour-minutes-seconds to order them in each day. } + + @objc static func removeFile(file: String) { + let fileManager = FileManager.default + do { + try fileManager.removeItem(atPath: file) + Log.directLog(BCTBX_LOG_MESSAGE, text: "File :\(file) removed") + + } catch { + print("Could not remove file : \(file) \(error)") + } + } + } diff --git a/Classes/Base.lproj/ChatConversationView.xib b/Classes/Base.lproj/ChatConversationView.xib index e321384a1..62255dabe 100644 --- a/Classes/Base.lproj/ChatConversationView.xib +++ b/Classes/Base.lproj/ChatConversationView.xib @@ -34,9 +34,18 @@ + + + + + + + + + @@ -209,7 +218,7 @@ - + @@ -221,7 +230,7 @@ + @@ -551,8 +625,21 @@ + - + @@ -637,10 +724,14 @@ - + + + + + diff --git a/Classes/CallManager.swift b/Classes/CallManager.swift index 68c0869eb..a9546176a 100644 --- a/Classes/CallManager.swift +++ b/Classes/CallManager.swift @@ -639,6 +639,25 @@ import AVFoundation AnyHashable("message"): message ]) } + + @objc func activateAudioSession() { + lc?.activateAudioSession(actived: true) + } + + @objc func getSpeakerSoundCard() -> String? { + var speakerCard: String? = nil + var earpieceCard: String? = nil + lc?.audioDevices.forEach { device in + if (device.hasCapability(capability: .CapabilityPlay)) { + if (device.type == .Speaker) { + speakerCard = device.id + } else if (device.type == .Earpiece) { + earpieceCard = device.id + } + } + } + return speakerCard != nil ? speakerCard : earpieceCard + } } diff --git a/Classes/ChatConversationTableView.h b/Classes/ChatConversationTableView.h index 3fb5e83be..1a3f783d7 100644 --- a/Classes/ChatConversationTableView.h +++ b/Classes/ChatConversationTableView.h @@ -39,10 +39,10 @@ @protocol ChatConversationDelegate -- (BOOL)resendMultiFiles:(FileContext *)newFileContext message:(NSString *)message; -- (BOOL)resendFile:(NSData *)data withName:(NSString *)name type:(NSString *)type key:(NSString *)key message:(NSString *)message; +- (BOOL)resendMultiFiles:(FileContext *)newFileContext message:(NSString *)message voiceContent:(LinphoneContent *)voiceContent; +- (BOOL)resendFile:(NSData *)data withName:(NSString *)name type:(NSString *)type key:(NSString *)key message:(NSString *)message voiceContent:(LinphoneContent *)voiceContent; - (BOOL)startFileUpload:(NSData *)data withName:(NSString *)name; -- (void)resendChat:(NSString *)message withExternalUrl:(NSString *)url; +- (void)resendChat:(NSString *)message withExternalUrl:(NSString *)url voiceContent:(LinphoneContent *)voiceContent; - (void)tableViewIsScrolling; @end diff --git a/Classes/ChatConversationView.h b/Classes/ChatConversationView.h index 06bb89c17..1a8d77309 100644 --- a/Classes/ChatConversationView.h +++ b/Classes/ChatConversationView.h @@ -92,6 +92,28 @@ @property (weak, nonatomic) IBOutlet UIInterfaceStyleButton *toggleMenuButton; @property (weak, nonatomic) IBOutlet UIImageView *ephemeralndicator; + +// Voice recording +@property (strong, nonatomic) IBOutlet UIView *vrView; +@property (weak, nonatomic) IBOutlet UIView *vrInnerView; +@property (weak, nonatomic) IBOutlet UIButton *vrDeleteButton; +@property (weak, nonatomic) IBOutlet UIButton *vrPlayButton; +@property (weak, nonatomic) IBOutlet UIImageView *vrWave; +@property (weak, nonatomic) IBOutlet UIView *vrWaveMask; +@property (weak, nonatomic) IBOutlet UIView *vrWaveMaskPlayer; +@property (weak, nonatomic) IBOutlet UILabel *vrDurationLabel; +@property NSTimer *vrRecordTimer; +@property NSTimer *vrPlayerTimer; +@property (weak, nonatomic) IBOutlet UIButton *toggleRecord; +@property BOOL isVoiceRecording; +@property BOOL isPendingVoiceRecord; +@property BOOL isPlayingVoiceRecording; +@property LinphoneRecorder *voiceRecorder; +@property LinphonePlayer *sharedVoicePlayer; +@property BOOL showVoiceRecorderView; +@property BOOL preservePendingRecording; + + + (void)markAsRead:(LinphoneChatRoom *)chatRoom; + (void)autoDownload:(LinphoneChatMessage *)message; +(NSString *)getKeyFromFileType:(NSString *)fileType fileName:(NSString *)name; @@ -123,4 +145,8 @@ - (NSURL *)getICloudFileUrl:(NSString *)name; - (void)removeCallBacks; +-(void) startSharedPlayer:(const char *)path; +-(void) stopSharedPlayer; +-(BOOL) sharedPlayedIsPlaying:(const char *)path; + @end diff --git a/Classes/ChatConversationView.m b/Classes/ChatConversationView.m index bfdca00f3..05ec3b14c 100644 --- a/Classes/ChatConversationView.m +++ b/Classes/ChatConversationView.m @@ -120,6 +120,7 @@ [NSNumber numberWithFloat:0.5], NSLocalizedString(@"Average", nil), [NSNumber numberWithFloat:0.0], NSLocalizedString(@"Minimum", nil), nil]; composingVisible = false; + [self initSharedPlayer]; } return self; } @@ -187,6 +188,12 @@ static UICompositeViewDescription *compositeDescription = nil; [_imagesCollectionView registerClass:[UIImageViewDeletable class] forCellWithReuseIdentifier:NSStringFromClass([UIImageViewDeletable class])]; [_imagesCollectionView setDataSource:self]; [_toggleSelectionButton setImage:[UIImage imageNamed:@"select_all_default.png"] forState:UIControlStateSelected]; + + _vrInnerView.layer.cornerRadius = 5.0f; + _vrInnerView.layer.masksToBounds = YES; + _vrWaveMaskPlayer.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"color_L"]]; // rgba(1,88,7,0.2); + _showVoiceRecorderView = false; + } - (void)refreshData { @@ -202,6 +209,10 @@ static UICompositeViewDescription *compositeDescription = nil; - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; + [NSNotificationCenter.defaultCenter addObserver:self + selector:@selector(applicationWillEnterBackground) + name:UIApplicationDidEnterBackgroundNotification + object:nil]; [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification @@ -222,6 +233,16 @@ static UICompositeViewDescription *compositeDescription = nil; selector:@selector(onLinphoneCoreReady:) name:kLinphoneGlobalStateUpdate object:nil]; + + [NSNotificationCenter.defaultCenter addObserver:self + selector:@selector(endVoicePlayingIfDoingSO:) + name:kLinphoneVoiceMessagePlayerLostFocus + object:nil]; + + [NSNotificationCenter.defaultCenter addObserver:self + selector:@selector(endVoicePlayingIfDoingSO:) + name:kLinphoneVoiceMessagePlayerEOF + object:nil]; if ([_fileContext count] > 0) { [UIView animateWithDuration:0 delay:0 @@ -236,6 +257,7 @@ static UICompositeViewDescription *compositeDescription = nil; CGRect tableViewFrame = [_tableController.tableView frame]; tableViewFrame.size.height -= 100; [_tableController.tableView setFrame:tableViewFrame]; + [self updateFramesInclRecordingView]; } completion:nil]; } @@ -245,10 +267,20 @@ static UICompositeViewDescription *compositeDescription = nil; CGRect popupFrame = _popupMenu.frame; popupFrame.size.height = 44 * [_popupMenu numberOfRowsInSection:0]; _popupMenu.frame = popupFrame; + + // Voice recording + _vrView.hidden = true; + _preservePendingRecording = false; } - (void)viewWillDisappear:(BOOL)animated { + + if (!_preservePendingRecording) + [self cancelVoiceRecording]; + else if (_isVoiceRecording) + [self stopVoiceRecording]; + [super viewWillDisappear:animated]; [self removeCallBacks]; @@ -256,7 +288,9 @@ static UICompositeViewDescription *compositeDescription = nil; [_messageField resignFirstResponder]; [self setComposingVisible:false withDelay:0]; // will hide the "user is composing.." message - + + [self stopAllPlays]; + [NSNotificationCenter.defaultCenter removeObserver:self]; PhoneMainView.instance.currentRoom = NULL; } @@ -287,10 +321,20 @@ static UICompositeViewDescription *compositeDescription = nil; _backButton.hidden = _tableController.isEditing; [_tableController scrollToBottom:true]; [self refreshImageDrawer]; + [self stopAllPlays]; + } #pragma mark - +- (void)applicationWillEnterBackground{ + if (!_preservePendingRecording) + [self cancelVoiceRecording]; + else if (_isVoiceRecording) + [self stopVoiceRecording]; +} + + - (void)configureForRoom:(BOOL)editing { if (!_chatRoom) { _chatView.hidden = YES; @@ -456,16 +500,24 @@ static UICompositeViewDescription *compositeDescription = nil; } } -- (BOOL)sendMessage:(NSString *)message withExterlBodyUrl:(NSURL *)externalUrl { +- (BOOL)sendMessage:(NSString *)message withExterlBodyUrl:(NSURL *)externalUrl andVoiceContent:(LinphoneContent *)voiceContent { if (_chatRoom == NULL) { LOGW(@"Cannot send message: No chatroom"); return FALSE; } - LinphoneChatMessage *msg = linphone_chat_room_create_message(_chatRoom, [message UTF8String]); + LinphoneChatMessage *msg = linphone_chat_room_create_empty_message(_chatRoom); + if (message && message.length > 0) + linphone_chat_message_add_utf8_text_content(msg, message.UTF8String); + if (externalUrl) { linphone_chat_message_set_external_body_url(msg, [[externalUrl absoluteString] UTF8String]); } + + // Voice recording + + if (voiceContent) + linphone_chat_message_add_content(msg, voiceContent); // we must ref & unref message because in case of error, it will be destroy otherwise linphone_chat_message_send(msg); @@ -512,10 +564,10 @@ static UICompositeViewDescription *compositeDescription = nil; [sheet addButtonWithTitle:NSLocalizedString(@"Send to this friend", nil) block:^() { if (![[self.messageField text] isEqualToString:@""]) { - [self sendMessageInMessageField]; + [self sendMessageInMessageFieldWithVoiceContent:nil]; } if (url) - [self sendMessage:url withExterlBodyUrl:nil]; + [self sendMessage:url withExterlBodyUrl:nil andVoiceContent:nil]; else [self startFileUpload:data withName:fileName]; }]; @@ -600,8 +652,8 @@ static UICompositeViewDescription *compositeDescription = nil; _addressLabel.frame = frame; } -- (void)sendMessageInMessageField { - if ([self sendMessage:[_messageField text] withExterlBodyUrl:nil]) { +- (void)sendMessageInMessageFieldWithVoiceContent:(LinphoneContent *)voiceContent { + if ([self sendMessage:[_messageField text] withExterlBodyUrl:nil andVoiceContent:voiceContent]) { scrollOnGrowingEnabled = FALSE; [_messageField setText:@""]; scrollOnGrowingEnabled = TRUE; @@ -657,6 +709,7 @@ static UICompositeViewDescription *compositeDescription = nil; CGRect tableRect = [_tableController.view frame]; tableRect.size.height -= diff; [_tableController.view setFrame:tableRect]; + [self updateFramesInclRecordingView]; // if we're showing the compose message, update it position if (![_composeLabel isHidden]) { @@ -681,28 +734,40 @@ static UICompositeViewDescription *compositeDescription = nil; } - (IBAction)onSendClick:(id)event { + LinphoneContent *voiceContent = nil; + if (_isPendingVoiceRecord && _voiceRecorder && linphone_recorder_get_file(_voiceRecorder)) { + voiceContent = linphone_recorder_create_content(_voiceRecorder); + _isPendingVoiceRecord = false; + [self cancelVoiceRecording]; + [self stopVoiceRecordPlayer]; + } + + if (!linphone_core_is_network_reachable(LC)) { + [PhoneMainView.instance presentViewController:[LinphoneUtils networkErrorView:@"send a message"] animated:YES completion:nil]; + //return; + } if ([_fileContext count] > 0) { if (linphone_chat_room_get_capabilities(_chatRoom) & LinphoneChatRoomCapabilitiesConference) { - [self startMultiFilesUpload]; + [self startMultiFilesUploadWithVoiceContent:voiceContent]; } else { int i = 0; for (i = 0; i < [_fileContext count]-1; ++i) { - [self startUploadData:[_fileContext.datasArray objectAtIndex:i] withType:[_fileContext.typesArray objectAtIndex:i] withName:[_fileContext.namesArray objectAtIndex:i] andMessage:NULL]; + [self startUploadData:[_fileContext.datasArray objectAtIndex:i] withType:[_fileContext.typesArray objectAtIndex:i] withName:[_fileContext.namesArray objectAtIndex:i] andMessage:NULL voiceContent:voiceContent]; } if (isOneToOne) { - [self startUploadData:[_fileContext.datasArray objectAtIndex:i] withType:[_fileContext.typesArray objectAtIndex:i] withName:[_fileContext.namesArray objectAtIndex:i] andMessage:NULL]; + [self startUploadData:[_fileContext.datasArray objectAtIndex:i] withType:[_fileContext.typesArray objectAtIndex:i] withName:[_fileContext.namesArray objectAtIndex:i] andMessage:NULL voiceContent:voiceContent]; if (![[self.messageField text] isEqualToString:@""]) { - [self sendMessage:[_messageField text] withExterlBodyUrl:nil]; + [self sendMessage:[_messageField text] withExterlBodyUrl:nil andVoiceContent:voiceContent]; } } else { - [self startUploadData:[_fileContext.datasArray objectAtIndex:i] withType:[_fileContext.typesArray objectAtIndex:i] withName:[_fileContext.namesArray objectAtIndex:i] andMessage:[self.messageField text]]; + [self startUploadData:[_fileContext.datasArray objectAtIndex:i] withType:[_fileContext.typesArray objectAtIndex:i] withName:[_fileContext.namesArray objectAtIndex:i] andMessage:[self.messageField text] voiceContent:voiceContent]; } } [self clearMessageView]; return; } - [self sendMessageInMessageField]; + [self sendMessageInMessageFieldWithVoiceContent:voiceContent]; } - (IBAction)onListTap:(id)sender { @@ -759,14 +824,11 @@ static UICompositeViewDescription *compositeDescription = nil; } - (IBAction)onMessageChange:(id)sender { - if ([[_messageField text] length] > 0) { - [_sendButton setEnabled:TRUE]; - } else { - [_sendButton setEnabled:FALSE]; - } + [self setSendButtonState]; } - (IBAction)onPictureClick:(id)event { + _preservePendingRecording = true; [_messageField resignFirstResponder]; [ImagePickerView SelectImageFromDevice:self atPosition:_pictureButton inView:self.view withDocumentMenuDelegate:self]; @@ -800,15 +862,15 @@ static UICompositeViewDescription *compositeDescription = nil; #pragma mark ChatRoomDelegate -- (BOOL)startMultiFilesUpload { +- (BOOL)startMultiFilesUploadWithVoiceContent:(LinphoneContent *)voiceContent { FileTransferDelegate *fileTransfer = [[FileTransferDelegate alloc] init]; [fileTransfer setText:[self.messageField text]]; - [fileTransfer uploadFileContent:_fileContext forChatRoom:_chatRoom]; + [fileTransfer uploadFileContent:_fileContext forChatRoom:_chatRoom andVoiceContent:voiceContent]; [_tableController scrollToBottom:true]; return TRUE; } -- (BOOL)startUploadData:(NSData *)data withType:(NSString*)type withName:(NSString *)name andMessage:(NSString *)message { +- (BOOL)startUploadData:(NSData *)data withType:(NSString*)type withName:(NSString *)name andMessage:(NSString *)message voiceContent:(LinphoneContent *)voiceContent { FileTransferDelegate *fileTransfer = [[FileTransferDelegate alloc] init]; if (message) [fileTransfer setText:message]; @@ -818,7 +880,7 @@ static UICompositeViewDescription *compositeDescription = nil; } else if ([type isEqualToString:@"image"]) { key = @"localimage"; } - [fileTransfer uploadData:data forChatRoom:_chatRoom type:type subtype:type name:name key:key]; + [fileTransfer uploadData:data forChatRoom:_chatRoom type:type subtype:type name:name key:key voiceContent:voiceContent]; [_tableController scrollToBottom:true]; return TRUE; } @@ -830,26 +892,26 @@ static UICompositeViewDescription *compositeDescription = nil; return TRUE; } -- (BOOL)resendMultiFiles:(FileContext *)newFileContext message:(NSString *)message { +- (BOOL)resendMultiFiles:(FileContext *)newFileContext message:(NSString *)message voiceContent:(LinphoneContent *)voiceContent { FileTransferDelegate *fileTransfer = [[FileTransferDelegate alloc] init]; if (message) [fileTransfer setText:message]; - [fileTransfer uploadFileContent:newFileContext forChatRoom:_chatRoom]; + [fileTransfer uploadFileContent:newFileContext forChatRoom:_chatRoom andVoiceContent:voiceContent]; [_tableController scrollToBottom:true]; return TRUE; } -- (BOOL)resendFile: (NSData *)data withName:(NSString *)name type:(NSString *)type key:(NSString *)key message:(NSString *)message { +- (BOOL)resendFile: (NSData *)data withName:(NSString *)name type:(NSString *)type key:(NSString *)key message:(NSString *)message voiceContent:(LinphoneContent *)voiceContent{ FileTransferDelegate *fileTransfer = [[FileTransferDelegate alloc] init]; if (message) [fileTransfer setText:message]; - [fileTransfer uploadData:data forChatRoom:_chatRoom type:type subtype:type name:name key:key]; + [fileTransfer uploadData:data forChatRoom:_chatRoom type:type subtype:type name:name key:key voiceContent:voiceContent]; [_tableController scrollToBottom:true]; return TRUE; } -- (void)resendChat:(NSString *)message withExternalUrl:(NSString *)url { - [self sendMessage:message withExterlBodyUrl:[NSURL URLWithString:url]]; +- (void)resendChat:(NSString *)message withExternalUrl:(NSString *)url voiceContent:(LinphoneContent *)voiceContent { + [self sendMessage:message withExterlBodyUrl:[NSURL URLWithString:url] andVoiceContent:voiceContent]; } #pragma mark ImagePickerDelegate @@ -1077,6 +1139,7 @@ static UICompositeViewDescription *compositeDescription = nil; [_messageView frame].origin.y - tableFrame.origin.y - composeIndicatorCompensation; [_tableController.view setFrame:tableFrame]; + // Scroll to bottom NSInteger lastSection = [_tableController.tableView numberOfSections] - 1; if (lastSection >= 0) { @@ -1102,9 +1165,12 @@ static UICompositeViewDescription *compositeDescription = nil; tableViewFrame.size.height = imagesFrame.origin.y - tableViewFrame.origin.y; [_tableController.tableView setFrame:tableViewFrame]; } + if (_showVoiceRecorderView) + _vrView.hidden = true; + [self updateFramesInclRecordingView]; + } completion:^(BOOL finished){ - }]; } @@ -1158,6 +1224,7 @@ static UICompositeViewDescription *compositeDescription = nil; tableFrame.size.height = [_messageView frame].origin.y - tableFrame.origin.y - composeIndicatorCompensation; [_tableController.view setFrame:tableFrame]; + } if ([_fileContext count] > 0){ @@ -1170,6 +1237,7 @@ static UICompositeViewDescription *compositeDescription = nil; CGRect tableViewFrame = [_tableController.tableView frame]; tableViewFrame.size.height = imagesFrame.origin.y - tableViewFrame.origin.y; [_tableController.tableView setFrame:tableViewFrame]; + } // Scroll @@ -1183,10 +1251,13 @@ static UICompositeViewDescription *compositeDescription = nil; animated:FALSE]; } } + if (_showVoiceRecorderView) + _vrView.hidden = true; + [self updateFramesInclRecordingView]; + } completion:^(BOOL finished){ - }]; } @@ -1395,7 +1466,7 @@ void on_chat_room_conference_alert(LinphoneChatRoom *cr, const LinphoneEventLog [imgView setUuid:[_fileContext.uuidsArray objectAtIndex:[indexPath item]]]; [imgView setDeleteDelegate:self]; [imgView setFrame:imgFrame]; - [_sendButton setEnabled:TRUE]; + [self setSendButtonState]; return imgView; } @@ -1416,10 +1487,10 @@ void on_chat_room_conference_alert(LinphoneChatRoom *cr, const LinphoneEventLog CGRect tableViewFrame = [_tableController.tableView frame]; tableViewFrame.size.height = imagesFrame.origin.y - tableViewFrame.origin.y; [_tableController.tableView setFrame:tableViewFrame]; + [self updateFramesInclRecordingView]; } completion:nil]; - if ([_messageField.text isEqualToString:@""]) - [_sendButton setEnabled:FALSE]; + [self setSendButtonState]; } else { // resizing imagesView CGRect imagesFrame = [_imagesView frame]; @@ -1430,6 +1501,7 @@ void on_chat_room_conference_alert(LinphoneChatRoom *cr, const LinphoneEventLog CGRect tableViewFrame = [_tableController.tableView frame]; tableViewFrame.size.height = imagesFrame.origin.y - tableViewFrame.origin.y; [_tableController.tableView setFrame:tableViewFrame]; + [self updateFramesInclRecordingView]; [_imagesCollectionView reloadData]; } } @@ -1592,4 +1664,268 @@ void on_chat_room_conference_alert(LinphoneChatRoom *cr, const LinphoneEventLog } +// Voice redcording + + +- (IBAction)onVrDelete:(id)sender { + [self cancelVoiceRecording]; + [self stopVoiceRecordPlayer]; +} + +- (IBAction)onvrPlayPauseStop:(id)sender { + if (_isVoiceRecording) { + [self stopVoiceRecording]; + } else { + if (_isPlayingVoiceRecording) + [self stopVoiceRecordPlayer]; + else + [self playRecordedMessage]; + } +} + +- (IBAction)onVrStart:(id)sender { + if (_isVoiceRecording) { + [self stopVoiceRecording]; + } else { + [self startVoiceRecording]; + } +} + +-(void) createVoiceRecorder { + LinphoneRecorderParams *p = linphone_core_create_recorder_params(LC); + linphone_recorder_params_set_file_format(p, LinphoneRecorderFileFormatWav); + _voiceRecorder = linphone_core_create_recorder(LC, p); + [CallManager.instance activateAudioSession]; +} + +-(void) cancelVoiceRecording { + _showVoiceRecorderView = false; + _toggleRecord.selected = false; + [self updateFramesInclRecordingView]; + _isPendingVoiceRecord = false; + _isVoiceRecording = false; + if (_voiceRecorder && linphone_recorder_get_state(_voiceRecorder) != LinphoneRecorderClosed) { + linphone_recorder_close(_voiceRecorder); + const char *recordingFile = linphone_recorder_get_file(_voiceRecorder); + if (recordingFile) { + [AppManager removeFileWithFile:[NSString stringWithUTF8String:recordingFile]]; + } + } + [self setSendButtonState]; +} + +-(void) stopVoiceRecording { + if (_voiceRecorder && linphone_recorder_get_state(_voiceRecorder) == LinphoneRecorderRunning) { + LOGI(@"[Chat Message Sending] Pausing / closing voice recorder"); + linphone_recorder_pause(_voiceRecorder); + linphone_recorder_close(_voiceRecorder); + _vrDurationLabel.text = [self formattedDuration:linphone_recorder_get_duration(_voiceRecorder)]; + } + _isVoiceRecording = false; + if ([LinphoneManager.instance lpConfigBoolForKey:@"voice_recording_send_right_away" withDefault:false]) { + [self onSendClick:nil]; + } + [_vrPlayButton setImage:[UIImage imageNamed:@"vr_play"] forState:UIControlStateNormal]; + _toggleRecord.selected = false; + _vrWaveMask.frame = CGRectZero; + [_vrRecordTimer invalidate]; + _isPendingVoiceRecord = linphone_recorder_get_duration(_voiceRecorder) > 0; + [self setSendButtonState]; + +} + +-(void) startVoiceRecording { + + if (!_voiceRecorder) + [self createVoiceRecorder]; + + _toggleRecord.selected = true; + [_vrPlayButton setImage:[UIImage imageNamed:@"vr_stop"] forState:UIControlStateNormal]; + + + _showVoiceRecorderView = true; + [self updateFramesInclRecordingView]; + _isVoiceRecording = true; + _vrWaveMaskPlayer.frame = CGRectZero; + + switch (linphone_recorder_get_state(_voiceRecorder)) { + case LinphoneRecorderClosed: { + NSString *filename = [NSString stringWithFormat:@"%@/voice-recording-%@.wav",[LinphoneManager imagesDirectory], [NSUUID UUID].UUIDString]; + linphone_recorder_open(_voiceRecorder, filename.UTF8String); + linphone_recorder_start(_voiceRecorder); + LOGW(@"[Chat Message Sending] Recorder is closed opening it with %@",filename); + break; + }; + case LinphoneRecorderRunning: { + LOGW(@"[Chat Message Sending] Recorder is already recording"); + break; + } + case LinphoneRecorderPaused: { + LOGW(@"[Chat Message Sending] Recorder isn't closed, resuming recording"); + linphone_recorder_start(_voiceRecorder); + } + } + _vrWaveMask.frame = _vrWave.frame; + _vrDurationLabel.text = [self formattedDuration:linphone_recorder_get_duration(_voiceRecorder)]; + _vrRecordTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 + target:self + selector:@selector(voiceRecordTimerUpdate) + userInfo:nil + repeats:YES]; + + +} + +-(void) voiceRecordTimerUpdate { + int recorderDuration = linphone_recorder_get_duration(_voiceRecorder); + if (recorderDuration > [LinphoneManager.instance lpConfigIntForKey:@"voice_recording_max_duration" withDefault:60000]) { + LOGW(@"[Chat Message Sending] Max duration for voice recording exceeded, stopping. (max = %d)",[LinphoneManager.instance lpConfigIntForKey:@"voice_recording_max_duration" withDefault:60000]); + [self stopVoiceRecording]; + } else { + _vrDurationLabel.text = [self formattedDuration:linphone_recorder_get_duration(_voiceRecorder)]; + CGRect r = _vrWaveMask.frame; + r.origin.x += 30; + r.size.width -= 30; + if (r.origin.x > _vrWave.frame.size.width) { + r = _vrWave.frame; + _vrWaveMask.frame = r; + } else { + [UIView animateWithDuration:1.0 delay:0.0 options:UIViewAnimationOptionCurveLinear animations:^{ + _vrWaveMask.frame = r; + }completion:^(BOOL finished) {}]; + } + } +} + +// Playback Shared Player (new recording & chat bubble) + +- (void) initSharedPlayer { + LOGI(@"[Voice Message] Creating shared player"); + _sharedVoicePlayer = linphone_core_create_local_player(LC, [CallManager.instance getSpeakerSoundCard].UTF8String, nil, nil); + LinphonePlayerCbs *cbs = linphone_factory_create_player_cbs(linphone_factory_get()); + linphone_player_cbs_set_eof_reached(cbs, on_shared_player_eof_reached); + linphone_player_cbs_set_user_data(cbs, (__bridge void*)self); + linphone_player_add_callbacks(_sharedVoicePlayer, cbs); +} + +-(void) startSharedPlayer:(const char *)path { + LOGI(@"[Voice Message] Starting shared player path = %s",path); + if (linphone_player_get_user_data(_sharedVoicePlayer)) { + LOGI(@"[Voice Message] a play was requested (%s), but there is already one going (%s)",path,(const char *)linphone_player_get_user_data(_sharedVoicePlayer) ); + NSDictionary* userInfo = @{@"path": [NSString stringWithUTF8String:linphone_player_get_user_data(_sharedVoicePlayer)]}; + [NSNotificationCenter.defaultCenter postNotificationName:kLinphoneVoiceMessagePlayerLostFocus object:nil userInfo:userInfo]; + } + [CallManager.instance changeRouteToSpeaker]; + linphone_player_set_user_data(_sharedVoicePlayer, (void *)path); + linphone_player_open(_sharedVoicePlayer, path); + linphone_player_start(_sharedVoicePlayer); +} + +-(void) stopSharedPlayer { + LOGI(@"[Voice Message] Stopping shared player path = %s",linphone_player_get_user_data(_sharedVoicePlayer) ? (const char *)linphone_player_get_user_data(_sharedVoicePlayer) : "nil"); + linphone_player_pause(_sharedVoicePlayer); + linphone_player_seek(_sharedVoicePlayer,0); + linphone_player_close(_sharedVoicePlayer); + linphone_player_set_user_data(_sharedVoicePlayer, nil); +} + +-(BOOL) sharedPlayedIsPlaying:(const char *)path { + return path && linphone_player_get_user_data(_sharedVoicePlayer) && !strcmp(path,linphone_player_get_user_data(_sharedVoicePlayer)); +} + +void on_shared_player_eof_reached(LinphonePlayer *p) { + LOGI(@"[Voice Message] End of file reached for player"); + const char * currentPlayedFile = (const char *) linphone_player_get_user_data(p); + if (currentPlayedFile) { + NSDictionary* userInfo = @{@"path": [NSString stringWithUTF8String:currentPlayedFile]}; + [NSNotificationCenter.defaultCenter postNotificationName:kLinphoneVoiceMessagePlayerEOF object:nil userInfo:userInfo]; + } + +// ChatConversationView *view = (__bridge ChatConversationView *)linphone_player_cbs_get_user_data(linphone_player_get_current_callbacks(p)); +// [view stopVoiceRecordPlayer]; +} + +// Playback of new recordings + +-(void) playRecordedMessage { + [_vrPlayButton setImage:[UIImage imageNamed:@"vr_stop"] forState:UIControlStateNormal]; + _vrDurationLabel.text = [self formattedDuration:linphone_player_get_duration(_sharedVoicePlayer)]; + _vrWaveMask.frame = CGRectZero; + CGRect r = CGRectZero; + r.size.height = _vrInnerView.frame.size.height; + _vrWaveMaskPlayer.frame = r; + _vrPlayerTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 + target:self + selector:@selector(voicePlayTimerUpdate) + userInfo:nil + repeats:YES]; + [self startSharedPlayer:linphone_recorder_get_file(_voiceRecorder)]; + [self animPlayerOnce]; + _isPlayingVoiceRecording = true; +} + +-(void) voicePlayTimerUpdate { + _vrDurationLabel.text = [self formattedDuration:linphone_player_get_duration(_sharedVoicePlayer)]; + [self animPlayerOnce]; +} + +-(void) animPlayerOnce { + CGRect r = _vrWaveMaskPlayer.frame; + r.size.width += _vrInnerView.frame.size.width / ((linphone_player_get_duration(_sharedVoicePlayer) / 1000)+1) ; + if (r.size.width > _vrInnerView.frame.size.width) { + r.size.width = _vrInnerView.frame.size.width; + } + [UIView animateWithDuration:1.0 delay:0.0 options:UIViewAnimationOptionCurveLinear animations:^{ + _vrWaveMaskPlayer.frame = r; + }completion:^(BOOL finished) {}]; +} + +-(void) endVoicePlayingIfDoingSO:(NSNotification *)notif { + if (_isPlayingVoiceRecording) + [self stopVoiceRecordPlayer]; +} + +-(void) stopVoiceRecordPlayer { + [self stopSharedPlayer]; + [_vrPlayButton setImage:[UIImage imageNamed:@"vr_play"] forState:UIControlStateNormal]; + _isPlayingVoiceRecording = false; + [_vrPlayerTimer invalidate]; + _vrWaveMaskPlayer.frame = CGRectZero; +} + +-(NSString *)formattedDuration:(long)valueMs { + return [NSString stringWithFormat:@"%02ld:%02ld", valueMs/ 60000, (valueMs % 60000) / 1000 ]; +} + +-(void) updateFramesInclRecordingView { // place below the messages table. + BOOL showHide = _showVoiceRecorderView != !_vrView.hidden; + if (showHide) + _vrView.hidden = !_showVoiceRecorderView; + + CGRect vrFrame = _vrView.frame; + CGRect tableFrame = _tableController.tableView.frame; + if (showHide) { + tableFrame.size.height = _showVoiceRecorderView ? tableFrame.size.height - vrFrame.size.height : tableFrame.size.height + vrFrame.size.height; + _tableController.tableView.frame = tableFrame; + [_tableController.tableView reloadData]; + } + vrFrame.origin.y = tableFrame.origin.y+tableFrame.size.height; + _vrView.frame = vrFrame; +} + +-(void) stopAllPlays { + if (linphone_player_get_user_data(_sharedVoicePlayer)) { + NSDictionary* userInfo = @{@"path": [NSString stringWithUTF8String:linphone_player_get_user_data(_sharedVoicePlayer)]}; + [NSNotificationCenter.defaultCenter postNotificationName:kLinphoneVoiceMessagePlayerLostFocus object:nil userInfo:userInfo]; + } +} + +// send button state + +-(void) setSendButtonState { + _sendButton.enabled = !_isVoiceRecording && ((_isPendingVoiceRecord && linphone_recorder_get_duration(_voiceRecorder) > 0) || [[_messageField text] length] > 0 || _fileContext.count > 0); +} + + + @end diff --git a/Classes/FirstLoginView.m b/Classes/FirstLoginView.m index f00b1182d..a0d10d0a6 100644 --- a/Classes/FirstLoginView.m +++ b/Classes/FirstLoginView.m @@ -200,7 +200,7 @@ static UICompositeViewDescription *compositeDescription = nil; - (void)onLoginClick:(id)sender { if (!linphone_core_is_network_reachable(LC)) { - [PhoneMainView.instance presentViewController:[LinphoneUtils networkErrorView] animated:YES completion:nil]; + [PhoneMainView.instance presentViewController:[LinphoneUtils networkErrorView:@"configure an account"] animated:YES completion:nil]; return; } diff --git a/Classes/LinphoneManager.h b/Classes/LinphoneManager.h index f168ada5a..1caf894f7 100644 --- a/Classes/LinphoneManager.h +++ b/Classes/LinphoneManager.h @@ -60,6 +60,8 @@ extern NSString *const kLinphoneFileTransferRecvUpdate; extern NSString *const kLinphoneQRCodeFound; extern NSString *const kLinphoneChatCreateViewChange; extern NSString *const kLinphoneEphemeralMessageDeletedInRoom; +extern NSString *const kLinphoneVoiceMessagePlayerEOF; +extern NSString *const kLinphoneVoiceMessagePlayerLostFocus; extern NSString *const kLinphoneMsgNotificationAppGroupId; diff --git a/Classes/LinphoneManager.m b/Classes/LinphoneManager.m index 64bb1b296..47b288df4 100644 --- a/Classes/LinphoneManager.m +++ b/Classes/LinphoneManager.m @@ -75,6 +75,8 @@ NSString *const kLinphoneFileTransferRecvUpdate = @"LinphoneFileTransferRecvUpda NSString *const kLinphoneQRCodeFound = @"LinphoneQRCodeFound"; NSString *const kLinphoneChatCreateViewChange = @"LinphoneChatCreateViewChange"; NSString *const kLinphoneEphemeralMessageDeletedInRoom = @"LinphoneEphemeralMessageDeletedInRoom"; +NSString *const kLinphoneVoiceMessagePlayerEOF = @"LinphoneVoiceMessagePlayerEOF"; +NSString *const kLinphoneVoiceMessagePlayerLostFocus = @"LinphoneVoiceMessagePlayerLostFocus"; NSString *const kLinphoneMsgNotificationAppGroupId = @"group.org.linphone.phone.msgNotification"; @@ -1784,7 +1786,7 @@ static int comp_call_state_paused(const LinphoneCall *call, const void *param) { - (void)call:(const LinphoneAddress *)iaddr { // First verify that network is available, abort otherwise. if (!linphone_core_is_network_reachable(theLinphoneCore)) { - [PhoneMainView.instance presentViewController:[LinphoneUtils networkErrorView] animated:YES completion:nil]; + [PhoneMainView.instance presentViewController:[LinphoneUtils networkErrorView:@"place a call"] animated:YES completion:nil]; return; } diff --git a/Classes/LinphoneUI/Base.lproj/UIChatBubblePhotoCell.xib b/Classes/LinphoneUI/Base.lproj/UIChatBubblePhotoCell.xib index d320d6580..b987f08b4 100644 --- a/Classes/LinphoneUI/Base.lproj/UIChatBubblePhotoCell.xib +++ b/Classes/LinphoneUI/Base.lproj/UIChatBubblePhotoCell.xib @@ -33,11 +33,16 @@ + + + + + - + @@ -57,11 +62,11 @@ - + - + @@ -158,28 +163,60 @@ - + + + + + + + + + + + + + + + + + + + + @@ -217,5 +254,7 @@ + + diff --git a/Classes/LinphoneUI/UIChatBubblePhotoCell.h b/Classes/LinphoneUI/UIChatBubblePhotoCell.h index 43b6b6eed..c1530c413 100644 --- a/Classes/LinphoneUI/UIChatBubblePhotoCell.h +++ b/Classes/LinphoneUI/UIChatBubblePhotoCell.h @@ -44,6 +44,16 @@ @property (strong, nonatomic) IBOutlet UILongPressGestureRecognizer *plusLongGestureRecognizer; @property(strong, nonatomic) NSMutableArray *contentViews; +// Video recordings +@property (weak, nonatomic) IBOutlet UIView *vrView; +@property (weak, nonatomic) IBOutlet UIButton *vrPlayPause; +@property (weak, nonatomic) IBOutlet UILabel *vrTimerLabel; +@property (weak, nonatomic) IBOutlet UIImageView *vrWave; +@property (weak, nonatomic) IBOutlet UIView *vrWaveMaskPlayback; +@property NSTimer *vrPlayerTimer; +@property NSString *voiceRecordingFile; + + - (void)setEvent:(LinphoneEventLog *)event; - (void)setChatMessage:(LinphoneChatMessage *)message; diff --git a/Classes/LinphoneUI/UIChatBubblePhotoCell.m b/Classes/LinphoneUI/UIChatBubblePhotoCell.m index d96ae3cb2..4e6766faa 100644 --- a/Classes/LinphoneUI/UIChatBubblePhotoCell.m +++ b/Classes/LinphoneUI/UIChatBubblePhotoCell.m @@ -26,6 +26,11 @@ #import #import +#define voicePlayer VIEW(ChatConversationView).sharedVoicePlayer +#define chatView VIEW(ChatConversationView) + + + @implementation UIChatBubblePhotoCell { FileTransferDelegate *_ftd; CGSize imageSize, bubbleSize, videoDefaultSize; @@ -54,6 +59,8 @@ assetIsLoaded = FALSE; self.contentView.userInteractionEnabled = NO; _contentViews = [[NSMutableArray alloc] init]; + self.vrWaveMaskPlayback.layer.cornerRadius = 10.0f; + self.vrWaveMaskPlayback.layer.masksToBounds = YES; } return self; } @@ -151,32 +158,55 @@ }); } + - (void)update { if (self.message == nil) { LOGW(@"Cannot update message room cell: NULL message"); return; } [super update]; - + + NSMutableDictionary *encrptedFilePaths = NULL; + if ([VFSUtil vfsEnabledWithGroupName:kLinphoneMsgNotificationAppGroupId]) { + encrptedFilePaths = [LinphoneManager getMessageAppDataForKey:@"encryptedfiles" inMessage:self.message]; + if (!encrptedFilePaths) { + encrptedFilePaths = [NSMutableDictionary dictionary]; + } + } + + _voiceRecordingFile = nil; + LinphoneContent *voiceContent = [UIChatBubbleTextCell voiceContent:self.message]; + if (voiceContent) { + _voiceRecordingFile = [NSString stringWithUTF8String:[VFSUtil vfsEnabledWithGroupName:kLinphoneMsgNotificationAppGroupId] ? linphone_content_get_plain_file_path(voiceContent) : linphone_content_get_file_path(voiceContent)]; + if ([VFSUtil vfsEnabledWithGroupName:kLinphoneMsgNotificationAppGroupId]) + [encrptedFilePaths setValue:_voiceRecordingFile forKey:[NSString stringWithUTF8String:linphone_content_get_name(voiceContent)]]; + [self setVoiceMessageDuration]; + _vrWaveMaskPlayback.frame = CGRectZero; + _vrWaveMaskPlayback.backgroundColor = linphone_chat_message_is_outgoing(self.message) ? UIColor.orangeColor : UIColor.grayColor; + } + const bctbx_list_t *contents = linphone_chat_message_get_contents(self.message); + + size_t contentCount = bctbx_list_size(contents); + if (voiceContent) + contentCount--; BOOL multiParts = ((linphone_chat_message_get_text_content(self.message) != NULL) ? bctbx_list_size(contents) > 2 : bctbx_list_size(contents) > 1); + if (voiceContent && !multiParts) { + _cancelButton.hidden = _fileTransferProgress.hidden = _downloadButton.hidden = _playButton.hidden = _fileName.hidden = _fileView.hidden = _fileButton.hidden = YES; + return; + } + if (multiParts) { if (!assetIsLoaded) { - NSMutableDictionary *encrptedFilePaths = NULL; - if ([VFSUtil vfsEnabledWithGroupName:kLinphoneMsgNotificationAppGroupId]) { - encrptedFilePaths = [LinphoneManager getMessageAppDataForKey:@"encryptedfiles" inMessage:self.message]; - if (!encrptedFilePaths) { - encrptedFilePaths = [NSMutableDictionary dictionary]; - } - } - _imageGestureRecognizer.enabled = NO; _cancelButton.hidden = _fileTransferProgress.hidden = _downloadButton.hidden = _playButton.hidden = _fileName.hidden = _fileView.hidden = _fileButton.hidden = YES; - const bctbx_list_t *it = contents; int i; for (it = contents, i=0; it != NULL; it=bctbx_list_next(it)){ LinphoneContent *content = (LinphoneContent *)it->data; + if (linphone_content_is_voice_recording(content)) { // Handled elsewhere + continue; + } if (linphone_content_is_file_transfer(content) || linphone_content_is_file(content)){ UIChatContentView *contentView = [[UIChatContentView alloc] initWithFrame: CGRectMake(0,0,0,0)]; if([VFSUtil vfsEnabledWithGroupName:kLinphoneMsgNotificationAppGroupId] && (linphone_chat_message_is_outgoing(self.message) || linphone_content_is_file(content))) { @@ -214,8 +244,6 @@ return; } - - const char *url = linphone_chat_message_get_external_body_url(self.message); BOOL is_external = (url && (strstr(url, "http") == url)) || linphone_chat_message_get_file_transfer_information(self.message); @@ -713,16 +741,95 @@ textFrame.origin = CGPointMake(textFrame.origin.x, self.finalAssetView.frame.origin.y + self.finalAssetView.frame.size.height); else // When image hasn't be download - textFrame.origin = CGPointMake(textFrame.origin.x, _imageSubView.frame.size.height + _imageSubView.frame.origin.y - 10); + textFrame.origin = CGPointMake(textFrame.origin.x, _voiceRecordingFile ? _fileView.frame.origin.y : _imageSubView.frame.size.height + _imageSubView.frame.origin.y - 10); if (!utf8Text) { textFrame.size.height = 0; } else { textFrame.size.height = bubbleFrame.size.height - 90;//textFrame.origin.x; } + + if (_voiceRecordingFile) { + CGRect vrFrame = _vrView.frame; + vrFrame.origin.y = _contentViews.count == 0 && !utf8Text ? _fileView.frame.origin.y : textFrame.origin.y; + _vrView.frame = vrFrame; + textFrame.origin.y += VOICE_RECORDING_PLAYER_HEIGHT; + _vrView.hidden = NO; + } else { + _vrView.hidden = YES; + } self.messageText.frame = textFrame; } +// Voice messages + +static AVAudioPlayer* utilityPlayer; + +-(void) setVoiceMessageDuration { + NSError *error = nil; + AVAudioPlayer* utilityPlayer = [[AVAudioPlayer alloc]initWithContentsOfURL:[NSURL URLWithString:_voiceRecordingFile] error:&error]; // Workaround as opening multiple linphone_players at the same time can cause crash (here for example layout refreshed whilst a voice memo is playing + _vrTimerLabel.text = [self formattedDuration:utilityPlayer.duration]; + utilityPlayer = nil; +} + +-(void) voicePlayTimerUpdate { + CGRect r = _vrWaveMaskPlayback.frame; + r.size.width += _vrView.frame.size.width / ((linphone_player_get_duration(voicePlayer) / 500)) ; + if (r.size.width > _vrView.frame.size.width) { + r.size.width = _vrView.frame.size.width; + } + [UIView animateWithDuration:0.5 delay:0.0 options:UIViewAnimationOptionCurveLinear animations:^{ + _vrWaveMaskPlayback.frame = r; + }completion:^(BOOL finished) {}]; +} + + +-(void) stopPlayer { + [NSNotificationCenter.defaultCenter removeObserver:self]; + [chatView stopSharedPlayer]; + [_vrPlayPause setImage:[UIImage imageNamed:@"vr_play"] forState:UIControlStateNormal]; + [_vrPlayerTimer invalidate]; + _vrWaveMaskPlayback.frame = CGRectZero; +} + +-(NSString *)formattedDuration:(long)valueMs { + return [NSString stringWithFormat:@"%02ld:%02ld", valueMs/ 60, (valueMs % 60) ]; +} + +-(void) startPlayer { + [chatView startSharedPlayer:_voiceRecordingFile.UTF8String]; + [NSNotificationCenter.defaultCenter addObserver:self + selector:@selector(stopPlayer) + name:kLinphoneVoiceMessagePlayerLostFocus + object:nil]; + + [NSNotificationCenter.defaultCenter addObserver:self + selector:@selector(stopPlayer) + name:kLinphoneVoiceMessagePlayerEOF + object:nil]; + + [_vrPlayPause setImage:[UIImage imageNamed:@"vr_stop"] forState:UIControlStateNormal]; + CGRect r = CGRectZero; + r.size.height = _vrView.frame.size.height - 14; + r.origin.y = 7; + _vrWaveMaskPlayback.frame = r; + _vrPlayerTimer = [NSTimer scheduledTimerWithTimeInterval:0.5 + target:self + selector:@selector(voicePlayTimerUpdate) + userInfo:nil + repeats:YES]; + [self voicePlayTimerUpdate]; + +} + +- (IBAction)onVRPlayPauseClick:(id)sender { + if ([chatView sharedPlayedIsPlaying:_voiceRecordingFile.UTF8String]) + [self stopPlayer]; + else { + [self startPlayer]; + } +} + @end diff --git a/Classes/LinphoneUI/UIChatBubbleTextCell.h b/Classes/LinphoneUI/UIChatBubbleTextCell.h index 6778a8143..062164d5f 100644 --- a/Classes/LinphoneUI/UIChatBubbleTextCell.h +++ b/Classes/LinphoneUI/UIChatBubbleTextCell.h @@ -26,6 +26,9 @@ #define CELL_IMAGE_X_MARGIN 100 #define IMAGE_DEFAULT_WIDTH 120 #define IMAGE_DEFAULT_MARGIN 5 +#define VOICE_RECORDING_PLAYER_HEIGHT 60 +#define VOICE_RECORDING_PLAYER_WIDTH 300 + @interface UIChatBubbleTextCell : UITableViewCell @@ -72,5 +75,6 @@ + (NSString *)TextMessageForChat:(LinphoneChatMessage *)message; + (CGSize)computeBoundingBox:(NSString *)text size:(CGSize)size font:(UIFont *)font; + (NSString *)ContactDateForChat:(LinphoneChatMessage *)message; ++(LinphoneContent *) voiceContent:(LinphoneChatMessage *)message; @end diff --git a/Classes/LinphoneUI/UIChatBubbleTextCell.m b/Classes/LinphoneUI/UIChatBubbleTextCell.m index 808cb20b1..fc3371774 100644 --- a/Classes/LinphoneUI/UIChatBubbleTextCell.m +++ b/Classes/LinphoneUI/UIChatBubbleTextCell.m @@ -306,6 +306,12 @@ } - (void)onResend { + + if (!linphone_core_is_network_reachable(LC)) { + [PhoneMainView.instance presentViewController:[LinphoneUtils networkErrorView:@"send a message"] animated:YES completion:nil]; + //return; + } + if (_message == nil || !linphone_chat_message_is_outgoing(_message)) return; @@ -314,7 +320,13 @@ return; const bctbx_list_t *contents = linphone_chat_message_get_contents(_message); - BOOL multiParts = ((linphone_chat_message_get_text_content(self.message) != NULL) ? bctbx_list_size(contents) > 2 : bctbx_list_size(contents) > 1); + LinphoneContent *voiceContent = [UIChatBubbleTextCell voiceContent:_message]; + size_t contentCount = bctbx_list_size(contents); + if (voiceContent) + contentCount--; + + BOOL multiParts = ((linphone_chat_message_get_text_content(_message) != NULL) ? contentCount > 2 : contentCount > 1); + if (multiParts) { FileContext *newfileContext = [[FileContext alloc] init]; [newfileContext clear]; @@ -323,6 +335,9 @@ const bctbx_list_t *it; for (it = contents, i=0; it != NULL; it=bctbx_list_next(it)){ LinphoneContent *content = (LinphoneContent *)it->data; + if (linphone_content_is_voice_recording(content)) { + continue; + } if (linphone_content_is_file_transfer(content) || linphone_content_is_file(content)){ NSString *name = [NSString stringWithUTF8String:linphone_content_get_name(content)]; NSString *filePath = [encrptedFilePaths valueForKey:name]; @@ -335,11 +350,11 @@ [self onDelete]; dispatch_async(dispatch_get_main_queue(), ^ { const char *text = linphone_chat_message_get_text_content(_message); - [_chatRoomDelegate resendMultiFiles:newfileContext message: text? [NSString stringWithUTF8String:text]: NULL]; + [_chatRoomDelegate resendMultiFiles:newfileContext message: text? [NSString stringWithUTF8String:text]: NULL voiceContent:voiceContent]; }); return; } - if (linphone_chat_message_get_file_transfer_information(_message) != NULL) { + if (!voiceContent && contentCount == 1 && linphone_chat_message_get_file_transfer_information(_message) != NULL) { NSString *localImage = [LinphoneManager getMessageAppDataForKey:@"localimage" inMessage:_message]; NSString *localVideo = [LinphoneManager getMessageAppDataForKey:@"localvideo" inMessage:_message]; NSString *localFile = [LinphoneManager getMessageAppDataForKey:@"localfile" inMessage:_message]; @@ -354,11 +369,11 @@ const char *text = linphone_chat_message_get_text_content(_message); NSString *str = text ? [NSString stringWithUTF8String:text] : NULL; if (localImage) { - [_chatRoomDelegate resendFile: (data?:[ChatConversationView getFileData:localImage]) withName:localImage type:@"image" key:@"localimage" message:str]; + [_chatRoomDelegate resendFile: (data?:[ChatConversationView getFileData:localImage]) withName:localImage type:@"image" key:@"localimage" message:str voiceContent:voiceContent]; } else if (localVideo) { - [_chatRoomDelegate resendFile:(data?:[ChatConversationView getFileData:localVideo]) withName:localVideo type:@"video" key:@"localvideo" message:str]; + [_chatRoomDelegate resendFile:(data?:[ChatConversationView getFileData:localVideo]) withName:localVideo type:@"video" key:@"localvideo" message:str voiceContent:voiceContent]; } else { - [_chatRoomDelegate resendFile:(data?:[ChatConversationView getFileData:localFile]) withName:localFile type:@"image" key:@"localfile" message:str]; + [_chatRoomDelegate resendFile:(data?:[ChatConversationView getFileData:localFile]) withName:localFile type:@"image" key:@"localfile" message:str voiceContent:voiceContent]; } }); } else { @@ -366,7 +381,10 @@ double delayInSeconds = 0.4; dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); dispatch_after(popTime, dispatch_get_main_queue(), ^(void) { - [_chatRoomDelegate resendChat:self.textMessage withExternalUrl:nil]; + NSString *text = self.textMessage; + if (voiceContent && [text isEqualToString:@"🗻"]) + text = nil; + [_chatRoomDelegate resendChat:text withExternalUrl:nil voiceContent:voiceContent]; }); } } @@ -457,6 +475,20 @@ static const CGFloat CELL_MESSAGE_Y_MARGIN = 44; return image; } ++(LinphoneContent *) voiceContent:(LinphoneChatMessage *)message { + for (const bctbx_list_t *it = linphone_chat_message_get_contents(message); it != NULL; it=bctbx_list_next(it)){ + LinphoneContent *content = (LinphoneContent *)it->data; + if (linphone_content_is_voice_recording(content)) + return content; + } + return nil; +} + + ++(CGSize) addVoicePlayerToSize:(CGSize)size withMargins:(BOOL)margins { + return CGSizeMake(MAX(size.width,VOICE_RECORDING_PLAYER_WIDTH + (margins ? CELL_MESSAGE_X_MARGIN: 0)), size.height + VOICE_RECORDING_PLAYER_HEIGHT+(margins ? CELL_MESSAGE_Y_MARGIN: 0)); + +} + (CGSize)ViewHeightForMessageText:(LinphoneChatMessage *)chat withWidth:(int)width textForImdn:(NSString *)imdnText { NSString *messageText = [UIChatBubbleTextCell TextMessageForChat:chat]; @@ -484,14 +516,51 @@ static const CGFloat CELL_MESSAGE_Y_MARGIN = 44; CGFloat imagesh=0; CGFloat max_imagesw=0; CGFloat max_imagesh=0; + LinphoneContent *voiceContent = [self voiceContent:chat]; const bctbx_list_t *contents = linphone_chat_message_get_contents(chat); - BOOL multiParts = ((linphone_chat_message_get_text_content(chat) != NULL) ? bctbx_list_size(contents) > 2 : bctbx_list_size(contents) > 1); + size_t contentCount = bctbx_list_size(contents); + if (voiceContent) + contentCount--; + + BOOL multiParts = ((linphone_chat_message_get_text_content(chat) != NULL) ? contentCount > 2 : contentCount > 1); + + if (voiceContent && contentCount == 0) { + size = CGSizeMake(VOICE_RECORDING_PLAYER_WIDTH, VOICE_RECORDING_PLAYER_HEIGHT); + CGSize textSize = CGSizeMake(0, 0); + if (![messageText isEqualToString:@"🗻"]) { + textSize = [self computeBoundingBox:messageText + size:CGSizeMake(max_imagesw , CGFLOAT_MAX) + font:messageFont]; + } + + // add size for message text + size.height += textSize.height; + size.width = MAX(textSize.width, size.width); + 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; + } + if (multiParts) { const bctbx_list_t *it = contents; NSMutableDictionary *encrptedFilePaths = [LinphoneManager getMessageAppDataForKey:@"encryptedfiles" inMessage:chat]; for (it = contents; it != NULL; it=bctbx_list_next(it)){ LinphoneContent *content = (LinphoneContent *)it->data; + if (linphone_content_is_voice_recording(content)) { + CGSize sSize = CGSizeMake(VOICE_RECORDING_PLAYER_WIDTH, VOICE_RECORDING_PLAYER_HEIGHT); + imagesw += sSize.width; + if (imagesw > width) { + imagesw = sSize.width; + max_imagesw = MAX(max_imagesw, imagesw); + max_imagesh += imagesh; + imagesh = sSize.height; + } else { + max_imagesw = MAX(max_imagesw, imagesw); + imagesh = MAX(imagesh, sSize.height); + } + continue; + } UIImage *image; if(!linphone_chat_message_is_outgoing(chat) && linphone_content_is_file_transfer(content)) { // not yet downloaded @@ -538,14 +607,15 @@ static const CGFloat CELL_MESSAGE_Y_MARGIN = 44; size.height = MAX(size.height + CELL_MESSAGE_Y_MARGIN, CELL_MIN_HEIGHT) ; return size; } + - - LinphoneContent *fileContent = linphone_chat_message_get_file_transfer_information(chat); + LinphoneContent *fileContent = linphone_chat_message_get_utf8_text(chat) ? nil : linphone_chat_message_get_file_transfer_information(chat); if (url == nil && fileContent == NULL) { size = [self computeBoundingBox:messageText size:CGSizeMake(width - CELL_MESSAGE_X_MARGIN - 4, CGFLOAT_MAX) font:messageFont]; } else { + NSString *localImage = [LinphoneManager getMessageAppDataForKey:@"localimage" inMessage:chat]; NSString *localFile = [LinphoneManager getMessageAppDataForKey:@"localfile" inMessage:chat]; NSString *localVideo = [LinphoneManager getMessageAppDataForKey:@"localvideo" inMessage:chat]; @@ -583,27 +653,43 @@ static const CGFloat CELL_MESSAGE_Y_MARGIN = 44; image = [[UIImage alloc] initWithData:data]; } } else { - return [self ViewHeightForFile:width]; + CGSize fileSize = [self ViewHeightForFile:width]; + if (voiceContent) { + fileSize = [self addVoicePlayerToSize:fileSize withMargins:true]; + } + return fileSize; } originalImageSize = image.size; } else { if (!localImage && !localVideo) { //We are loading the image - return CGSizeMake(CELL_MIN_WIDTH + CELL_MESSAGE_X_MARGIN, CELL_MIN_HEIGHT + CELL_MESSAGE_Y_MARGIN + textSize.height + 20); + CGSize baseSize = CGSizeMake(CELL_MIN_WIDTH + CELL_MESSAGE_X_MARGIN, CELL_MIN_HEIGHT + CELL_MESSAGE_Y_MARGIN + textSize.height + 20); + if (voiceContent) { + baseSize = [self addVoicePlayerToSize:baseSize withMargins:true]; + } + return baseSize; } if (localImage && [[NSFileManager defaultManager] fileExistsAtPath:filePath]) { NSData* data = [NSData dataWithContentsOfFile:filePath]; UIImage *image = [[UIImage alloc] initWithData:data]; if (!image) { - return [self ViewHeightForFile:width]; + CGSize fileSize = [self ViewHeightForFile:width]; + if (voiceContent) { + fileSize = [self addVoicePlayerToSize:fileSize withMargins:true]; + } + return fileSize; } originalImageSize = image.size; } else if (localVideo && [[NSFileManager defaultManager] fileExistsAtPath:filePath]) { UIImage *image = [UIChatBubbleTextCell getImageFromVideoUrl:[NSURL fileURLWithPath:filePath]]; if (!image) { - return [self ViewHeightForFile:width]; + CGSize fileSize = [self ViewHeightForFile:width]; + if (voiceContent) { + fileSize = [self addVoicePlayerToSize:fileSize withMargins:true]; + } + return fileSize; } originalImageSize = image.size; } else { @@ -615,7 +701,11 @@ static const CGFloat CELL_MESSAGE_Y_MARGIN = 44; assets = [PHAsset fetchAssetsWithLocalIdentifiers:[NSArray arrayWithObject:localVideo] options:nil]; if (![assets firstObject]) { - return CGSizeMake(CELL_MIN_WIDTH, CELL_MIN_WIDTH + CELL_MESSAGE_Y_MARGIN + textSize.height); + CGSize baseSize = CGSizeMake(CELL_MIN_WIDTH, CELL_MIN_WIDTH + CELL_MESSAGE_Y_MARGIN + textSize.height); + if (voiceContent) { + baseSize = [self addVoicePlayerToSize:baseSize withMargins:true]; + } + return baseSize; } else { PHAsset *asset = [assets firstObject]; originalImageSize = CGSizeMake([asset pixelWidth], [asset pixelHeight]); @@ -627,6 +717,11 @@ static const CGFloat CELL_MESSAGE_Y_MARGIN = 44; size.height += textSize.height; size.width = MAX(textSize.width, size.width); } + + if (voiceContent) { + size.width = MAX(size.width,VOICE_RECORDING_PLAYER_WIDTH); + size.height += VOICE_RECORDING_PLAYER_HEIGHT; + } size.width = MAX(size.width + CELL_MESSAGE_X_MARGIN, CELL_MIN_WIDTH); size.height = MAX(size.height + CELL_MESSAGE_Y_MARGIN, CELL_MIN_HEIGHT); diff --git a/Classes/PhoneMainView.m b/Classes/PhoneMainView.m index c8d78cd4f..680712541 100644 --- a/Classes/PhoneMainView.m +++ b/Classes/PhoneMainView.m @@ -830,7 +830,7 @@ static RootViewManager *rootViewManagerInstance = nil; } if (!linphone_core_is_network_reachable(LC)) { - [PhoneMainView.instance presentViewController:[LinphoneUtils networkErrorView] animated:YES completion:nil]; + [PhoneMainView.instance presentViewController:[LinphoneUtils networkErrorView:@"send a message"] animated:YES completion:nil]; return; } diff --git a/Classes/Utils/FileTransferDelegate.h b/Classes/Utils/FileTransferDelegate.h index 3aa3d22db..21ff1d605 100644 --- a/Classes/Utils/FileTransferDelegate.h +++ b/Classes/Utils/FileTransferDelegate.h @@ -24,8 +24,8 @@ @interface FileTransferDelegate : NSObject -- (void)uploadFileContent: (FileContext *)context forChatRoom:(LinphoneChatRoom *)chatRoom; -- (void)uploadData:(NSData *)data forChatRoom:(LinphoneChatRoom *)chatRoom type:(NSString *)type subtype:(NSString *)subtype name:(NSString *)name key:(NSString *)key; +- (void)uploadFileContent: (FileContext *)context forChatRoom:(LinphoneChatRoom *)chatRoom andVoiceContent:(LinphoneContent *)voiceContent; +- (void)uploadData:(NSData *)data forChatRoom:(LinphoneChatRoom *)chatRoom type:(NSString *)type subtype:(NSString *)subtype name:(NSString *)name key:(NSString *)key voiceContent:(LinphoneContent *)voiceContent; - (void)uploadImage:(UIImage *)image 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; diff --git a/Classes/Utils/FileTransferDelegate.m b/Classes/Utils/FileTransferDelegate.m index d8d20670b..9ad65457e 100644 --- a/Classes/Utils/FileTransferDelegate.m +++ b/Classes/Utils/FileTransferDelegate.m @@ -102,7 +102,7 @@ static void file_transfer_progress_indication_send(LinphoneChatMessage *message, } } -- (void)uploadData:(NSData *)data forChatRoom:(LinphoneChatRoom *)chatRoom type:(NSString *)type subtype:(NSString *)subtype name:(NSString *)name key:(NSString *)key{ +- (void)uploadData:(NSData *)data forChatRoom:(LinphoneChatRoom *)chatRoom type:(NSString *)type subtype:(NSString *)subtype name:(NSString *)name key:(NSString *)key voiceContent:(LinphoneContent *)voiceContent{ if ([[LinphoneManager.instance fileTransferDelegates] containsObject:self]) { LOGW(@"fileTransferDelegates has already added %p", self); return; @@ -124,12 +124,15 @@ static void file_transfer_progress_indication_send(LinphoneChatMessage *message, linphone_chat_message_cbs_set_file_transfer_progress_indication(linphone_chat_message_get_callbacks(_message), file_transfer_progress_indication_send); [LinphoneManager setValueInMessageAppData:name forKey:key inMessage:_message]; + + if (voiceContent) + linphone_chat_message_add_content(_message, voiceContent); LOGI(@"%p Uploading content from message %p", self, _message); linphone_chat_message_send(_message); } -- (void)uploadFileContent: (FileContext *)context forChatRoom:(LinphoneChatRoom *)chatRoom { +- (void)uploadFileContent: (FileContext *)context forChatRoom:(LinphoneChatRoom *)chatRoom andVoiceContent:(LinphoneContent *)voiceContent{ [LinphoneManager.instance.fileTransferDelegates addObject:self]; _message = linphone_chat_room_create_empty_message(chatRoom); @@ -162,6 +165,8 @@ static void file_transfer_progress_indication_send(LinphoneChatMessage *message, // todo indication progress [LinphoneManager setValueInMessageAppData:names forKey:@"multiparts" inMessage:_message]; [LinphoneManager setValueInMessageAppData:types forKey:@"multipartstypes" inMessage:_message]; + if (voiceContent) + linphone_chat_message_add_content(_message, voiceContent); LOGI(@"%p Uploading content from message %p", self, _message); linphone_chat_message_send(_message); } @@ -170,12 +175,12 @@ static void file_transfer_progress_indication_send(LinphoneChatMessage *message, - (void)uploadImage:(UIImage *)image forChatRoom:(LinphoneChatRoom *)chatRoom withQuality:(float)quality { NSString *name = [NSString stringWithFormat:@"%li-%f.jpg", (long)image.hash, [NSDate timeIntervalSinceReferenceDate]]; NSData *data = UIImageJPEGRepresentation(image, quality); - [self uploadData:data forChatRoom:chatRoom type:@"image" subtype:@"jpg" name:name key:@"localimage"]; + [self uploadData:data forChatRoom:chatRoom type:@"image" subtype:@"jpg" name:name key:@"localimage" voiceContent:nil]; } - (void)uploadVideo:(NSData *)data withassetId:(NSString *)phAssetId forChatRoom:(LinphoneChatRoom *)chatRoom { NSString *name = [NSString stringWithFormat:@"IMG-%f.MOV", [NSDate timeIntervalSinceReferenceDate]]; - [self uploadData:data forChatRoom:chatRoom type:@"video" subtype:@"mov" name:name key:@"localvideo"]; + [self uploadData:data forChatRoom:chatRoom type:@"video" subtype:@"mov" name:name key:@"localvideo" voiceContent:nil]; } - (void)uploadFile:(NSData *)data forChatRoom:(LinphoneChatRoom *)chatRoom withName:(NSString *)name { @@ -184,7 +189,7 @@ static void file_transfer_progress_indication_send(LinphoneChatMessage *message, NSString *fileType = [[asset tracksWithMediaType:AVMediaTypeVideo] count] > 0 ? @"video" : @"file"; NSString *key = [ChatConversationView getKeyFromFileType:fileType fileName:name]; - [self uploadData:data forChatRoom:chatRoom type:fileType subtype:name.lastPathComponent name:name key:key]; + [self uploadData:data forChatRoom:chatRoom type:fileType subtype:name.lastPathComponent name:name key:key voiceContent:nil]; } - (BOOL)download:(LinphoneChatMessage *)message { diff --git a/Classes/Utils/Utils.h b/Classes/Utils/Utils.h index 7289bbe83..d515926af 100644 --- a/Classes/Utils/Utils.h +++ b/Classes/Utils/Utils.h @@ -39,7 +39,7 @@ + (UIImage *)resizeImage:(UIImage *)imageToResize newSize:(CGSize)newSize; + (LinphoneAddress *)normalizeSipOrPhoneAddress:(NSString *)addr; -+ (UIAlertController *)networkErrorView; ++ (UIAlertController *)networkErrorView:(NSString *)action; typedef enum { LinphoneDateHistoryList, diff --git a/Classes/Utils/Utils.m b/Classes/Utils/Utils.m index 4f6d1249d..7ae1fedca 100644 --- a/Classes/Utils/Utils.m +++ b/Classes/Utils/Utils.m @@ -516,12 +516,10 @@ return res; } -+ (UIAlertController *)networkErrorView { ++ (UIAlertController *)networkErrorView:(NSString *)action { UIAlertController *errView = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Network Error", nil) - message:NSLocalizedString(@"There is no network connection available, " - @"enable WIFI or WWAN prior to place a call", - nil) + message:NSLocalizedString([@"There is no network connection available, enable WIFI or WWAN prior to " stringByAppendingString:action],nil) preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction *defaultAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", nil) diff --git a/Resources/en.lproj/Localizable.strings b/Resources/en.lproj/Localizable.strings index a6921777283f02b6a3459d925a1e1ca1157e6be4..76f33d13e5ef9b0b98da692eded9ac33c05d1570 100644 GIT binary patch delta 68 zcmZ29i@9qq^9Hrn$tN1RCZE~PHTjLH5W5nC0)s7s!sLSu6JcVkN(|NvT$2;qk@YWry3e} delta 34 ocmeC0%e-h7^9Hrn$vK@IlN~zvCO>Oam^`mj1jOF_tZj)q0024<9smFU diff --git a/Resources/fr.lproj/Localizable.strings b/Resources/fr.lproj/Localizable.strings index b119be4b7285149ae778d8e5445b958e36e3a61d..318063fa67dcd23b218657727217bd4acbf5c768 100644 GIT binary patch delta 46 zcmV+}0MY-5z5|TG1F)u!lP-;lld4A!lUlP5lhBQ*lioWf2QC0206PE*lb??tv%HQ> EP~bQd6#xJL delta 18 acmeA<$lS4ydBd{4$qtjmHakr42><|5AP5)$ diff --git a/Resources/images/vr_off.png b/Resources/images/vr_off.png new file mode 100644 index 0000000000000000000000000000000000000000..5f319e5c8f05cab76695bddb373900429132a997 GIT binary patch literal 12846 zcmeHtWmH>V^KNk0;u@sI8c1-06?b=Q2@puo6t@;D*5bul+#QM*O0gDq_W~^xDDG0E zm-hGSz4yy|*ZqHcv(`z@*)#LZ%(LgrIy?JBYHKPH;nU#*001HtWqIAZU(VkLF6Q0$ zgLBk60KgdUt8eVC3-bb^TwQFDj&Pv64+;*1dn0WD0Pp#VG^8g(S#szvftPf$ql}iK zbq+1GlLy-iHmWtC<-IR8$w{u<{OFps^zF{S_5)(Bm~Rs)h6>C}g=)j4OA4r<(vusf zc8mMBD(6=>_oSY01Uz4UcFTHTxF*zkJNN18X6WjAXZe@&{`O7JDYMAWiw>{n{ZHC9 zZ*FccE^o-Bl+FgCm)Ih1PuJ{Pqz_tk2=~bfX4*FVWet< z8tW~Y)vDjiOYB>gB*Gy2_cPI20a78C){d=KCpZDO!*xq=*MSuQhphA40x7A0p9K=C z0d34r_5;53N<9fs-R`-P`gQdD+F$(wDSx`%_qB)2;CQu2>{r~Bo1vuL@+*t2zSPZI!0?tvIpK-R+EYGKvn&5=2+gvhb~x8s4E zi`anU(_O`lV!Mw4TXy9%M%q&XgmBH}rk5`+j$^oJJw!Th1xBTBb*$KE2So;x@XqW$ zIzwuHl~xo>lwAot`QdY#%D4RtL$ae50uvqwSIc(Jk~p4XyQ>n^?o^VTvOa-0Y3A^Q#x@zk|V=^Cs9vJ z{6@fGzx+16{)xc(hRN}Md1zYk=7phDZTCWf|KcYC%^Fw5ZrLN7kDM43?%KndTYC8Y zi`fngALWeJ+Bs_zYCrNTVo#j0F#mefTs3$j?7}7Msr6S;|*WJ1Qk7dFgI$p*2IVfy32`nt9V2`m&4P59^0kGr#$4>Z*#P z(Y6%oC1-V2ReM!8F^kWIlvpR#oYkf*Z{)Wg4LdRW5$C+yDkd^(u4?j%@sQpf)$AJg znTJNbo|X1nlf_lESH$!te&+zYcZnya3#%qM-;skZ%mzhNI?|)(=Pa=Lj38hFwa6cH z0vR#9oR`S!il~jKiDy|JJJWf!kRVi=hm!8miN2y5Wp*gbgoM<41Is>DmQH)WjJfT5 zy{Ibx@m@8xqP<jFO-u?L3;nWpUHt*XLDTyfNV zh7`!@p&OR=OW^VtzALAkM)(C^n##Tn7nz0*YF;V0rH2#UFS-V+6 z>zg2>y*6LbiVhd;#~D*vdkr2*1DUt;%HvUlr?q3`+POk6@o}FQ7b*rVUk=Mv$dw|o zx}slDY_ju)L5HGgREdx@7VBnt=-b7<#*1|w**lslEfr2{Pm}d}xj&!!q%_KvH}Uz} z2`c%_fO`Pco~r_zOkv;jlHIRxs`gGXG{%Sz-alLL*j>>AHQ8-P9}U4p))m@z%>7hJ zK#+~Scv-x06^Gc}xWJ;GnrCxzWKMRv(>YZwllEL>tQZXTe1qwG`74_iZ3H*2D?Nli z>m@8^r@Xj*>Jx+Lk5t+ApjUiE9ZmLO zkgn@*Lftog3}xDYj)w1QYMjaXZ3RP`)3b%uYO{2TlNszVr!#cBL&awy4kWn8qgro> z(K|%~j?^>O(jl(&kdZOEiUhwLSEENKrVk9he5cTnxy{Hc|tN3eP;E5+WUi^fhoGE=P+4?DSz%_(M>r(G^h3x%vY9s?b`$zi{Nqf z$MJ@@)&oA#bknYI7pUzRDl?n6dt*vNWyK>oqSMpfVR6atMf1S;%P&>5?{y|9kVCK! z@TCa`bt>g>LBl6YRQWi`M>4B=10)OI@B?g>d!Q6L*zKo}c%;6bBV{pGwB-3PvXrNl6vdsw%+A9jnYPQ5Ps3hf*Ec$K?g3R<3t8w3RL%MIs2_(m(3KEO zx;fJE9FS|3KNGw+9!LH(Y=0rq`8`&6vqs!2sd5;Ya-3NDB#klk9^GPQQ}6_dH^EOC zRv!p_^ zV@MZlT1RmwAF?xdSK>6KeaIk1lya2|<5HgxZncJI;ytwiCzx0&a1FlR1~^Cl%zg8w zrdX(KSx)*jRdy=${8Z4s1f*Sq1ai*98x|3I-XKdl<%DpBzAZVzphFDi60W1*BO+c2 zKP}_P#}DSx2IBlw^U!V&S}fVKFdtKP%LR4x;tFq$FCP-V3;dMzwLz4z`Fm$@&d;AE z*I54T?E)$E1q!U@#MSwIC2pnUQQNViSeS2oWmPULgaUR}a#K-kF`h6uz) z=={W#f(2;)2G7oZk|h$JzFCBrdoxLqLHtd^UGUjkH>Y5R*Nf}c1(izu2#a_$nBy1b z(a1B$VlL+`?&jb}oUx6KDgi9hWAY_Ip0PH0_Ve1$1P)Hnb!7UOE;=*NxJWLR>6#%#Rw)vFt^2C^l@j3G*Xo-zRPS%Yt-wTTr~|YPSO>0?3~NjgiKJt7gG#HA*tlz z7h9jro8tX&isNf(?JP2Befso#qVg!Te7oCc@d)A9@1J-2c z`#x|7rD2RA7FX>yGjbfz*nHe2bPZq9I+=3I@~{`TVz`o!#@bG~ThgjZJg1h$3{n4ndP^a`HQSu0 zZ-G+l$RI_$iYC7NZ}71M19@=@RGZi_JgS0de=$0t0u?i z*Hn)nSVyeT!S+(6Fbiyjx?>+XE3`QrmX|68->bJz4jzr?exHf(Vt9Pq{rZ>f^DgsS*ke;STiYt1xshY^DZ#2(69aDuU;;jl zyg0&smk~6Fn6XGz%pexUjBiuKBC2DX!a=C}-mXV~Xg%{lfhh6LDZDx{6`R{X4^qYa zgx939Z&}TIYcy24yl!b(yDizi5bJx=fm*J;RugCE)Q?cM}ik+RSbMeegxhPX;_W=Up3=slWmaq9vxo|7@eOghHYeM52n9U?M#!f>$6+UUuyOoJ)rsSR$qf~8VD#p*Ct9t;VJVhs+Q_p+R54|aK|xnD z0(*g!GDu#eA(3=>tPj5nZ!X8y^ulnaAFN1rUuFf&*QjloxM1$$A$vtSzK5egl!=qh z=85em-vsmOa1`taaVl2E1eIq;>O4U3))AKn#=OPqde2xaq0=2-`00z#13JsOL*2EnK>x0|JSL1=B=x6W5!Nn90ud8Xh@ zMfq0@&kqn`Jgee47P$Sn3KZJXju{)XaUqLu-^Z&9pg3}9nZS$7*s(musZHAnX(EsvjN*AM! zzYT=d4?ixL;enH|z$YIVY)k@AfHNf;Obakuh$P9~Q4s;jY@1NNGf?G@i8+l>>~|rQ zK}5bPHwc#5#2Eh+e$o3v41BY97Q{n9ucC)JxLb#n2nl?n(mmR3C8xkwV(pJE8G~w{ zea+ZboHu8o@eRiu*f5eySj>(=dg^LLD;LspoJd%zhzt!jeNWr$PuKO1()}$}q0;CS z4puK)fie1*=h}To88m<~Do7{03xAh+-GCsvFL~tg`Jp!N(w z=mRHiG3r{HAq!F2{$~d0yn(GOuZanY_OdCkeSN1(y3o5*;$^D6i!gxj+LyPo^Q-><&5Rw>np&^8ZqwV+GTkaVY3qdvTf4$Bd^D;$z zJo45a$$mAhmiwW^U0qd^&GoK-QBY~6ct+)>6urKAP~kI9UzQuc6q2vf6g}SS`?6>8WJ6H{VY4tIyZtlDi6VZY^sa1mjf7V%nT9UQrPm7}Sz- zc0ODP{FIu6+uW`drp4mmq;r7S=Proldh3MkZ^?=^ifbA$n(rs#qL!v#W1C=WLXfLZ zK^|BWCb5``lc)LvMpk^QDG-#eB2EzdaP9+BvVMa2sC{XsurFYnp@Kv|p~uF@4LQcI zPeqP7Qqtu+iT+W|ZU;XTHds1Ln&|PxYBc8LEYE(#+DY)$Swe~L`}*W+@;yu?419q8 zJ92mIV&k;gRyAcYv#}T&1>6nR+=v&#K;h$&(JR+FqREgdn!2A`DrceG9S-(JE0L-% zIVSuyF{19DR_JD4Y3ZH!%A>^3fliR*gt@;$kBCWlm6+ zj6jf?;WwM*To- z-Rg6^E;=!7sbn?$!rnk3cl zNFM7L0S#q*Nd@t%e6q#)>loM~|IUl{F(OpVi~Nf&*LUOwa1qJi~DQ z&Zy(X1uHitac)dKhsG2;jRBtHj=elH&5)!B-eMKI+)!%az;oZGpQPCf2kBH^=G*Zy zkgo~wx`oI-3}(_{TvK*qqQYJ?hg5MA^+&x5+*>LK7#SX_yk+NmG@Mdxi&$=P7&(EP zW9(JNc5#+IaddrWzTzXZ86kUzI;9XH@dLTM4lPSI%t?f9__S}s9B;JgyY$_GEr;JW*qs;TL_flW(B%L?s9$cKL6S#VX ztP!Ff2_B2?opx1_B}`^D_ns*_A2&rXEpAsFqPTDe;?ieanG(P5SJSIr2>X$|*uabxd% zxE=-M>XBY?Qh=|W3(==CMUMuZX}UR1|Dfn zKD2Oouc^XcfyE|K=#GY5_f-k z$f!)Yb^#w06SSux7m-&WhnHvM=c09i)0Z^DT2@gu%$`YAW-I$bh3cr`haX?6zvHU& z8dAKy&^GTxPHvOssLa`TpH8<9`smJSPeJIsB_rN=~Mv|pNI&`dFR*5AG{c2OuR z>FAGmfU~t{KGr&tfTZr7ah%f^ZD}q$abTPVA~dBZqB3v8VkF%kHW>p z(-({82$q+sjYUkUZ;0^~oBi@E9Y3Q4C;F%#!JIN;H;F1)+)PI)9LdO-$p8V=_h^qrYR%-5Pgxo$k2DPBH(YF;^X5`BjqpQd*3rCEinJ(PzFi z^p$LdP_kfJnK;1$l-v*TiKF?AzAX{wArwSGboV>3axN1OyP@!AwxOP^EQD-k0=>Yy z>!E((Ss5+}(Y(+Isw4_UxeKnQVlV`u?XG#g(UnTv_RfGIO+!ZKC>VjJ#q<`V?8i^& zzhDMGW+Oq^ERvaG>F`;wBz9D?V3!HIT27+XK>C$mWtD{b4#MXe?VRR&lloC|!l3(# z>>acXRD;zx@@w@Xz#q{(xByC1O|u;X&7?zZP$B3n(MH^(0iJiB9`lg~<&K5<=FwC2 zCS88rV2%X>jTZys&OtUt_=B3WLI)g_4lftu7d+ZqK^aZxg$Q%73*Jn-Hna|Lf@sNS zl+!mJBj%rYX(~oyfujIWFFs^XIUe8#)C;1WD`tlHn{#|u^s|^R)L%9e52y<{uKs?n zkl62GS7}t)IoUvdI*fZ47)~sl<5JAiOYdcRu;_K{5LhrLUV#xL2NY#(p(}*h+x?IS zeUE9L%d2H>p9$vaut=JDbf#;4uY2`Kjb5`g-=W%epD^!*Jf%Z8A)}GLtVXkwTVKcM z2`;X;X^%=BS^|YgY{MrMj|2Z$MCdHWwbIB~V-&6VmWvegHPy+yRkPg?pMG?5e_eIP725Zj%a`^w><;x~ZyE0lW_0jKUst zYPN`=uQobcFY;m_Uf~XnW-Mhzt#59sVAR%+qVzTU8ymv+eaOR#S~P8O&x2WM=QQ)t;}ACUykYG0By!q0nbKb~IQC z3Ukj>rtinQ|6Y?)Kk|c^BJYE()L$vTYr*=K=|c zN~arE#%KxNmuRVHdxlq&nc8c1CSCeb{_NDy?vfGT8%dzi!Xi1p$k)T!%ziqmPw;-@ zF%Q!ZV0_&dLiGs~HJ)L^Fk-ZRrXSkdD`?QTqVG+&P=SNhnzE~1--iYjtC5*UQ^7Q2 z(@*u{&vG;*T;&-E84pQMI=a1}PIcX4qlATQ=TM5e$7N}mHZ+KxDmvAPO75Ke@aeGm zd%>p7100hyjJ9p|D%=bUt{n%!O2*-%+=;@o6<=@Fw#eY(+!ml?2T14xjl#==da@q1@^EYKAjX??&7@U0O zJ?FZ8m%KA!_@;>ZOx{s427gQ{*7Mm)#YPT!Sdl$`8b{F3-q(Ss2*MgWf+o0)K^b6^ zlW|Ql#}PvnBGmp>U#E!s%vt#d3*8HT8vRDn9x!wcqpIjknW%SkG)Vk}RS*GsZf zEq$e6C!)m~d(bC`q-a+xN~<~D}O6`-Q^v^}sL-{7SdeQK#szgk&I!IYR^iNc3 z-8ZQ#Y#2j)C=|dQo0AsXIy~l~i`!zv>r|`;F}~0{hlzO$x8Y8*s|uWYYH3HXc5jQi zN#A@w(|SKcwh=u1p^ct+rMk=qL1>J>M57IPA3i=duCJgJzROdw303y~kvIx{syf}R z%YzT3N`#_8u?s7dgVC(i@c&icwxF>s|UGI zcog@7yoJL-wmpS!o*pK1go_IPE4JPQkBqln!`L-t_uUXVVu_MciM$hYT+RWCa59U?GmGT1=tzSg)>njZY+}fSjzk7 zGH6-JUa*8$;b2-!^tuFi1=+H2V@Y>JcP?xsh*8lH(Ivp-Qan?M1t5*(GXlJk04HJf zH8Wd;Xb}Pic5|TXO|p$6!S<$6z6z zfV7+j27gLjk~%$O^apd@(zWfL|7xDu7h%=gU&S!>y%CklmNf40xMs>QLM^t%4)cia zEh}vZ5uS_WwqF~%{la<6`0^qU(IE){ps^w4=nd$%QHO}@GaAS7JMo~s-A``42kt`})twAoiBoi{E{uoalbpSf|5EBf%V)TB#o1R*qXL(8LM z9#%>Kr&?FQnht7|u5$gX+Tg6T6n`P(sDz}7%<62`p`#UUe*AXu6SIcfkZHO_dRlAE ztIv3H=8oNisR`O+dP9?9g(Iz{ax(3b_jBxJ%a_arC`EF0Rf)rMYK!F2<`)T)=M6{dQN&m+Bg*eY7z>98~7ss;}ITO+TCU51ZP?t{UEdFEn_v{U)IG zkRZ2ATQ=nfr0{N!`v!Tpmu;+}4z+f1;)U6`SiyO{oltjs-2i}uv^NT7?ErTNTEXp* z&XTPA&26keq>Uu2k&p&h10@GXAeDVx;d;KB`qsV<)?zlS(o*;m-q1S$C%8Kd=<0CgWc`B+y*vLs%*P7+qvGx$$!e^j4U}_ng#!h71$n_B1#hG$KdTfzP{P&57OE?+ z_&12VD@j&_yE_WX$LHnc#p@-&>*8w12N4q!;{)^a@$-Z3G(c`X&h9X8kh2@xZ-~Dz z4T_eubtF5Jz4f@Ybw~EUX}TkA|4G)r#r9kBC!K!{PmJ${F$A}`7M+dtIC#Tsb?{c|eJFCxaz&uGLkTqDy8fFEt68aYiEm!1SRl*$qHLBlGHg`}~g8agwFyT8CxBwg^XeA;Hg4u`( zf~;)80@nP3zc)EV|3KMTLls?IonUv$$^zvk4LA7lQDMLPUjyg#{sC$Ul_~;jV6XmG~PI0_Nox z{3H22EzrAU?udo`uG2e!KjU|~K;>NFFn1SMeHRx;N!H&X0e@TmDQ}>}Uz4JYbi32= z`CajUt9d>6)4#6%x&<7Oe?&mwpR$F*tp9T22J?j5{Bd+=_m{{T0duy4-`(GTE2zJZ zBmXaxC1xvVEg&ey4}u7Z3V{S+Vxk~1F%d8bEGTGgWepJ(5rPQ(JGz^Tt-BY@6)t0U zm+4(LcLnq(8zAQ&D!Kk$+Y15z%@dd(0s;$zz+(Ck2oxd;6%^tD^FzU4R=)rF6h*7+C#Te zQ<4V&N~78M@2>Dr$|i0A0KvWA2O8jQI?df6j=PG60?v0FG+ajB3Pf4#-4Lz2g0Z`t zi_`CA0pQQmD%=L>g>**%e?P4n1rbvN0GM4W@-q6$J>Qd&j`{`^JtHkH8E=L^4y*t8 zR(JO?PVTodAXQNzyN@KwA*E);CAYE?{Q3$aV}-)&+{B@dg0S1aCG8}|BqNelH7Unr z0fBT?sa4GiGv4sKEDbKCY~zrB<`<~LV-{SB-S{=kx3TE9B#9%_eN4I6N6V9E|D|lI z0FM{Pe;0*oTIl|2Olx=WG3V=dL^4n7q{g$SprLoKov^%+pbF^lQl3wv`x$9-l*f-p z@T&U>*^?AzxhT5`@uG(67T$rAQuq zVUr}_VYF0^b$4>3u+Fi(s+i0-7HfoG3-n=ybb3k|?4|id_ zT5er4{hl1T{_Fs0(Qye9qtdi4fM_`Be&uME5JehPnD6)xHnv`LN$ca-V7KdXe49wuJ( z?CmUr5x0|{q0!FlKj}~EJ6>oh^jry`KXc*W%2|EAU+`|ca%y|3yS5u1v2+{yO@|tJ z%+lwDFS3;VnWr3@9FihXs2-l(XL2vC$%1Nr(Squp3xm_F$+cGesCaOz`2vD!tVLh# zdunssLpF^jo*IdF^+~=N9V@LG7OI%vQ{56vY<)^Cxt{CX7|iX7>C*ein7RyxJbfO! zG3t6VUekGO8;`29ezMM;mFJhl@6>THmXWdy-K6-e=IT*y{21zAFXfrM!Be67Id;{X zZ|6`x;5lc5GS6Y((mfMafAm58mhO{TwviM5>c!{f#bSla52x)O5tUTSrapZ#ARgEz zjDuc&VdGi>nVMR+ml8=JYwQHa3->(aNc0t=%w(h%AL8}xb$~q5W$K&R72mpQH~~m+ z7Id0?gZe<&ezp!EZ{&u|;R+Hz=1I(Swffl5Dyqb_)UWs2dYKY~h0lVY+q;kI zCf$}+CxEY2ZX1mT6I=<9qqIX@DR@aglaE6dgUk1M~4?s zpXjf-F*$tNJsXD8kom2_*g0!`sSErV`~SCx^{;R&81vp|#^CVUTN?V5L8*Ehj2 zYvI1|_!)ZhkgeBn!bR9&mu*}^lY@G5n_Hb4KT=;eN6LOEVIV_7b#)}$V)TPn=1R>8 zWJcP;tVHm^0AtXsC|vag0L;V+#4bv8`1Io=^IWa?nu_C-?ZXqxTu+n8>IRhhvDhI zX}HF5zQ?+UPpGe=xKoF?b|A~~X%+Yv@S zLb%?15%u|WbQD}(4>sYLsjQ7J)?<(lj*JvVkg9t)>KwR@c*SqxYRSUin8W^H88hnr zOz-B_@uIcv?SSp!+0E8n&^D}V{P@=RdjE3lW_O;dZJbx4)72s1`L1xHU*6?C zdQ0uI>LcW#oBJ2beKS#WFQ07~e7caOsKy_i3G!yDemI#wSbTN8=um$!K+KOY(t) z#>b!#X6%b`p66i?Q8Sw_`8z5OyzoxJr4;_^GdZ^XuiZbh&n&v%+~>IP^zJlr-u`%r zN8WBIdontnFC-t(ysm0z5vDRyvK*e#x3X;Ie$jI9%(Z1WHGN&{NGNk%v*VVh^XB-- zKNDI~GB|tvhP8i_e;q?M@r!pn4d}3KE$tBDm4ADWYv(5_om7+((a}`0?_Of1Btrq8 zU~cQ=LL4V&%+$8SzPW+SYjaWEO;QL>UewLsolZK02E+{Dr=B!pf5xU+(K(Hgz9y0R zS#r7O>3m<(FA5@MexG z(cn3r3S0SxjtV>M2dTrm@-1Tr80_bk2>_`3306O6yuX+|7ACU2N51^R_7Yct+ip|78Dy(gMl0>d#R$zpOl1j|0|1-YU(Ii0-RcP~D4h>$W$oWwxw;$=CCX z5-#$%6?-4g??`RAiZe+OKZ}~PI0bztt!#3w;(nQRJrW#5sf=Yt=e+fnYmJYuXvdif zT*_xlMGi(v&d%m6gq1Pnea_ets%^CLo7vu~75(s3!!7DoxX^$@XD~_Kv|Y8=BIYIr zxyL&OI+gm<67AIfjbUZlO9RTKyd?k8a)sracfHn>uBbaK&Rod4TCFiiU6?_)F$ApE zp48mZO*$r#bY#2rUq6V3*4@0lRBEdUptW4pT2tqB%*P1eb0A@NGuSKCJF(u^hXcJD zl28yg++yR7U++1t>e~okn$239v`EyQkHU~ip2>jKnXIeu)3=@~>)l8D35=hcWL7ql zAH=`7;;w`FnM{8qqL3G=X=x7fl!$woL{zZ#T{+A6$#*0IW313f77)|NfhS`qPo)p~ z;u9-p)!Q;F^WrjEaLEKkRp%Bsn(`JkJhwkbzL(*)V(YE>9wIQc(4-+W9E}uT#^G*e zlg!;BuCd36g>Dz<(xEW+R8#9a6j{YsiRG(`QPPnWx$0=6i-IR>r2}hYwr9u&ubgZo z0Hv$qTXPx(nhTY+ovAqXjL4IzK*Dcow#znLN9@iz8!7IV8EWu_h!T6N3cN!ISGuOO zlsXeHT=;)fzO_Mh_u_GLWytPgn4GFG3RtgZR#YiXNH4X1o6cU%N5(y#C_t_dkSs2% zO$8L7BI@N6DT)|ryF4+wdUbxLn$P_zj;|AmbcHAd0BYG+>k-I@4-+6h&K5YI#-U-x zlR3%`okgxT8+e$BB(_tf0vGJ2!-(;h~tiM&-y&wNcM1$l1t_p1|fz{;cSqYCKfojb&@xBFEjx?a9}y zfqCU9u1sRX)ij-f5$kmfJ9hB!1O02E-Y@&jVzXhoWT*+IiAf%*k)k?~h^emBH`0@& zyj;z9(CQV}BeH_#q}MNc@xG5CfsCcn<=KNgnQ0i~rQShev_!R*MfC!l0*xq&X46@Z z`J!DdwGz(v^8s1Zai(To)p4q_Ty4Oq8E3MEzy5cOW? zQk>LHa!5XT;cU%Na#5ytAtiK&X*ef`!#IM2F&nUDTvcX0Tk%o zqrfbVJ*Sv9B_r7`kJd%#oBW9YrU*jO2|Dt7-P(IoE>xOf(fJ48g(ECh^wFw4YZ>yl z2lWgYck8=!lp^_DSf5xj#y( zH6rp?^s3lwnJLUF83Acoepn9;qQY}Ir;JeA>lnbd$ujr&B9Ebdf>>h>7AkABQYwbL zB0;jYi_gasqmf1x31c4osB3#7=@WVSZK1DB@4zCh)r4~YyKI3mYOQC{N%RmweNgU> zR1sYZJd%+&yihWYY0TP7J^9#5IO)5oXQ~+qvDmU@z%ROYYM$B51s9}B$i;ZX(Y3TQ z$THV%Pb+}O7)u)W3i7WNkfk!NQ!eWS)=?VeYS7G;GDQPuw8Rpbe&h(gqB)p2l47@X zHzRolxhs1inBkGRok}yaIvx5UHRc}y__H`}L6h$%qO$D9~B$xGPj1m$bvrw>ZwNZ%>oyOuJ zuzSob*;Tf~cBMnKrv3~0(_Sp^Ap`fI;_JZd?x*8E(!vBzM$Vq3+cNe-74zEgf*ACaOty?_J_|_%F59aU$ikQnk?43y@EY!vR8!o2cQiRjks2;HU}PP- z=7mJ!Js=!J7r*1rfuA3E#I>ZDTAx=@WQHCg))97EmCUEAyz?pp^B@c$Pp3nb=-$Oj z?4Yf-b~GVwix8I(rZj2w@|9LD1~&7g!>Tz#A1OgD6s#@%KG_-Co1sxoT$ipAxng3P zcV|FIgmVaGn|Si}nb9jHAH?1~57;`{UM_#f%%p(2uV8BG$9%kKzuJgm8#&$i}7ka8aNKHbpuYqw9wH zW^tvMR>=bmGny{XwjPJp5ej?^9A`J>a?kN zO+A}rMA^8B;w9!r_?x;eo%1t<jJo1Zh`elK?;b?g@TWY< zd1!@8I=OLZW1{bv^0j-zW>w1KUU~h;Sq^;wP2Ql);s?K%&<4)2-ipR_BehI6@CvZH7G%h*ky|0 zTSGb3w|GI`*>@bvJzsPoE?s=ZAIRM^+!#t~ykg2#9Ri<^=mDqutk%ouuxiDHm)oRC z4YfwOiTbZ)={wL*dQ5!ZUJ}qg8S>Z&m9&w9l2+hKtG{Q9X?aARc9r0{3fg<|X?Bniok+TNoo%fh6u+0X^`lRnTmF0w+{l{(T1G z!qVFz7XnkMxCNay87$PXyl=rz`s-;`ln_(^3ABY$AR87eu1;7ggVUV*HkK<8b2>y6 zzL~n={*v5Zpl@Y*twf6jmFk3j=Y~M#I|VP6bp=$mN_f_LflhJC`IyTKfNSca#_k5; zkNAv(x6q#$?{a*9XomiCIT0RN$ua zAFPhY!9`c0AGj+-O}|aF$abJ+9LNT4PiM>WjMh*PYj+;&Cv1GUYM~=TFckce)~F&W zx#$&&^7Np_=RGUCL&bcW0p^Lc3NLy}Q9{;GlTX+Z@xwLkbc4kq;6hI6_J{4~Y9k(N zI^;6j{9H(~jEQAhKf|Y$aZyBzXj6S#J zsylC)&uqFwc73powaGIa%WQE*w8SfvW0w+Wjv7SI>+#K_BX?f5${Z&mCHVk8V>q9K zY|*25FSXHp8fk!mTRB7)sA@Yg>8TmK3pJQ*z*xTq${?aO!l_XaPiczx2RMoherLUy z<(^?n>-aK=1ZCm>BG<;6}+kMOa5T$c!m?&QWbK{tBCD zC_o#dI-5X8B!snZ?ZzYF7zhjk$#K66nM)Wrr(aoKHpS(JgjJnM8Yun&8z?zbOSXk5G%XD2m}M@5Pnm>|3G$MIT~KGk z@FiO;3*g2hsc=r#xZ$O5^+wRekT>s!T~LOGLzLUkV#!7p8Bs1dW#(Z?v3;zG&tr`y zUJJECVQrzW5QgLmG=L<=gyyR!l?vX2_s2o6VAGs4%nF|mE4^x-3dN$AY!LVK+g=$S zoR{sKqLx%{Wilp2>MO0f@2VRL^Wq*d`P*0$SC#1Kqv~ zz+uU;39=A3#c%~G^&vt5qM`gMOz>Rzeg$R}EG9c&O?K?gG0bsGquptgA%Vup+vHZ3 zdsu^M-L(9e+M_+tF%7JOCawqs556e0s=_f}#^7nju@;a#jB{`UHNRlatdw`e{>xm6 z5wq0v0^Xa1D^=sUrQG>p6k9`+nZs;2^|Xx$7>Q`TV6B2L1#Zwy@(1&0LC!jfJgEMX zdnaR29P35X)MEj^ftX{=M(YsPwSY&g+c#vMIET(9WaMYf|A*zPdIJFl%3Nl375tx%t*6vI6) zWr*QBP|l8aq$^)t$(KSNMM|i5uAZ;zqB@V*CB^6*le5^zky33RXQJx|D+s;&p>eV( z(~?`8YbPq$AOu7~RVZAPdNwu5u*FKnh!|@ew>9*>ndG>ya3@pY(8=$F_6$3^(z!aS zoT=;Zl;~&hx(OM6CZ4mQ)D^4OTz_yI$gf+Y9!>;aUd9tOPf6Liv)cp?)xd$@PWpLk znYpuBnh%4lSGmQT+aclQRDYANb6JUN*fxgZk_YGRJPuRF$fWpF0(w@)VztAzJ1Fyp zMD%e=7g*;g6XuISgbn=#vhe+|dFxd*b^{BJ_B5YlHVnb?)ZmTddV3H48;dhXLZO5; zcsf=GLhN~isW%Al7hORH@b%}#p>UWow}meLuLUWtfY)}LxTFkFb2N5kV=k?Eyo4{< zNL9oL&&ZIn3F)(2UMb0>6uGa@pB`#aCqvMqP~LOvyGvvM)#bKT<>pS{J zXa@+@#M1_`e?F~s2(xnkrYagv)96;yaiP`Rs-RG9GXYN&tw_81_5k1ffC%yJX<|wa z8;fB}^iXet!2^n)6k8^6Q1j9a3 zM?oZJ!bl%>V{R6tXXz{5Ll((f)b!fz`N&JEpW=&x7ZxlD@CYZ?C8QD%ZE8qm=Y14@ zbd`TL9@qA@FwDv}1!@gpyoxZnYU0PqsqanC4@7~})+}RtcZe4W8MPVbnOH_^TL_c; z32+e-R_+B8Qyv6gFqRL%w-34u06^H*<#?ppBJ}`LcuPLGvNGbv_d^aU+8q)OH{@5J z>5GmRnx(=~n>qv$qCsD9%A8dgRN6ykQ>Bb?Dj1tf%MK^MKbv-F;InC<8~?%MLLj0K z?P5R(Ny~tnLRFS~n-JUAv5Euf2Ge~AAu)`0!x1}r!`4a?r5Y&Zs@Co$_ZYO9a}8e4 z{JN09RR>f-6)k=JB=5gU$q5fr*HON7Jpj*{WzS12 z$MC8hX&jZkI1_3I63|>x$Pp=DUsW8O^1Nu5)`l%?c-dU( z%4R9WYE&ubxv#Q0rl!(_ioL6f)>zCoU2-(f@%Lo*Gs#fl%HOGq6m9I9bJVOpD4ml1F+Toc#0J?%Mh#+OEoUvm3$z|Q0N?^P*ZZzzNga@ z0BHEHq+?tz(x`{_4o@ivuWWX87`G@GjtNRTi}D@#ILFgx#n<%vX##F>?4QNlkZ70lgyIrirAz_v&c3QM zrIU8qKZVhTvEuiJ8#3%{CF#jzM6OpXUdt(%?AQ2d?dE?ivMRB{`%u(yJ!cgXBJA!n zjTfFTKlJ>vRRu1Y3}=TX&dfk0@AF;_!$CPRKZ^8^q;_r9Froa=&!2fIv0fAo$Q>G( zp(*!YaNMnnxK@+O&c0Vv2tazr{T7oD)*z$z!os(qoR}MK#Q1;%qY*KnaRTk4UR%I{ zP}v!d4^oaquXhYBYX8t>B3LPa^G)ZqsfH4fG&YGPEWr0G_|2k!1|GcNkW&q@_4Hng zz;)6?Y(gs$;wZf^ckKQKmBts}UxsyaQ>ZwH=d3T+B zKJ~8qPoNv>vx=3f7eA3)sAItQZkwK+0z_HtdGoN~T?X0@=6qLS376cCW7 z&`wn$S;g#eg&ZnkyKo%Lcb-vzr@5XLxb+tpv7&2%^TL^Sx;dD&8Fn;D&5maa7RWZ`{4sD@T>2hqQnYBq%^21XOFBrxLGfNFXctMG#^)7_eFP zt{d!VQOe-_FqOtxI<12`tSyQH7c}|@?R5#TL#&aUyxvgm~MiBUKTTY1?OJPdp^_1M880zSZk~b|0-=fO~<4?Za zydOKLC!??r)4-5{vb@u17^P7l951FIHy+G6rAxADdSl#RMjYnfMSF!)s>m5l^z#tQ zvG%3w#LP1LE1WQ2`pC$4Qr<;JQ>6G?{f>+u3t{zY%QvUWYbsyUAZ&&vmi&9W+iuY-!>QRgtPl*ZI#PXaEU@8OilCU%rfH@i-u|H^yIa`13;mQ2D?@Qtdm>xWh1&mS7A)~L z4dD8kCy_6IcLXOfcTOovxJ-yFWG<%&_binE^E2T2!6hakWU35NaX-!*f)g}tMII7d zZ%(t}7>7?C!s8gHjBltlR`)xh2(unD*k%{2EGU(% zEHF@jG+Yz&eBFc^*8(Gm-GKUtz*!5l_Ii&LLe$IwpbrVDJvb zCg@+Q)jJuNa*gM9@~7XKlxbkcF&E8o!S!gwW*Xpp+@2z>C z^p^cLL^mR}CsEm3s2!Nz)Sy&}seWh`_D0(-_cn#5WIiL^s@BUPq9m?&NtUNq%>Lmk z+Qk$31A<;{U@qaR-j});tl`InguKf?H}8!|y5n^2X)tg#`^HsPxBeEOZvp2AB z<;W)>zL=1?leqx^4h3v>d{!y1?UNZ*bx)4tYQIM$el(G%uqLH(e%A&w|ZsC0O zBkj5>+YNRsMa=GyuLGrp{`tx9^sT$~Y~WyhU^z7(^<`9o=h;ZBICcY&1N1G(cFUc~ z$E7@#C(V>{vYXbnp$CYYO`jaHe^Iz6+&hhv=j}PO^alLg;2<&_q6oMQeub&5DHRM_ zc}Z%lPDjXWtmN~@=)W<0`oITSRrsIddY#~*5mJs|fGV;=XjZclgspT84&v1<%5K1`_5|R{+ zRsUr@-);gupzbm+f0q@&_v+&6?YJ#{4p+(*6;kbslt4q4Q~&@j)LvFrOIcR-ALlS0 zPhaE&B#JA2lp^nYu3Ie6f@P4fmQWz_6o=4#iiMy+ri{?oldJg$dKO6%0kL9t!^+Bl z<#c!7Q$u7!X`EYtI~1zpk%;$@PGH>Y&vTJd+(NX43e~v3# zjbZ_?=uJq-1dpibkan!^Q^IvD(fg!5G)pR08`qHF;6&MGSj_eA_v&VU=zftWG(23Z z;;$j*Fo_sN)m-Is+uyuV+r?XGBfzUjPEVvt@y>&bHJkK=&fRp{W0)91S((!)zW?IyipODXhlgj0cw4D zEP1&I)6XHfNTN~Bzv*+A2oIXzn0O~TcJY3lPcv|Z*R8nmjttZMI{AmMNemTSztT%aO8;xxZ`MINtzb#u~yenUJQ#c7PxwLr425HN_3gO7uYUEasui-$%6 z4I~D!wh_^hQ}~nOu_aDp=i%Wd!pZ6F?akrM%i#*K<>VF?7UtyQ;pE|Ae?+iDU$}T! z_^`V`X@61t!665RT0!jHJnUUvK)*OGEL}Z4#A#?A$3cIRKaN_-`#g63>EKuUH$BwD znp63)!S{GSj{r^{E-oQ36c>RNx<{F2eu-r4Q9#V_>Vk=9m!;oLkSPQNkMR-9lbu=8Ue&_`zOf5HDcM)jA) zzueESk^e1_M|b}o{SUo<$MRcO5jj^Y&tFcJ<-}=z#Vca%YGrRN^81q81}wC9y;$j!H5)u^T=CQKm5#;+Dm9h)e!@|W1{EO<5oWuT+2Mo606B4u( zX1Czy<74OJ=CWoN=Hs(rw-K`B39&KGAFaG9V7wrB=>yI_d$^N%gAkgo1Ld3%Amx|&vPzx`x_3!z4)bWoYD?1Ap zTkzw8{AV@(qu>7DWV)b$m8G>M7dN|&fUwPDrdwOF3)u+qvp;4LkFW*5l`xOhAG7}# zJJi+2!`lJ^mbQJYzsGrgtiIp#3}X5tL7D$G7;ii9uk7RE;b!L&War`5k3BYFN` zaDTJ^Gg1Ff=6{F%VJ++G_TsVD?L0KSUH(V+{|5L6gPOh7V_CWWPoe({MC^A->O;X0 zSy$&@E8gFNmUjBH^^XMkwRMR&Jx>1PO2%#twz2SZ@}QB>wSs^jecHKt{2{WF{hu7a zCI1JB80Ww9^oPaYw#!G;Kl&cG;m2K_^RI3F56)l3{D1TB&l32*=>i1$uao~s-~Y<> zU%CDx1^y%Of3xeqa{WgN{72ybX4n63a-se6!2sCh@x!P0ToRMq(l{1>f!fF!Iq2Fs-yB$0 zkD!qQEDjHxO4@!vj>_Uw=s%0KI1$o+9l%bq@|sE5pEyq#-Z+c; z^91IiJ7ZZpwpY!2fc-tj+)Lw)qyxTtX1fhpbC;CMsqDZ{ew-f;g@JgBhCQE2ZDtU6 z>WG3&@h0{-Vsirra}cMU5HH9>+dVn!KPu|ll2#YGrl=fl{_=Dy+6P#~9vcW)qB227Y7);Al|m#d_BD#FB? zJ;(61by6b_&}I)>o#$$ix%~5cL0p3HtRw;H&VGwQCd43pG@Rn^XM<`%P~}J|47)|d zr|cdox*(G;?`cwN?+u8oWuVPpTo$lS1p!x2wQSH7+zY-i*$7}Kz@b!P=xoBXyl`tk zx$K#^=h6d_PENY3OM_^PN`S+53znI5kbD{@s6E%*cD-T?jsnpa}%vPr3k#ZqMG-bR@3R=t$#;G@!5EuQUM0*xc4YAp>ums$CooeTOM);K3y z@XxrKXx_H*%vFXb2W}!3q`%^NVr`O0wlOdYd?r4vV%kySiV$eTh9-~$a%MJu#*!|; zK*WQI6FIXKV~D7hBLPP%iA{uDdtowYNH%31NZmyTmlUWUtOZ8V*Xg}(xpFN-i#@eW zf$dS|W+J}FJpCGI*74Tr`#P$L$j1#gSI6oTXAG_EH>Ex3H9H+V(sWY!`9-(hgV zz!g)pl*}NTE9d#u@M9r!_pu5cgcYz=`1Abc^#tg40W_dyUj#IF(*>oS(9H|f z>_i_fUa{M~p$f!_DNOZ0+VbDlx6wOdmz&r0tgv9vt%wBIaZX*NaEf|JWJSx zFVW{eA3lfrEej6q?!HaLU)c~gw?`VhL&j)mTSuWj3|L9e8CW`eXb6aV9+`_~!pAdv# zB%(Sp&3It08(qKTSfub$zHp#@@4I%S3em#kK$|D0z2?q9#fRcr>aN<(C#e*z%ja*{ zI+$9HUWCUy(Q+VJcpzdDow4>>I1w+bI;bRfxVJj^u+%=3Sl}zGpHVFL($Q6b3$iDu zz;VT&7_Vr5TLdf|=vGC|2Q!vkNUv$F$DE7)Y|9Y~@V8<_Pfkw8!Dn0*Kuy@%Cy=~C z!K8#^9G51+a%;l0n?>dGl0_!tZwA(20*m;|nh=b$2$e8HY{EfFv=auXULMHSN5C4Z zfCzJw*9H}Y-`LyjoZ}Y7%*7~?0hhY6y>w3|Pu^sZUc!gJhSLS+Pr@6aX@StR=aHVo z2CEYr>32Q;MquQ!Hthq6+qJo3tCZ*=u<=j?&Ex~QF8AQni^xoBlIEv&Ux3q7=q__`l`qMf zIFL>+muvhn?-aPtbQ6mF4+J_E;Bn{XEXjng^9k8is!BpO*Z6V_HVIkhJbOnKK)Gb5wG3QCJo>^VbXgTyL6_&m0Y2rO)mrnq-n`-L_fo=VJ!EHuq1eO?eAQl#}ijQR89PCKRc_S&0m%rPe>r`uYKoP!NhruSTFQN#yLN zA*7v1Y5lwGFFEz%*vv+~g{k?x$+RdX0^5rSag)8H2A*9?P;B85yR~crwDh b2YC4b%V(@KLQ0RH)d0%!8gf-K=E46D2OeY( literal 0 HcmV?d00001 diff --git a/Resources/images/vr_pause.png b/Resources/images/vr_pause.png new file mode 100644 index 0000000000000000000000000000000000000000..af2d89acc98945412be133dafe955a9d62101730 GIT binary patch literal 6392 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 P-(Lr$wxL#?h7;yLPY<~f literal 0 HcmV?d00001 diff --git a/Resources/images/vr_play.png b/Resources/images/vr_play.png new file mode 100644 index 0000000000000000000000000000000000000000..6de2686f096bad08d5c6451422f224e47fc41458 GIT binary patch literal 7003 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 literal 0 HcmV?d00001 diff --git a/Resources/images/vr_wave.png b/Resources/images/vr_wave.png new file mode 100644 index 0000000000000000000000000000000000000000..26d7f1be77b3dd16a83756abd4bba75487a36c3e GIT binary patch literal 15224 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"; }; C61B1BF32667D202001A4E4A /* more_menu_default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = more_menu_default.png; sourceTree = ""; }; C61B1BF62667EC6B001A4E4A /* ephemeral_messages_color_A.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = ephemeral_messages_color_A.png; sourceTree = ""; }; + C622E3E926A8128F004F5434 /* vr_stop.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = vr_stop.png; sourceTree = ""; }; + C622E3EA26A8128F004F5434 /* vr_wave.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = vr_wave.png; sourceTree = ""; }; + C622E3EB26A8128F004F5434 /* vr_on.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = vr_on.png; sourceTree = ""; }; + C622E3EC26A8128F004F5434 /* vr_off.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = vr_off.png; sourceTree = ""; }; + C622E3ED26A8128F004F5434 /* vr_pause.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = vr_pause.png; sourceTree = ""; }; + C622E3EE26A81290004F5434 /* vr_play.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = vr_play.png; sourceTree = ""; }; C64A854C2667B66900252AD2 /* EphemeralSettingsView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EphemeralSettingsView.h; sourceTree = ""; }; C64A854D2667B67200252AD2 /* EphemeralSettingsView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EphemeralSettingsView.m; sourceTree = ""; }; C64A854F2667B67A00252AD2 /* EphemeralSettingsView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = EphemeralSettingsView.xib; sourceTree = ""; }; @@ -1951,7 +1963,7 @@ files = ( 61DD7E1F2372E88F001BDD01 /* CoreLocation.framework in Frameworks */, 6180D6FE21EE41A800AD9CB6 /* QuickLook.framework in Frameworks */, - 61AEBEA321906AFC00F35E7F /* BuildFile in Frameworks */, + 61AEBEA321906AFC00F35E7F /* (null) in Frameworks */, D37DC7181594AF3400B2A5EB /* MessageUI.framework in Frameworks */, 61F1997520C6B1D5006B069A /* AVKit.framework in Frameworks */, 249660951FD6A35F001D55AA /* Photos.framework in Frameworks */, @@ -2285,7 +2297,7 @@ path = LinphoneUI; sourceTree = ""; }; - 29B97314FDCFA39411CA2CEA = { + 29B97314FDCFA39411CA2CEA /* CustomTemplate */ = { isa = PBXGroup; children = ( 8C23BCB71D82AAC3005F19BB /* linphone.entitlements */, @@ -2447,6 +2459,12 @@ 633FEBE11D3CD5570014B822 /* images */ = { isa = PBXGroup; children = ( + C622E3EC26A8128F004F5434 /* vr_off.png */, + C622E3EB26A8128F004F5434 /* vr_on.png */, + C622E3ED26A8128F004F5434 /* vr_pause.png */, + C622E3EE26A81290004F5434 /* vr_play.png */, + C622E3E926A8128F004F5434 /* vr_stop.png */, + C622E3EA26A8128F004F5434 /* vr_wave.png */, C61B1BF62667EC6B001A4E4A /* ephemeral_messages_color_A.png */, C61B1BF32667D202001A4E4A /* more_menu_default.png */, C61B1BF12667D075001A4E4A /* menu_security_default.png */, @@ -3357,7 +3375,7 @@ fr, hu, ); - mainGroup = 29B97314FDCFA39411CA2CEA; + mainGroup = 29B97314FDCFA39411CA2CEA /* CustomTemplate */; productRefGroup = 19C28FACFE9D520D11CA2CBB /* Products */; projectDirPath = ""; projectRoot = ""; @@ -3408,6 +3426,7 @@ 615A2817217F280C0060F920 /* chat_list_indicator.png in Resources */, 633FEEFF1D3CD55A0014B822 /* options_add_call_disabled@2x.png in Resources */, 633FEF091D3CD55A0014B822 /* options_start_conference_disabled@2x.png in Resources */, + C622E3F326A81290004F5434 /* vr_pause.png in Resources */, 633FEE051D3CD5590014B822 /* cancel_edit_disabled@2x.png in Resources */, 633FEE5F1D3CD5590014B822 /* edit_list_default@2x.png in Resources */, 633FEEB61D3CD55A0014B822 /* numpad_3_over@2x.png in Resources */, @@ -3418,7 +3437,7 @@ 633FEED41D3CD55A0014B822 /* numpad_7_default@2x.png in Resources */, 633FEEE01D3CD55A0014B822 /* numpad_8_over~ipad@2x.png in Resources */, 633FEDDC1D3CD5590014B822 /* call_start_body_disabled~ipad.png in Resources */, - 63E802DB1C625AEF000D5509 /* BuildFile in Resources */, + 63E802DB1C625AEF000D5509 /* (null) in Resources */, 633FEE2E1D3CD5590014B822 /* color_F.png in Resources */, 633FEDC51D3CD5590014B822 /* call_hangup_disabled@2x.png in Resources */, 633FEEDF1D3CD55A0014B822 /* numpad_8_over~ipad.png in Resources */, @@ -3453,6 +3472,7 @@ 633FEDD91D3CD5590014B822 /* call_start_body_default~ipad@2x.png in Resources */, 633FEE401D3CD5590014B822 /* contacts_all_selected.png in Resources */, 633FEE0C1D3CD5590014B822 /* chat_attachment_disabled.png in Resources */, + C622E3EF26A81290004F5434 /* vr_stop.png in Resources */, 633FEF001D3CD55A0014B822 /* options_default.png in Resources */, CF15F21F20E4F9A3008B1DE6 /* UIImageViewDeletable.xib in Resources */, 633FEE951D3CD55A0014B822 /* micro_default@2x.png in Resources */, @@ -3478,6 +3498,7 @@ 633FEE7A1D3CD5590014B822 /* history_missed_default.png in Resources */, 633FEF121D3CD55A0014B822 /* pause_big_over_selected.png in Resources */, 633FED9D1D3CD5590014B822 /* add_field_default@2x.png in Resources */, + C622E3F426A81290004F5434 /* vr_play.png in Resources */, 639E9CB01C0DB83000019A75 /* SideMenuView.xib in Resources */, 633FEDBB1D3CD5590014B822 /* call_audio_start_default@2x.png in Resources */, 633FEF1A1D3CD55A0014B822 /* presence_away.png in Resources */, @@ -3505,6 +3526,7 @@ 615A283E2180A2560060F920 /* invite_linphone.png in Resources */, 633FEF281D3CD55A0014B822 /* route_earpiece_default.png in Resources */, 633FEE4F1D3CD5590014B822 /* delete_field_over@2x.png in Resources */, + C622E3F226A81290004F5434 /* vr_off.png in Resources */, 633FEE531D3CD5590014B822 /* dialer_alt_back@2x.png in Resources */, 633FEE3E1D3CD5590014B822 /* contacts_all_disabled.png in Resources */, 633FEEF31D3CD55A0014B822 /* numpad_over_background.png in Resources */, @@ -3740,6 +3762,7 @@ 633FEE191D3CD5590014B822 /* chat_send_over@2x.png in Resources */, 633FEF181D3CD55A0014B822 /* pause_small_over_selected.png in Resources */, 633FEE001D3CD5590014B822 /* camera_switch_over.png in Resources */, + C622E3F126A81290004F5434 /* vr_on.png in Resources */, 633FEF401D3CD55A0014B822 /* select_all_default.png in Resources */, 633FEDF01D3CD5590014B822 /* call_transfer_disabled.png in Resources */, 633FEE351D3CD5590014B822 /* conference_exit_default@2x.png in Resources */, @@ -3772,6 +3795,7 @@ 633FEEFA1D3CD55A0014B822 /* numpad_star~ipad.png in Resources */, D38187B915FE342200C3EDCA /* ContactDetailsView.xib in Resources */, 633FEE921D3CD55A0014B822 /* menu.png in Resources */, + C622E3F026A81290004F5434 /* vr_wave.png in Resources */, 633FEDE41D3CD5590014B822 /* call_status_incoming~ipad.png in Resources */, 633FEE4C1D3CD5590014B822 /* delete_field_default.png in Resources */, 633FEE391D3CD5590014B822 /* contact_add_default@2x.png in Resources */, From daadc1023ab61a322e72685e6f4ea70474cb75fc Mon Sep 17 00:00:00 2001 From: Christophe Deschamps Date: Fri, 23 Jul 2021 16:22:02 +0200 Subject: [PATCH 03/12] File icons in chat --- Classes/LinphoneUI/UIChatBubblePhotoCell.m | 21 ++++++---- Classes/LinphoneUI/UIChatBubbleTextCell.h | 1 + Classes/LinphoneUI/UIChatBubbleTextCell.m | 29 +++++++++---- Classes/LinphoneUI/UIChatContentView.m | 1 + Classes/SwiftUtil.swift | 46 +++++++++++++++++++++ Resources/images/file_audio_default.png | Bin 0 -> 2483 bytes Resources/images/file_default.png | Bin 0 -> 2227 bytes Resources/images/file_pdf_default.png | Bin 0 -> 2646 bytes Resources/images/file_picture_default.png | Bin 0 -> 2853 bytes Resources/images/file_video_default.png | Bin 0 -> 9006 bytes linphone.xcodeproj/project.pbxproj | 24 +++++++++++ 11 files changed, 105 insertions(+), 17 deletions(-) create mode 100644 Classes/SwiftUtil.swift create mode 100644 Resources/images/file_audio_default.png create mode 100644 Resources/images/file_default.png create mode 100644 Resources/images/file_pdf_default.png create mode 100644 Resources/images/file_picture_default.png create mode 100644 Resources/images/file_video_default.png diff --git a/Classes/LinphoneUI/UIChatBubblePhotoCell.m b/Classes/LinphoneUI/UIChatBubblePhotoCell.m index 4e6766faa..b5a946b01 100644 --- a/Classes/LinphoneUI/UIChatBubblePhotoCell.m +++ b/Classes/LinphoneUI/UIChatBubblePhotoCell.m @@ -28,6 +28,8 @@ #define voicePlayer VIEW(ChatConversationView).sharedVoicePlayer #define chatView VIEW(ChatConversationView) +#define FILE_ICON_TAG 0 +#define REALIMAGE_TAG 1 @@ -105,6 +107,7 @@ } - (void) loadImageAsset:(PHAsset*) asset image:(UIImage *)image { + _finalImage.tag = REALIMAGE_TAG; dispatch_async(dispatch_get_main_queue(), ^{ [_finalImage setImage:image]; [_messageImageView setAsset:asset]; @@ -134,14 +137,10 @@ } - (void) loadFileAsset:(NSString *)name { - NSString *text = [NSString stringWithFormat:@"📎 %@",name]; - _fileName.text = text; - dispatch_async(dispatch_get_main_queue(), ^{ - _fileName.hidden = _fileView.hidden = _fileButton.hidden = NO; - _imageGestureRecognizer.enabled = NO; - _plusLongGestureRecognizer.enabled = NO; - _playButton.hidden = YES; - }); + UIImage *image = [UIChatBubbleTextCell getImageFromFileName:name]; + [self loadImageAsset:nil image:image]; + _imageGestureRecognizer.enabled = YES; + _finalImage.tag = FILE_ICON_TAG; } - (void) loadPlaceholder { @@ -550,6 +549,10 @@ } - (IBAction)onImageClick:(id)event { + if (_finalImage.tag == FILE_ICON_TAG) { + [self onFileClick:nil]; + return; + } LinphoneChatMessageState state = linphone_chat_message_get_state(self.message); if (state == LinphoneChatMessageStateNotDelivered) { [self onResendClick:event]; @@ -690,7 +693,7 @@ CGFloat max_imagesh=0; CGFloat max_imagesw=0; CGFloat originy=0; - CGFloat originx=0; + CGFloat originx=-IMAGE_DEFAULT_MARGIN; CGFloat availableWidth = chatTableView.tableView.frame.size.width-CELL_IMAGE_X_MARGIN; NSMutableArray *fileUrls = [[NSMutableArray alloc] init]; diff --git a/Classes/LinphoneUI/UIChatBubbleTextCell.h b/Classes/LinphoneUI/UIChatBubbleTextCell.h index 062164d5f..e90bf6b44 100644 --- a/Classes/LinphoneUI/UIChatBubbleTextCell.h +++ b/Classes/LinphoneUI/UIChatBubbleTextCell.h @@ -60,6 +60,7 @@ + (CGSize)getMediaMessageSizefromOriginalSize:(CGSize)originalSize withWidth:(int)width; + (UIImage *)getImageFromVideoUrl:(NSURL *)url; + (UIImage *)getImageFromContent:(LinphoneContent *)content filePath:(NSString *)filePath; ++ (UIImage *)getImageFromFileName:(NSString *)fileName; - (void)setEvent:(LinphoneEventLog *)event; - (void)setChatMessageForCbs:(LinphoneChatMessage *)message; diff --git a/Classes/LinphoneUI/UIChatBubbleTextCell.m b/Classes/LinphoneUI/UIChatBubbleTextCell.m index fc3371774..f0d583b5a 100644 --- a/Classes/LinphoneUI/UIChatBubbleTextCell.m +++ b/Classes/LinphoneUI/UIChatBubbleTextCell.m @@ -455,6 +455,23 @@ static const CGFloat CELL_MESSAGE_Y_MARGIN = 44; return size; } + ++ (UIImage *)getImageFromFileName:(NSString *)fileName { + NSString *extension = [[fileName.lowercaseString componentsSeparatedByString:@"."] lastObject]; + UIImage *image; + if ([extension isEqualToString:@"pdf"]) + image = [UIImage imageNamed:@"file_pdf_default"]; + else if ([@[@"png", @"jpg", @"jpeg", @"bmp", @"heic"] containsObject:extension]) + image = [UIImage imageNamed:@"file_picture_default"]; + else if ([@[@"mkv", @"avi", @"mov", @"mp4"] containsObject:extension]) + image = [UIImage imageNamed:@"file_video_default"]; + else if ([@[@"wav", @"au", @"m4a"] containsObject:extension]) + image = [UIImage imageNamed:@"file_audio_default"]; + else + image = [UIImage imageNamed:@"file_default"]; + return [SwiftUtil textToImageWithDrawText:fileName inImage:image]; +} + + (UIImage *)getImageFromContent:(LinphoneContent *)content filePath:(NSString *)filePath; { NSString *type = [NSString stringWithUTF8String:linphone_content_get_type(content)]; NSString *name = [NSString stringWithUTF8String:linphone_content_get_name(content)]; @@ -470,9 +487,7 @@ static const CGFloat CELL_MESSAGE_Y_MARGIN = 44; image = [[UIImage alloc] initWithData:data]; } if (image) return image; - UIImage *basicImage = [ChatConversationView getBasicImage]; - image = [ChatConversationView drawText:[NSString stringWithFormat:@"📎 %@",name] image:basicImage textSize:25]; - return image; + else return [self getImageFromFileName:name]; } +(LinphoneContent *) voiceContent:(LinphoneChatMessage *)message { @@ -652,12 +667,10 @@ static const CGFloat CELL_MESSAGE_Y_MARGIN = 44; NSData *data = [NSData dataWithContentsOfURL:[VIEW(ChatConversationView) getICloudFileUrl:localFile]]; image = [[UIImage alloc] initWithData:data]; } + } else if (voiceContent){ + return [self addVoicePlayerToSize:[self ViewHeightForFile:width] withMargins:true]; } else { - CGSize fileSize = [self ViewHeightForFile:width]; - if (voiceContent) { - fileSize = [self addVoicePlayerToSize:fileSize withMargins:true]; - } - return fileSize; + image = [UIChatBubbleTextCell getImageFromFileName:fileName]; } originalImageSize = image.size; diff --git a/Classes/LinphoneUI/UIChatContentView.m b/Classes/LinphoneUI/UIChatContentView.m index 45882677f..418d3a996 100644 --- a/Classes/LinphoneUI/UIChatContentView.m +++ b/Classes/LinphoneUI/UIChatContentView.m @@ -56,6 +56,7 @@ tapGestureRecognizer.numberOfTapsRequired = 1; tapGestureRecognizer.enabled = YES; [self addGestureRecognizer:tapGestureRecognizer]; + self.userInteractionEnabled = true; } } diff --git a/Classes/SwiftUtil.swift b/Classes/SwiftUtil.swift new file mode 100644 index 000000000..6c9022fac --- /dev/null +++ b/Classes/SwiftUtil.swift @@ -0,0 +1,46 @@ +// +// SwiftUtil.swift +// linphone +// +// Created by Tof on 23/07/2021. +// + +import UIKit + +@objc class SwiftUtil: NSObject { + + @objc static func textToImage(drawText text: String, inImage image: UIImage) -> UIImage { + let textColor = UIColor.black + let textFont = UIFont(name: "Helvetica", size: 12)! + let backgroundColor = UIColor.white + + let size = CGSize(width: 120, height: 120) + + let scale = UIScreen.main.scale + UIGraphicsBeginImageContextWithOptions(size, false, scale) + let context = UIGraphicsGetCurrentContext() + backgroundColor.setFill() + context!.fill(CGRect(x: 0, y: 0, width: size.width, height: size.height)) + + let paragraph = NSMutableParagraphStyle() + paragraph.alignment = .center + + let textFontAttributes = [ + NSAttributedString.Key.font: textFont, + NSAttributedString.Key.foregroundColor: textColor, + NSAttributedString.Key.paragraphStyle: paragraph, + ] as [NSAttributedString.Key : Any] + + image.draw(in: CGRect(origin: CGPoint(x: size.width/2 - (image.size.width)/2,y: 15), size: image.size)) + + let rect = CGRect(origin: CGPoint(x: 0,y: 70), size: size) + text.draw(in: rect, withAttributes: textFontAttributes) + + let newImage = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + + return newImage! + } + +} + diff --git a/Resources/images/file_audio_default.png b/Resources/images/file_audio_default.png new file mode 100644 index 0000000000000000000000000000000000000000..ce5af4fd5a3cca42750cc190deff73a45901539c GIT binary patch literal 2483 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 A;Q#;t literal 0 HcmV?d00001 diff --git a/Resources/images/file_default.png b/Resources/images/file_default.png new file mode 100644 index 0000000000000000000000000000000000000000..f660964fbebc8871ddf082ca3e6d19303ea6c3cb GIT binary patch literal 2227 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

+t4NEn&|n=`GltcGpDX? zW7(Lni}&5rk<_h7H2)e#k@`@X|;%qY}&(;>hDaBqN zYr^nwxvYaVme*S#8nYs4jc#bxRY}fKpyX#xxKNr0ZM7GN;B6{C=ybfb7=|VGrSnI~ z7xl2VZ6~{QU0=%^j_*(UaU<7oW?<&aF8H08i~OH5G>Lghx7$q zN8tiMrsmc#MZ zS~8Sfk73&X!hDgT2rMcXhYG>~e~f~B!cc)Y1r!Ektf3Av#d=4EBD{|~(%+wi+L;=G z;C7Je6DG$+3gG`&1nrFp^YcdF!mvo}Ulpsu^ud4K|1ooH9vKMy55>p=ocl2WFo)V0 J*BW`n{Ri1W7f}EJ literal 0 HcmV?d00001 diff --git a/Resources/images/file_video_default.png b/Resources/images/file_video_default.png new file mode 100644 index 0000000000000000000000000000000000000000..9a3d1a3014bd81e152e8f691b8fd1596b54e9323 GIT binary patch literal 9006 zcmd6NcU)6Vx9$$TNexYcL_vBBodAL$UFjVWAp{7W5UL_r5J8ZlfKmcdq)3r2h>8V4 z5Kt75Dgsia_m>v2-{wkYxT@kO)Oc5f;sx(x2&HHDO%9F+D=)rXJiDki{ghflh4v#hh_k0u z6jm|qxNE)nk|&_F(jX&f#1&BcT+-=Bx&6&KrX1#gAE1uvjJL4eiD=TBBB3)1qj2LQ>P|m%PQQXO2uoJnuiz zHFw4{Ae~y?`vqpHoZphMP^JPn+HTLEZIxK5A>-ikBex0}~A&%DYIgyU3gk zbx#36NjyKP3u*oIYy$v*o{c0BAU%@{%%sNYpV6lNM$Jjj$W6|;Lj(3MD$K$LXXX?P z_rviE48bfips~erDsWYjkhNU_8+<=os=Cbr^=thMxZfHEerA2L{yp0 z@@>BEJAa4sJzc%8tD?k6sUn-XlmcgOadQ`i)8zM_iLa#PJ>*#l)V zT6bCYxL0%apqG-BCQ_2#F(c)&mGy-bd|Hi%M`1~7uk;!h;4Glhw1G0s7w?L0j^y!B z?dtX>n-*rJ#rS0*sFVp@x5kTZYEk6fK;76tGGO_xj`Mo)beh6SkvvE4w!cQsTgMpM z5geRPToSlmDj6+wJ6xJaRJsk1h%n1y{ZRL8@}}C&sg9n^GArHe>TmPGHSSd}!ua_{ zlO9}H68E-%?LHQXuM6&fqFnSD{XV<@^io&oRyrn2HXQPxY@$6!%0vVl0f;Zw$O6)x z8=MXRls@{WPFm=-)}~^-EG(g|5nE3cwN$c#Tpz{r+F!dOTxB7yR~qE zN8z5r-jc`0968)Q`eYj$9@@CmoobG@?UR7bjBpo=H}*b1$)|jN&*A*q6MDBMQ|^sN zxdBTonXuLueirD=3MHk(8YeNgPim5L$KDsvYsP*Sdfnb$g5BndO11?WbrrI?Xg2hnkx5 zXlR|^^kg*;Do^7f?Fr=8N7jbh+$$-1f>Tx%bVdqPfflD*T}2nHp5)w1!Bpw-p-LW< zBuHDQF<|%?=Tl*Jy-H!C^p%K2ACE6Kq5>SV^o{)B^ZcMI2SF-=aJHoL%#!(=8n@91+* zuVOPa5-rjigT9jwwx*b}`%Atq_YiDlK;=C=bDA@~q4hH>!-ad?Qrhoog$@cl4KKwU z;pgq&3H@HOjZ{p6HBO|osg$s8MmX~diV#lMa`Uurg+8jJwh6`OwD!Jj?NaQ+5V^nj z_FZ&c4H$-*)y)M_bZ;iQk0@3(+{!x@`GI1&s#tfFcetDB^qhp#OXo9Edn0=IDY+I^ zR7K@huLye*=RMX7SBo$98j366&o4Yr3b~g7I(QQ*e55-q z4t2y!^orcP5z1?P9>D~n$+?oF1p=D-7kRynubP`Ra^&* zN6TYwE$oEl_sBf+nMlasZJ?s>zHnSkk5ymNoXBc6dm+c5O#GXlbA_o+X8c-0Q*r5f z*kEE7?NVwvRce~gh31s(WYHO3Gpg{ZbNzgKdx;s>BxU<*rwTZk=nl;LeFSK|wC+5! z7nR?=c4Op~l*>BwR4?*+I$7Mc<`Gw)wG*#K{hecLfQx-DwWDlgGh9BZgW zoohS!PR^Te0G^)A7kqXAXZ&g8!OYYPIomJEiS1%dt^+}Qs|3iZP#-pLHM_RL;zWe zRJDiuNY4W@wS&y#5-VauAzGF0_?LYv6_%DvlV8y7UyFO`dEDMm6e(Rgj61ums_VH% z&-6VWN`*yj23fzJ`_8X&aR8vp?POqrGiy}dgt7s_2W@+VVYLjZO+=EsYVv5+r+RoM zWlSxx!!d-Pvoh^oU4o!OckLmgFvCL~ow{o!Y*l&1soTC3qIfoZZu2~g7K$Bs5^}>q zsWkR>+QqEho$GY+SBE}Z*b*E{2k_2@35;z`aN6M7wFRf{ce|xWgJL_gk{2wJJA^$X zUWt$SR*0M8$2~)L0e}OEw$XRh*B1rAEfAmwmt_h70>)fOEA(%1O)yRdAqU98Fc}m^ z2pPZ%hIc_>1@lRGk{pIm{s>FJIL~kKDljhm8-}Eh0Cf+6VIwFqU>pjDZJ=PHf7Uw; z#-X4wnjbGMEZPZ&c7X?DaPDwj{WB&23@j(r^?OLlgv&@Gq$OqKK#S5c2&A+;5}^b~ zC?F9CBtj7&1Fk1i1KgkvNEd+Q{MlYG`N#9mnn*{2pKT&l_C-F0J3}80U(boYhA`t{Xh(mxDKtn}EO+`sV zO-)TpOGC#1J;Xpy&%n*f#suX(%y)$MFboD4kQ9O+6X%D)gcU`^r4e#+a(qI{YDzMy zlCp9#BqI=7T3QBr2ChSgxMYsPj>`OhKg4E$g$5{sfMkyWWGoPJ76|b*zz4RI67u8u zNd-)kkyB7oQPa@U(Sv|mW>Qa)lTnaUQc{4YSV%Zn4p6XAvK~d8q+&C5qCVyimARaB zpN3zv@&&uuhh+g-XY3VPIu1@Q?!(6gg@i>!<>VCE-)xKBsA>mwW#aSH)3LwZ{JBtO}l$9JvT4E;6Y(gaYJHMEJk-t+NOZ(slCfx)li6O-Sjre|j7R#w;6H#WDncXmmO)P1b^ zLZfis+r1xK)+0J)R;mNtg&fS?Qp1i556bD)CDkVeBy?5NietliOem_2K4v~5PYx+6 z%8jHG{tsg5T$ZG-4S1X^A08^Eh3N{ZUM0v&Mwe(845=RFy_Z?1M}Y#u5)9ttMv82A zybM#goUg5Rt$zvo@y2JPHgk-X>UnZJ_54}2FoVa*9Cc}T9So}0$2O){%2D>qoxM+L zN8bF&0*!G++x*Qw_>ru4!PB z73p>>g>y41ZU1112z+@#kbl)7e!G3OeL>(>OV;pYrE0Qq!08;ur9I6K!leZwurx*l z4y)IQKc8OwLLe}$l(WVHi$vhhjJ&@fzY70v(Pg~vO1&T69I=o?YVt;z?#T?}c$<5$ z+wsp#k2Dcd?pPGRpa}QO?AdoJwjGu0@8=AQUMv4%YIxy}{Y`Fu zJP^cv{*|zgk&IH8w7&+oWo6;;TmJ{=Fs@(E--iwPFvo6Aj?^CRicyL;BqN{+9C}2*O#id+M)>Q4a>~x{~xH-7Z7rUpcGa zTZRa!BnTW#obv}=#v*a36Z;K*UEhWwMO)l|z6H>Ca|$gw?OauZpVD{|tHq6_K0OvR zc_|Rf$28|13gzVB3~FC2S=IMxh6@#UAsIb;?^Cj0*pWoqszA7QNN^kTe@n%v7PG(5eR$eNnj3#+w-VA_|!Bq zw0}3EcY>>ncK-HkPa{LNxmB4j1y6n9y-bu)0if-#jMr#YWxxiQe9#6IE_7t5&==M{ zPUTp=yBOb|8R-{NPG;+Zw@$Uj2n2~QP9Kk%Oumr5D9^M*H4@R!Nzi$000`&C0>b6b z^_C0rHzf!2)0of3&{)LZxpEh_t~#wY-{9?ds!?TX`Ac+;Q?m_j407b@40+2;g=Qa4 zP0A{L%{2CS4PNhU?BE9uC9RS%;PDu%Oz7MhKWmQFL8JFZPh8HrxWHW8Y5{F+s0rln z;t+9NKDSXd{n*Qsdncc)y&?it#q~bwu<7M;&AN-SMBu21`g{z*ITG1usp4S&ekwT@ zc)gPHHp>gr-Nskkz2%u&p7B~?b!2#U^S$H_I^eLRMh9ySLw494PEE|D#$>iM|sGL$Kv*% zZ9EOybqS!A+Y<}n@#%4nJIcRIKA4N&8Ne&q=u-K`9I>zVvGX~pL^cMtnUtHz%Te=0fGmfJCbzc9oCxp-)#9J~W)lG&d6)P97yaW>5wE9HgtnnHY()&Rh3K1}uQyzqLm&p%5;ZS<3Jf5KqKyzI4udt~; z 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 literal 0 HcmV?d00001 diff --git a/linphone.xcodeproj/project.pbxproj b/linphone.xcodeproj/project.pbxproj index 035b2db19..b02841227 100644 --- a/linphone.xcodeproj/project.pbxproj +++ b/linphone.xcodeproj/project.pbxproj @@ -686,6 +686,12 @@ C64A85522667B74100252AD2 /* ephemeral_messages_default.png in Resources */ = {isa = PBXBuildFile; fileRef = C64A85512667B74100252AD2 /* ephemeral_messages_default.png */; }; C666756F264C925800A0273C /* VFSUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6DA657B261C950C0020CB43 /* VFSUtil.swift */; }; C6667571264C925B00A0273C /* VFSUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6DA657B261C950C0020CB43 /* VFSUtil.swift */; }; + C6B4444226AAD0980076C517 /* file_video_default.png in Resources */ = {isa = PBXBuildFile; fileRef = C6B4443D26AAD0970076C517 /* file_video_default.png */; }; + C6B4444326AAD0980076C517 /* file_default.png in Resources */ = {isa = PBXBuildFile; fileRef = C6B4443E26AAD0970076C517 /* file_default.png */; }; + C6B4444426AAD0980076C517 /* file_picture_default.png in Resources */ = {isa = PBXBuildFile; fileRef = C6B4443F26AAD0970076C517 /* file_picture_default.png */; }; + C6B4444526AAD0980076C517 /* file_audio_default.png in Resources */ = {isa = PBXBuildFile; fileRef = C6B4444026AAD0970076C517 /* file_audio_default.png */; }; + C6B4444626AAD0980076C517 /* file_pdf_default.png in Resources */ = {isa = PBXBuildFile; fileRef = C6B4444126AAD0970076C517 /* file_pdf_default.png */; }; + C6B4444826AADA530076C517 /* SwiftUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6B4444726AADA530076C517 /* SwiftUtil.swift */; }; C6DA657C261C950C0020CB43 /* VFSUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6DA657B261C950C0020CB43 /* VFSUtil.swift */; }; C90FAA7915AF54E6002091CB /* HistoryDetailsView.m in Sources */ = {isa = PBXBuildFile; fileRef = C90FAA7715AF54E6002091CB /* HistoryDetailsView.m */; }; CF15F21E20E4F9A3008B1DE6 /* UIImageViewDeletable.m in Sources */ = {isa = PBXBuildFile; fileRef = CF15F21C20E4F9A3008B1DE6 /* UIImageViewDeletable.m */; }; @@ -1730,6 +1736,12 @@ C64A854D2667B67200252AD2 /* EphemeralSettingsView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EphemeralSettingsView.m; sourceTree = ""; }; C64A854F2667B67A00252AD2 /* EphemeralSettingsView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = EphemeralSettingsView.xib; sourceTree = ""; }; C64A85512667B74100252AD2 /* ephemeral_messages_default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = ephemeral_messages_default.png; sourceTree = ""; }; + C6B4443D26AAD0970076C517 /* file_video_default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = file_video_default.png; sourceTree = ""; }; + C6B4443E26AAD0970076C517 /* file_default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = file_default.png; sourceTree = ""; }; + C6B4443F26AAD0970076C517 /* file_picture_default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = file_picture_default.png; sourceTree = ""; }; + C6B4444026AAD0970076C517 /* file_audio_default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = file_audio_default.png; sourceTree = ""; }; + C6B4444126AAD0970076C517 /* file_pdf_default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = file_pdf_default.png; sourceTree = ""; }; + C6B4444726AADA530076C517 /* SwiftUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUtil.swift; sourceTree = ""; }; C6DA657B261C950C0020CB43 /* VFSUtil.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VFSUtil.swift; sourceTree = ""; }; 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 = ""; }; @@ -2459,6 +2471,11 @@ 633FEBE11D3CD5570014B822 /* images */ = { isa = PBXGroup; children = ( + C6B4444026AAD0970076C517 /* file_audio_default.png */, + C6B4443E26AAD0970076C517 /* file_default.png */, + C6B4444126AAD0970076C517 /* file_pdf_default.png */, + C6B4443F26AAD0970076C517 /* file_picture_default.png */, + C6B4443D26AAD0970076C517 /* file_video_default.png */, C622E3EC26A8128F004F5434 /* vr_off.png */, C622E3EB26A8128F004F5434 /* vr_on.png */, C622E3ED26A8128F004F5434 /* vr_pause.png */, @@ -3061,6 +3078,7 @@ 63423C091C4501D000D9A050 /* Contact.m */, 8C1B67081E6718BC001EA2FE /* AudioHelper.h */, 8C1B67051E671826001EA2FE /* AudioHelper.m */, + C6B4444726AADA530076C517 /* SwiftUtil.swift */, ); name = Utils; sourceTree = ""; @@ -3624,6 +3642,7 @@ 633FEDFD1D3CD5590014B822 /* camera_switch_default@2x.png in Resources */, 633FEEC51D3CD55A0014B822 /* numpad_5_over.png in Resources */, 633FEE721D3CD5590014B822 /* history_all_default.png in Resources */, + C6B4444326AAD0980076C517 /* file_default.png in Resources */, 615A283C2180789C0060F920 /* security_toogle_button@2x.png in Resources */, 633FEF0A1D3CD55A0014B822 /* options_transfer_call_default.png in Resources */, 633FEDA51D3CD5590014B822 /* back_default@2x.png in Resources */, @@ -3649,6 +3668,7 @@ 63E27A321C4FECD000D332AE /* LaunchScreen.xib in Resources */, 633FEED11D3CD55A0014B822 /* numpad_6~ipad.png in Resources */, CF7602E82108759A00749F76 /* UIRecordingCell.xib in Resources */, + C6B4444626AAD0980076C517 /* file_pdf_default.png in Resources */, 633FEED21D3CD55A0014B822 /* numpad_6~ipad@2x.png in Resources */, 633FEDCD1D3CD5590014B822 /* call_quality_indicator_0@2x.png in Resources */, 636316D41A1DEC650009B839 /* SettingsView.xib in Resources */, @@ -3764,6 +3784,7 @@ 633FEE001D3CD5590014B822 /* camera_switch_over.png in Resources */, C622E3F126A81290004F5434 /* vr_on.png in Resources */, 633FEF401D3CD55A0014B822 /* select_all_default.png in Resources */, + C6B4444426AAD0980076C517 /* file_picture_default.png in Resources */, 633FEDF01D3CD5590014B822 /* call_transfer_disabled.png in Resources */, 633FEE351D3CD5590014B822 /* conference_exit_default@2x.png in Resources */, 633FEECF1D3CD55A0014B822 /* numpad_6_over~ipad.png in Resources */, @@ -3855,6 +3876,7 @@ 633FEEF71D3CD55A0014B822 /* numpad_star_over@2x.png in Resources */, 633FEEAB1D3CD55A0014B822 /* numpad_2_default.png in Resources */, 633FEE851D3CD5590014B822 /* led_error@2x.png in Resources */, + C6B4444226AAD0980076C517 /* file_video_default.png in Resources */, 633FEDBE1D3CD5590014B822 /* call_back_default.png in Resources */, 633FEF0F1D3CD55A0014B822 /* pause_big_default@2x.png in Resources */, CF7602F6210898CC00749F76 /* rec_on_default@2x.png in Resources */, @@ -3900,6 +3922,7 @@ 633FEF3B1D3CD55A0014B822 /* security_ko@2x.png in Resources */, 633FEE4A1D3CD5590014B822 /* delete_disabled.png in Resources */, 614D09CE21E74D5400C43EDF /* GoogleService-Info.plist in Resources */, + C6B4444526AAD0980076C517 /* file_audio_default.png in Resources */, C61B1BF72667EC6B001A4E4A /* ephemeral_messages_color_A.png in Resources */, 633FEF151D3CD55A0014B822 /* pause_small_default@2x.png in Resources */, 633FEEF91D3CD55A0014B822 /* numpad_star_over~ipad@2x.png in Resources */, @@ -4219,6 +4242,7 @@ 6135761C240E81BB005304D4 /* UIInterfaceStyleButton.m in Sources */, 8CD99A3C2090B9FA008A7CDA /* ChatConversationImdnView.m in Sources */, 1D3623260D0F684500981E51 /* LinphoneAppDelegate.m in Sources */, + C6B4444826AADA530076C517 /* SwiftUtil.swift in Sources */, 614C087A23D1A37400217F80 /* CallManager.swift in Sources */, CF15F21E20E4F9A3008B1DE6 /* UIImageViewDeletable.m in Sources */, 22F2508E107141E100AC9B3F /* DialerView.m in Sources */, From 77f096a38d421db082befef332b802651d3a1ae9 Mon Sep 17 00:00:00 2001 From: Christophe Deschamps Date: Fri, 23 Jul 2021 23:18:41 +0200 Subject: [PATCH 04/12] Chat messages icons adjustements for auto-download off & imageDrawer --- Classes/Base.lproj/ChatConversationView.xib | 2 +- Classes/ChatConversationView.h | 2 +- Classes/ChatConversationView.m | 20 +++++++++++++------- Classes/KIF | 1 - Classes/LinphoneUI/UIChatBubblePhotoCell.m | 7 ++++++- Classes/LinphoneUI/UIChatBubbleTextCell.m | 8 ++++---- Classes/LinphoneUI/UIChatContentView.m | 13 +++++++------ Classes/LinphoneUI/UIImageViewDeletable.xib | 18 ++++++++---------- Classes/SwiftUtil.swift | 2 +- 9 files changed, 41 insertions(+), 32 deletions(-) delete mode 160000 Classes/KIF diff --git a/Classes/Base.lproj/ChatConversationView.xib b/Classes/Base.lproj/ChatConversationView.xib index 62255dabe..e4db76e66 100644 --- a/Classes/Base.lproj/ChatConversationView.xib +++ b/Classes/Base.lproj/ChatConversationView.xib @@ -361,7 +361,7 @@ - + diff --git a/Classes/ChatConversationView.h b/Classes/ChatConversationView.h index 1a8d77309..bc4b00e93 100644 --- a/Classes/ChatConversationView.h +++ b/Classes/ChatConversationView.h @@ -49,7 +49,7 @@ @interface ChatConversationView : TPMultiLayoutViewController { + UIDocumentInteractionControllerDelegate, UISearchBarDelegate, UIImageViewDeletableDelegate,QLPreviewControllerDelegate, UICollectionViewDataSource,UICollectionViewDelegate,UIDocumentMenuDelegate,UIDocumentPickerDelegate,UITableViewDataSource, UITableViewDelegate> { OrderedDictionary *imageQualities; BOOL scrollOnGrowingEnabled; BOOL composingVisible; diff --git a/Classes/ChatConversationView.m b/Classes/ChatConversationView.m index 05ec3b14c..5ee487012 100644 --- a/Classes/ChatConversationView.m +++ b/Classes/ChatConversationView.m @@ -187,6 +187,7 @@ static UICompositeViewDescription *compositeDescription = nil; [_tableController setChatRoomDelegate:self]; [_imagesCollectionView registerClass:[UIImageViewDeletable class] forCellWithReuseIdentifier:NSStringFromClass([UIImageViewDeletable class])]; [_imagesCollectionView setDataSource:self]; + [_imagesCollectionView setDelegate:self]; [_toggleSelectionButton setImage:[UIImage imageNamed:@"select_all_default.png"] forState:UIControlStateSelected]; _vrInnerView.layer.cornerRadius = 5.0f; @@ -250,12 +251,12 @@ static UICompositeViewDescription *compositeDescription = nil; animations:^{ // resizing imagesView CGRect imagesFrame = [_imagesView frame]; - imagesFrame.origin.y = [_messageView frame].origin.y - 100; - imagesFrame.size.height = 100; + imagesFrame.origin.y = [_messageView frame].origin.y - 120; + imagesFrame.size.height = 120; [_imagesView setFrame:imagesFrame]; // resizing chatTable CGRect tableViewFrame = [_tableController.tableView frame]; - tableViewFrame.size.height -= 100; + tableViewFrame.size.height -= 120; [_tableController.tableView setFrame:tableViewFrame]; [self updateFramesInclRecordingView]; } @@ -1452,14 +1453,19 @@ void on_chat_room_conference_alert(LinphoneChatRoom *cr, const LinphoneEventLog return [_fileContext count]; } + +- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { + return UIInterfaceOrientationIsLandscape([[UIApplication sharedApplication] statusBarOrientation]) ? CGSizeMake(60, 60) : CGSizeMake(120, 120); +} + - (__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; + imgFrame.size.height = 60; } else { - imgFrame.size.height = 100; + imgFrame.size.height = 120; } [imgView.image setImage:[UIImage resizeImage:[_fileContext.previewsArray objectAtIndex:[indexPath item]] withMaxWidth:imgFrame.size.width andMaxHeight:imgFrame.size.height]]; @@ -1471,7 +1477,7 @@ void on_chat_room_conference_alert(LinphoneChatRoom *cr, const LinphoneEventLog } - (void)refreshImageDrawer { - int heightDiff = UIInterfaceOrientationIsLandscape([[UIApplication sharedApplication] statusBarOrientation]) ? 55 : 105; + int heightDiff = UIInterfaceOrientationIsLandscape([[UIApplication sharedApplication] statusBarOrientation]) ? 65 : 125; if ([_fileContext count] == 0) { [UIView animateWithDuration:0 @@ -1560,7 +1566,7 @@ void on_chat_room_conference_alert(LinphoneChatRoom *cr, const LinphoneEventLog NSFileCoordinator *co =[[NSFileCoordinator alloc] init]; NSError *error = nil; [co coordinateReadingItemAtURL:url options:0 error:&error byAccessor:^(NSURL * _Nonnull newURL) { - UIImage *image = [ChatConversationView drawText:[newURL lastPathComponent] image:[ChatConversationView getBasicImage] textSize:10]; + UIImage *image = [UIChatBubbleTextCell getImageFromFileName:[newURL lastPathComponent]]; [_fileContext addObject:[NSData dataWithContentsOfURL:newURL] name:[newURL lastPathComponent] type:@"file" image:image]; [self refreshImageDrawer]; }]; diff --git a/Classes/KIF b/Classes/KIF deleted file mode 160000 index 99ae001ae..000000000 --- a/Classes/KIF +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 99ae001ae51f4883d189e3f77544828ec035395d diff --git a/Classes/LinphoneUI/UIChatBubblePhotoCell.m b/Classes/LinphoneUI/UIChatBubblePhotoCell.m index b5a946b01..5371c49ad 100644 --- a/Classes/LinphoneUI/UIChatBubblePhotoCell.m +++ b/Classes/LinphoneUI/UIChatBubblePhotoCell.m @@ -268,10 +268,15 @@ _playButton.hidden = YES; _fileName.hidden = _fileView.hidden = _fileButton.hidden =YES; } else { - _downloadButton.hidden = NO; + _downloadButton.hidden = YES; + UIChatContentView * contentView = [[UIChatContentView alloc] init]; + [contentView setContent:fileContent message:self.message]; + contentView.position = 0; + [_contentViews addObject:contentView]; _cancelButton.hidden = _fileTransferProgress.hidden = YES; _playButton.hidden = YES; _fileName.hidden = _fileView.hidden = _fileButton.hidden = YES; + [self layoutSubviews]; } return; } diff --git a/Classes/LinphoneUI/UIChatBubbleTextCell.m b/Classes/LinphoneUI/UIChatBubbleTextCell.m index f0d583b5a..67233af23 100644 --- a/Classes/LinphoneUI/UIChatBubbleTextCell.m +++ b/Classes/LinphoneUI/UIChatBubbleTextCell.m @@ -635,10 +635,10 @@ static const CGFloat CELL_MESSAGE_Y_MARGIN = 44; NSString *localFile = [LinphoneManager getMessageAppDataForKey:@"localfile" inMessage:chat]; NSString *localVideo = [LinphoneManager getMessageAppDataForKey:@"localvideo" inMessage:chat]; NSString *filePath = [LinphoneManager getMessageAppDataForKey:@"encryptedfile" inMessage:chat]; - NSString *fileName = [NSString stringWithUTF8String:linphone_content_get_name(fileContent)]; - + NSString *fileName = fileContent ? [NSString stringWithUTF8String:linphone_content_get_name(fileContent)] : nil; + CGSize textSize = CGSizeMake(0, 0); - if (![messageText isEqualToString:@"🗻"]) { + if (![messageText isEqualToString:@"🗻"] && messageText.length > 0) { textSize = [self computeBoundingBox:messageText size:CGSizeMake(width - CELL_MESSAGE_X_MARGIN - 4, CGFLOAT_MAX) font:messageFont]; @@ -677,7 +677,7 @@ static const CGFloat CELL_MESSAGE_Y_MARGIN = 44; } else { if (!localImage && !localVideo) { //We are loading the image - CGSize baseSize = CGSizeMake(CELL_MIN_WIDTH + CELL_MESSAGE_X_MARGIN, CELL_MIN_HEIGHT + CELL_MESSAGE_Y_MARGIN + textSize.height + 20); + CGSize baseSize = CGSizeMake(120 + CELL_MESSAGE_X_MARGIN, 120 + CELL_MESSAGE_Y_MARGIN + textSize.height + (textSize.height != 0 ? 20 : 0)); if (voiceContent) { baseSize = [self addVoicePlayerToSize:baseSize withMargins:true]; } diff --git a/Classes/LinphoneUI/UIChatContentView.m b/Classes/LinphoneUI/UIChatContentView.m index 418d3a996..3b5713e94 100644 --- a/Classes/LinphoneUI/UIChatContentView.m +++ b/Classes/LinphoneUI/UIChatContentView.m @@ -31,19 +31,20 @@ if(!linphone_chat_message_is_outgoing(_message) && linphone_content_is_file_transfer(_content)) { // has not yet downloaded - UIImage *basicImage = [ChatConversationView getBasicImage]; NSString *name = [NSString stringWithUTF8String:linphone_content_get_name(content)] ; - UIImage *image = [ChatConversationView drawText:name image:basicImage textSize:25]; + UIImage *image = [UIChatBubbleTextCell getImageFromFileName:name]; [self setImage:image]; _downloadButton = [UIButton buttonWithType:UIButtonTypeCustom]; [_downloadButton addTarget:self action:@selector(onDownloadClick:) forControlEvents:UIControlEventTouchUpInside]; - _downloadButton.backgroundColor = [UIColor orangeColor]; - UIFont *boldFont = [UIFont systemFontOfSize:10]; - NSMutableAttributedString *boldText = [[NSMutableAttributedString alloc] initWithString:@"Download" attributes:@{ NSFontAttributeName : boldFont }]; + UIFont *boldFont = [UIFont systemFontOfSize:12]; + NSMutableParagraphStyle * paragraphStyle = [[NSMutableParagraphStyle alloc] init]; + paragraphStyle.alignment = NSTextAlignmentCenter; + + NSMutableAttributedString *boldText = [[NSMutableAttributedString alloc] initWithString:@"Download" attributes:@{ NSFontAttributeName : boldFont, NSParagraphStyleAttributeName:paragraphStyle,NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle) }]; [_downloadButton setAttributedTitle:boldText forState:UIControlStateNormal]; - _downloadButton.frame = CGRectMake(3, 3, 60, 30); + _downloadButton.frame = CGRectMake(0, 90, 120, 30); [self addSubview:_downloadButton]; } else { if (_filePath == NULL) { diff --git a/Classes/LinphoneUI/UIImageViewDeletable.xib b/Classes/LinphoneUI/UIImageViewDeletable.xib index 2fa5d17e1..4d0dea67a 100644 --- a/Classes/LinphoneUI/UIImageViewDeletable.xib +++ b/Classes/LinphoneUI/UIImageViewDeletable.xib @@ -1,11 +1,9 @@ - - - - + + - + @@ -17,15 +15,15 @@ - + - + + - - + diff --git a/Classes/SwiftUtil.swift b/Classes/SwiftUtil.swift index 6c9022fac..ff1701b87 100644 --- a/Classes/SwiftUtil.swift +++ b/Classes/SwiftUtil.swift @@ -33,7 +33,7 @@ import UIKit image.draw(in: CGRect(origin: CGPoint(x: size.width/2 - (image.size.width)/2,y: 15), size: image.size)) - let rect = CGRect(origin: CGPoint(x: 0,y: 70), size: size) + let rect = CGRect(origin: CGPoint(x: 0,y: 70), size: CGSize(width: size.width, height: 30)) text.draw(in: rect, withAttributes: textFontAttributes) let newImage = UIGraphicsGetImageFromCurrentImageContext() From aa4b7e5554ab399109d49d9d8149f49e690c6af6 Mon Sep 17 00:00:00 2001 From: Christophe Deschamps Date: Sun, 25 Jul 2021 01:24:33 +0200 Subject: [PATCH 05/12] Fix incoming picture with no text display --- Classes/LinphoneUI/UIChatBubbleTextCell.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Classes/LinphoneUI/UIChatBubbleTextCell.m b/Classes/LinphoneUI/UIChatBubbleTextCell.m index 67233af23..5e8e57013 100644 --- a/Classes/LinphoneUI/UIChatBubbleTextCell.m +++ b/Classes/LinphoneUI/UIChatBubbleTextCell.m @@ -624,7 +624,7 @@ static const CGFloat CELL_MESSAGE_Y_MARGIN = 44; } - LinphoneContent *fileContent = linphone_chat_message_get_utf8_text(chat) ? nil : linphone_chat_message_get_file_transfer_information(chat); + LinphoneContent *fileContent = linphone_chat_message_get_file_transfer_information(chat); if (url == nil && fileContent == NULL) { size = [self computeBoundingBox:messageText size:CGSizeMake(width - CELL_MESSAGE_X_MARGIN - 4, CGFLOAT_MAX) From 17a4580d16b2a00db86737542d226d6fa87b9a58 Mon Sep 17 00:00:00 2001 From: Christophe Deschamps Date: Sun, 25 Jul 2021 22:18:54 +0200 Subject: [PATCH 06/12] adjust chat bubble size when only VR & Text --- Classes/LinphoneUI/UIChatBubbleTextCell.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Classes/LinphoneUI/UIChatBubbleTextCell.m b/Classes/LinphoneUI/UIChatBubbleTextCell.m index 5e8e57013..972398ee1 100644 --- a/Classes/LinphoneUI/UIChatBubbleTextCell.m +++ b/Classes/LinphoneUI/UIChatBubbleTextCell.m @@ -625,7 +625,7 @@ static const CGFloat CELL_MESSAGE_Y_MARGIN = 44; LinphoneContent *fileContent = linphone_chat_message_get_file_transfer_information(chat); - if (url == nil && fileContent == NULL) { + if (url == nil && (fileContent == NULL||fileContent == voiceContent)) { size = [self computeBoundingBox:messageText size:CGSizeMake(width - CELL_MESSAGE_X_MARGIN - 4, CGFLOAT_MAX) font:messageFont]; @@ -680,6 +680,7 @@ static const CGFloat CELL_MESSAGE_Y_MARGIN = 44; CGSize baseSize = CGSizeMake(120 + CELL_MESSAGE_X_MARGIN, 120 + CELL_MESSAGE_Y_MARGIN + textSize.height + (textSize.height != 0 ? 20 : 0)); if (voiceContent) { baseSize = [self addVoicePlayerToSize:baseSize withMargins:true]; + } return baseSize; } From 7968c65055310703b60f3644f96c2a987449a94a Mon Sep 17 00:00:00 2001 From: Christophe Deschamps Date: Mon, 26 Jul 2021 09:50:16 +0200 Subject: [PATCH 07/12] Fix Chat bubble size with only text, or text with single attcht --- Classes/LinphoneUI/UIChatBubbleTextCell.m | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Classes/LinphoneUI/UIChatBubbleTextCell.m b/Classes/LinphoneUI/UIChatBubbleTextCell.m index 972398ee1..c5a9f2cf1 100644 --- a/Classes/LinphoneUI/UIChatBubbleTextCell.m +++ b/Classes/LinphoneUI/UIChatBubbleTextCell.m @@ -624,13 +624,14 @@ static const CGFloat CELL_MESSAGE_Y_MARGIN = 44; } - LinphoneContent *fileContent = linphone_chat_message_get_file_transfer_information(chat); - if (url == nil && (fileContent == NULL||fileContent == voiceContent)) { + // if here, either 1 file + text or just one file or just text. + BOOL justText = linphone_chat_message_get_text_content(chat) != NULL && contentCount == 1; + if (justText) { // Just text size = [self computeBoundingBox:messageText size:CGSizeMake(width - CELL_MESSAGE_X_MARGIN - 4, CGFLOAT_MAX) font:messageFont]; } else { - + LinphoneContent *fileContent = linphone_chat_message_get_file_transfer_information(chat); NSString *localImage = [LinphoneManager getMessageAppDataForKey:@"localimage" inMessage:chat]; NSString *localFile = [LinphoneManager getMessageAppDataForKey:@"localfile" inMessage:chat]; NSString *localVideo = [LinphoneManager getMessageAppDataForKey:@"localvideo" inMessage:chat]; From 3dc747cf6e78c5f013305ccd4f4b3bf14821a793 Mon Sep 17 00:00:00 2001 From: Christophe Deschamps Date: Mon, 26 Jul 2021 10:16:51 +0200 Subject: [PATCH 08/12] Size adjust when recording & picture only --- Classes/LinphoneUI/UIChatBubbleTextCell.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Classes/LinphoneUI/UIChatBubbleTextCell.m b/Classes/LinphoneUI/UIChatBubbleTextCell.m index c5a9f2cf1..8f8fed31b 100644 --- a/Classes/LinphoneUI/UIChatBubbleTextCell.m +++ b/Classes/LinphoneUI/UIChatBubbleTextCell.m @@ -681,7 +681,8 @@ static const CGFloat CELL_MESSAGE_Y_MARGIN = 44; CGSize baseSize = CGSizeMake(120 + CELL_MESSAGE_X_MARGIN, 120 + CELL_MESSAGE_Y_MARGIN + textSize.height + (textSize.height != 0 ? 20 : 0)); if (voiceContent) { baseSize = [self addVoicePlayerToSize:baseSize withMargins:true]; - + baseSize.height -= VOICE_RECORDING_PLAYER_HEIGHT; + baseSize.height += 10; } return baseSize; } From e23f80ca512f600716073e1f11d55960a0496b59 Mon Sep 17 00:00:00 2001 From: Christophe Deschamps Date: Tue, 27 Jul 2021 14:56:54 +0200 Subject: [PATCH 09/12] Do not attempt to send message if network connection is missing --- Classes/ChatConversationView.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Classes/ChatConversationView.m b/Classes/ChatConversationView.m index 5ee487012..a052f1799 100644 --- a/Classes/ChatConversationView.m +++ b/Classes/ChatConversationView.m @@ -745,7 +745,7 @@ static UICompositeViewDescription *compositeDescription = nil; if (!linphone_core_is_network_reachable(LC)) { [PhoneMainView.instance presentViewController:[LinphoneUtils networkErrorView:@"send a message"] animated:YES completion:nil]; - //return; + return; } if ([_fileContext count] > 0) { if (linphone_chat_room_get_capabilities(_chatRoom) & LinphoneChatRoomCapabilitiesConference) { From 04aded6a48236eb08c1fc1ccc1776064917249e0 Mon Sep 17 00:00:00 2001 From: Christophe Deschamps Date: Thu, 2 Sep 2021 21:22:38 +0200 Subject: [PATCH 10/12] Disable voice recording function when in call --- Classes/ChatConversationView.m | 3 +++ Classes/LinphoneUI/UIChatBubblePhotoCell.m | 3 +++ 2 files changed, 6 insertions(+) diff --git a/Classes/ChatConversationView.m b/Classes/ChatConversationView.m index a052f1799..1a29be608 100644 --- a/Classes/ChatConversationView.m +++ b/Classes/ChatConversationView.m @@ -272,6 +272,7 @@ static UICompositeViewDescription *compositeDescription = nil; // Voice recording _vrView.hidden = true; _preservePendingRecording = false; + _toggleRecord.enabled = linphone_core_get_calls_nb(LC) == 0; } @@ -474,6 +475,8 @@ static UICompositeViewDescription *compositeDescription = nil; - (void)callUpdateEvent:(NSNotification *)notif { [self updateSuperposedButtons]; + _toggleRecord.enabled = linphone_core_get_calls_nb(LC) == 0; + } - (void)update { diff --git a/Classes/LinphoneUI/UIChatBubblePhotoCell.m b/Classes/LinphoneUI/UIChatBubblePhotoCell.m index 5371c49ad..43474ea1a 100644 --- a/Classes/LinphoneUI/UIChatBubblePhotoCell.m +++ b/Classes/LinphoneUI/UIChatBubblePhotoCell.m @@ -165,6 +165,9 @@ } [super update]; + _vrPlayPause.enabled = linphone_core_get_calls_nb(LC) == 0; + + NSMutableDictionary *encrptedFilePaths = NULL; if ([VFSUtil vfsEnabledWithGroupName:kLinphoneMsgNotificationAppGroupId]) { encrptedFilePaths = [LinphoneManager getMessageAppDataForKey:@"encryptedfiles" inMessage:self.message]; From b3a5168191c64af8f9cc864ccb77e0bcf4775d0f Mon Sep 17 00:00:00 2001 From: Christophe Deschamps Date: Fri, 3 Sep 2021 08:41:19 +0200 Subject: [PATCH 11/12] Re-enable voice play button in chat after call ending --- Classes/ChatConversationView.m | 1 + 1 file changed, 1 insertion(+) diff --git a/Classes/ChatConversationView.m b/Classes/ChatConversationView.m index 1a29be608..d7e4d4479 100644 --- a/Classes/ChatConversationView.m +++ b/Classes/ChatConversationView.m @@ -476,6 +476,7 @@ static UICompositeViewDescription *compositeDescription = nil; - (void)callUpdateEvent:(NSNotification *)notif { [self updateSuperposedButtons]; _toggleRecord.enabled = linphone_core_get_calls_nb(LC) == 0; + [_tableController.tableView reloadData]; } From 0d55c0ca2ff2105f3ca5959143479788f7497bbf Mon Sep 17 00:00:00 2001 From: Christophe Deschamps Date: Mon, 26 Jul 2021 15:20:02 +0200 Subject: [PATCH 12/12] Copy/Transfer/Reply feature for chat messages --- .../Base.lproj/ChatConversationImdnView.xib | 76 +--- Classes/Base.lproj/ChatConversationView.xib | 6 + Classes/Base.lproj/ChatsListView.xib | 76 +++- Classes/ChatConversationImdnView.h | 8 +- Classes/ChatConversationImdnView.m | 100 +++-- Classes/ChatConversationTableView.h | 12 +- Classes/ChatConversationTableView.m | 47 +++ Classes/ChatConversationView.h | 15 +- Classes/ChatConversationView.m | 204 ++++++--- Classes/ChatsListTableView.h | 1 + Classes/ChatsListTableView.m | 7 + Classes/ChatsListView.h | 3 + Classes/ChatsListView.m | 25 +- .../Base.lproj/UIChatBubblePhotoCell.xib | 302 ++++++------- .../Base.lproj/UIChatBubbleTextCell.xib | 14 + Classes/LinphoneUI/Base.lproj/UIChatCell.xib | 14 +- .../Base.lproj/UIChatReplyBubbleView.xib | 84 ++++ .../Base.lproj/UIConfirmationDialog.xib | 40 +- Classes/LinphoneUI/UIChatBubblePhotoCell.h | 3 - Classes/LinphoneUI/UIChatBubblePhotoCell.m | 59 ++- Classes/LinphoneUI/UIChatBubbleTextCell.h | 24 +- Classes/LinphoneUI/UIChatBubbleTextCell.m | 395 ++++++++++++------ Classes/LinphoneUI/UIChatCell.h | 2 + Classes/LinphoneUI/UIChatCell.m | 1 + Classes/LinphoneUI/UIChatReplyBubbleView.h | 40 ++ Classes/LinphoneUI/UIChatReplyBubbleView.m | 159 +++++++ Classes/LinphoneUI/UIConfirmationDialog.h | 1 + .../en.lproj/UIChatReplyBubbleView.strings | 9 + .../fr.lproj/UIChatReplyBubbleView.strings | 9 + Classes/PhoneMainView.h | 1 + Classes/PhoneMainView.m | 11 + Classes/SwiftUtil.swift | 69 ++- Classes/Utils/FileTransferDelegate.h | 7 +- Classes/Utils/FileTransferDelegate.m | 25 +- Classes/Utils/Utils.h | 9 + Classes/Utils/Utils.m | 30 ++ Podfile | 2 +- Resources/fr.lproj/Localizable.strings | Bin 57484 -> 29538 bytes Resources/images/cancel_forward.png | Bin 0 -> 9030 bytes Resources/images/file_voice_default.png | Bin 0 -> 8605 bytes Resources/images/forward_message_default.png | Bin 0 -> 3732 bytes Resources/images/menu_copy_text_default.png | Bin 0 -> 1899 bytes Resources/images/menu_delete.png | Bin 0 -> 729 bytes Resources/images/menu_forward_default.png | Bin 0 -> 1910 bytes Resources/images/menu_info.png | Bin 0 -> 2069 bytes Resources/images/menu_reply_default.png | Bin 0 -> 9498 bytes Resources/images/menu_resend_default.png | Bin 0 -> 8229 bytes Resources/images/reply_cancel.png | Bin 0 -> 2884 bytes linphone.xcodeproj/project.pbxproj | 70 +++- 49 files changed, 1396 insertions(+), 564 deletions(-) create mode 100644 Classes/LinphoneUI/Base.lproj/UIChatReplyBubbleView.xib create mode 100644 Classes/LinphoneUI/UIChatReplyBubbleView.h create mode 100644 Classes/LinphoneUI/UIChatReplyBubbleView.m create mode 100644 Classes/LinphoneUI/en.lproj/UIChatReplyBubbleView.strings create mode 100644 Classes/LinphoneUI/fr.lproj/UIChatReplyBubbleView.strings create mode 100644 Resources/images/cancel_forward.png create mode 100644 Resources/images/file_voice_default.png create mode 100644 Resources/images/forward_message_default.png create mode 100644 Resources/images/menu_copy_text_default.png create mode 100644 Resources/images/menu_delete.png create mode 100644 Resources/images/menu_forward_default.png create mode 100644 Resources/images/menu_info.png create mode 100644 Resources/images/menu_reply_default.png create mode 100644 Resources/images/menu_resend_default.png create mode 100644 Resources/images/reply_cancel.png diff --git a/Classes/Base.lproj/ChatConversationImdnView.xib b/Classes/Base.lproj/ChatConversationImdnView.xib index c768ffd1d..99dc77538 100644 --- a/Classes/Base.lproj/ChatConversationImdnView.xib +++ b/Classes/Base.lproj/ChatConversationImdnView.xib @@ -1,17 +1,15 @@ - + + - + + + - - - - - @@ -19,19 +17,19 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + - + - + @@ -112,10 +76,14 @@ - - + + + + + + diff --git a/Classes/Base.lproj/ChatConversationView.xib b/Classes/Base.lproj/ChatConversationView.xib index e4db76e66..446b483da 100644 --- a/Classes/Base.lproj/ChatConversationView.xib +++ b/Classes/Base.lproj/ChatConversationView.xib @@ -31,6 +31,7 @@ + @@ -387,6 +388,11 @@ + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + - - - @@ -229,21 +246,11 @@ - - - - - - - - - - @@ -254,6 +261,7 @@ + diff --git a/Classes/LinphoneUI/Base.lproj/UIChatBubbleTextCell.xib b/Classes/LinphoneUI/Base.lproj/UIChatBubbleTextCell.xib index 481e4541a..d19e81d0c 100644 --- a/Classes/LinphoneUI/Base.lproj/UIChatBubbleTextCell.xib +++ b/Classes/LinphoneUI/Base.lproj/UIChatBubbleTextCell.xib @@ -18,6 +18,8 @@ + + @@ -41,6 +43,17 @@ + + + + + @@ -80,5 +93,6 @@ + diff --git a/Classes/LinphoneUI/Base.lproj/UIChatCell.xib b/Classes/LinphoneUI/Base.lproj/UIChatCell.xib index 67257c9d5..9ed041472 100644 --- a/Classes/LinphoneUI/Base.lproj/UIChatCell.xib +++ b/Classes/LinphoneUI/Base.lproj/UIChatCell.xib @@ -14,6 +14,7 @@ + @@ -64,10 +65,6 @@ - + + @@ -101,6 +106,7 @@ + diff --git a/Classes/LinphoneUI/Base.lproj/UIChatReplyBubbleView.xib b/Classes/LinphoneUI/Base.lproj/UIChatReplyBubbleView.xib new file mode 100644 index 000000000..4a657bdc5 --- /dev/null +++ b/Classes/LinphoneUI/Base.lproj/UIChatReplyBubbleView.xib @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Classes/LinphoneUI/Base.lproj/UIConfirmationDialog.xib b/Classes/LinphoneUI/Base.lproj/UIConfirmationDialog.xib index 3bc5b8c8e..eb265f061 100644 --- a/Classes/LinphoneUI/Base.lproj/UIConfirmationDialog.xib +++ b/Classes/LinphoneUI/Base.lproj/UIConfirmationDialog.xib @@ -1,11 +1,9 @@ - - - - + + - + @@ -15,6 +13,7 @@ + @@ -25,26 +24,26 @@ - + - + - - + - - + + - + + diff --git a/Classes/LinphoneUI/UIChatBubblePhotoCell.h b/Classes/LinphoneUI/UIChatBubblePhotoCell.h index c1530c413..49555feaa 100644 --- a/Classes/LinphoneUI/UIChatBubblePhotoCell.h +++ b/Classes/LinphoneUI/UIChatBubblePhotoCell.h @@ -41,7 +41,6 @@ @property(strong, nonatomic) IBOutlet UITapGestureRecognizer *imageGestureRecognizer; @property (weak, nonatomic) IBOutlet UIButton *fileButton; @property (weak, nonatomic) IBOutlet UIView *fileView; -@property (strong, nonatomic) IBOutlet UILongPressGestureRecognizer *plusLongGestureRecognizer; @property(strong, nonatomic) NSMutableArray *contentViews; // Video recordings @@ -61,10 +60,8 @@ - (IBAction)onDownloadClick:(id)event; - (IBAction)onImageClick:(id)event; - (IBAction)onCancelClick:(id)sender; -- (IBAction)onResendClick:(id)event; - (IBAction)onPlayClick:(id)sender; - (IBAction)onFileClick:(id)sender; -- (IBAction)onPlusClick:(id)sender; @end diff --git a/Classes/LinphoneUI/UIChatBubblePhotoCell.m b/Classes/LinphoneUI/UIChatBubblePhotoCell.m index 43474ea1a..f94ea00d7 100644 --- a/Classes/LinphoneUI/UIChatBubblePhotoCell.m +++ b/Classes/LinphoneUI/UIChatBubblePhotoCell.m @@ -61,8 +61,13 @@ assetIsLoaded = FALSE; self.contentView.userInteractionEnabled = NO; _contentViews = [[NSMutableArray alloc] init]; - self.vrWaveMaskPlayback.layer.cornerRadius = 10.0f; - self.vrWaveMaskPlayback.layer.masksToBounds = YES; + + + self.vrView.layer.cornerRadius = 30.0f; + self.vrView.layer.masksToBounds = YES; + [self.innerView addGestureRecognizer:[[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(onPopupMenuPressed)]]; + self.messageText.userInteractionEnabled = false; + } return self; } @@ -82,7 +87,6 @@ - (void)setChatMessage:(LinphoneChatMessage *)amessage { _imageGestureRecognizer.enabled = NO; - _plusLongGestureRecognizer.enabled = NO; _messageImageView.image = nil; _finalImage.image = nil; _finalImage.hidden = TRUE; @@ -115,7 +119,6 @@ _messageImageView.hidden = YES; _finalImage.hidden = NO; _fileView.hidden = YES; - _plusLongGestureRecognizer.enabled = YES; [self layoutSubviews]; }); } @@ -151,7 +154,6 @@ [_messageImageView stopLoading]; _messageImageView.hidden = YES; _imageGestureRecognizer.enabled = YES; - _plusLongGestureRecognizer.enabled = YES; _finalImage.hidden = NO; [self layoutSubviews]; }); @@ -499,25 +501,6 @@ }]; } -- (IBAction)onPlusClick:(id)sender { - UILongPressGestureRecognizer *gesture = (UILongPressGestureRecognizer *)sender; - if (gesture.state != UIGestureRecognizerStateBegan) { - // allow only one click once time - return; - } - DTActionSheet *sheet = [[DTActionSheet alloc] initWithTitle:@""]; - dispatch_async(dispatch_get_main_queue(), ^{ - [sheet addButtonWithTitle:NSLocalizedString(@"Save to Gallery", nil) - block:^() { - LinphoneContent *content = linphone_chat_message_get_file_transfer_information(self.message); - NSString *name = [NSString stringWithUTF8String:linphone_content_get_name(content)]; - [ChatConversationView writeMediaToGallery:name fileType:[NSString stringWithUTF8String:linphone_content_get_type(content)?:""]]; - }]; - - [sheet addCancelButtonWithTitle:NSLocalizedString(@"Cancel", nil) block:nil]; - [sheet showInView:PhoneMainView.instance.view]; - }); -} - (IBAction)onFileClick:(id)sender { ChatConversationView *view = VIEW(ChatConversationView); @@ -545,16 +528,6 @@ } } -- (void)onResendClick:(id)event { - if (_downloadButton.hidden == NO) { - // if download button is displayed, click on it - [self onDownloadClick:event]; - } else if (_cancelButton.hidden == NO) { - [self onCancelClick:event]; - } else { - [super onResend]; - } -} - (IBAction)onImageClick:(id)event { if (_finalImage.tag == FILE_ICON_TAG) { @@ -563,7 +536,7 @@ } LinphoneChatMessageState state = linphone_chat_message_get_state(self.message); if (state == LinphoneChatMessageStateNotDelivered) { - [self onResendClick:event]; + return; } else { if (![_messageImageView isLoading]) { ImageView *view = VIEW(ImageView); @@ -768,6 +741,14 @@ } else { _vrView.hidden = YES; } + + CGRect r = super.photoCellContentView.frame; + r.origin.y = linphone_chat_message_is_reply(super.message) ? super.replyView.view.frame.origin.y + super.replyView.view.frame.size.height + 10 : 7 ; + super.photoCellContentView.frame = r; + + r = super.photoCellContentView.frame; + r.origin.y = linphone_chat_message_is_forward(super.message) ? super.contactDateLabel.frame.origin.y + super.contactDateLabel.frame.size.height + 3 : r.origin.y; + super.photoCellContentView.frame = r; self.messageText.frame = textFrame; } @@ -841,6 +822,14 @@ static AVAudioPlayer* utilityPlayer; } } + +// menu + +-(void) onPopupMenuPressed { + [super onPopupMenuPressed]; +} + + @end diff --git a/Classes/LinphoneUI/UIChatBubbleTextCell.h b/Classes/LinphoneUI/UIChatBubbleTextCell.h index e90bf6b44..fe729b670 100644 --- a/Classes/LinphoneUI/UIChatBubbleTextCell.h +++ b/Classes/LinphoneUI/UIChatBubbleTextCell.h @@ -22,6 +22,7 @@ #import "UITextViewNoDefine.h" #import "ChatConversationTableView.h" #import "UIRoundedImageView.h" +#import "UIChatReplyBubbleView.h" #define CELL_IMAGE_X_MARGIN 100 #define IMAGE_DEFAULT_WIDTH 120 @@ -30,7 +31,7 @@ #define VOICE_RECORDING_PLAYER_WIDTH 300 -@interface UIChatBubbleTextCell : UITableViewCell +@interface UIChatBubbleTextCell : UITableViewCell @property(readonly, nonatomic) LinphoneEventLog *event; @property(readonly, nonatomic) LinphoneChatMessage *message; @@ -50,6 +51,22 @@ @property (weak, nonatomic) IBOutlet UIView *innerView; @property (weak, nonatomic) IBOutlet UILabel *ephemeralTime; @property (weak, nonatomic) IBOutlet UIImageView *ephemeralIcon; +@property ChatConversationTableView *tableController; +@property BOOL popupMenuAllowed; + +// Message popup menu +@property UITableView *popupMenu; +@property NSMutableArray *messageActionsTitles; +@property NSMutableArray *messageActionsIcons; +@property NSMutableArray *messageActionsBlocks; + +// Message reply/transfer +@property UIChatReplyBubbleView *replyView; +@property UILabel *replyOrForward; +@property (weak, nonatomic) IBOutlet UIImageView *replyTransferIcon; +@property (weak, nonatomic) IBOutlet UILabel *replyTransferLabel; +@property (weak, nonatomic) IBOutlet UIView *photoCellContentView; + @property(nonatomic) BOOL isFirst; @property(nonatomic) BOOL isLast; @@ -67,8 +84,6 @@ - (void)clearEncryptedFiles; - (void)onDelete; -- (void)onResend; -- (void)onLime; - (void)update; - (void)displayImdmStatus:(LinphoneChatMessageState)state; @@ -77,5 +92,6 @@ + (CGSize)computeBoundingBox:(NSString *)text size:(CGSize)size font:(UIFont *)font; + (NSString *)ContactDateForChat:(LinphoneChatMessage *)message; +(LinphoneContent *) voiceContent:(LinphoneChatMessage *)message; - +-(void) onPopupMenuPressed; +-(void) dismissPopup; @end diff --git a/Classes/LinphoneUI/UIChatBubbleTextCell.m b/Classes/LinphoneUI/UIChatBubbleTextCell.m index 8f8fed31b..ef170ae1b 100644 --- a/Classes/LinphoneUI/UIChatBubbleTextCell.m +++ b/Classes/LinphoneUI/UIChatBubbleTextCell.m @@ -20,6 +20,7 @@ #import "UIChatBubbleTextCell.h" #import "LinphoneManager.h" #import "PhoneMainView.h" +#import "Utils.h" #import #import @@ -28,6 +29,8 @@ #pragma mark - Lifecycle Functions + + - (id)initWithIdentifier:(NSString *)identifier { if ((self = [super initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier]) != nil) { if ([identifier isEqualToString:NSStringFromClass(self.class)]) { @@ -40,22 +43,11 @@ [self addSubview:sub]; } } + + + [_innerView addGestureRecognizer:[[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(onPopupMenuPressed)]]; + _messageText.userInteractionEnabled = false; - UITapGestureRecognizer *limeRecognizer = - [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onLime)]; - limeRecognizer.numberOfTapsRequired = 1; - //[_LIMEKO addGestureRecognizer:limeRecognizer]; - //_LIMEKO.userInteractionEnabled = YES; - UITapGestureRecognizer *resendRecognizer = - [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onResend)]; - resendRecognizer.numberOfTapsRequired = 1; - [_bubbleView addGestureRecognizer:resendRecognizer]; - _imdmIcon.userInteractionEnabled = YES; - UITapGestureRecognizer *resendRecognizer2 = - [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onResend)]; - resendRecognizer2.numberOfTapsRequired = 1; - //[_imdmLabel addGestureRecognizer:resendRecognizer2]; - //_imdmLabel.userInteractionEnabled = YES; self.contentView.userInteractionEnabled = NO; return self; @@ -205,11 +197,12 @@ _avatarImage.hidden = !_isFirst; } + // Not use [UIImage imageNamed], it takes too much time - NSString *imageName = outgoing ? @"color_A.png" : @"color_D.png"; - _backgroundColorImage.image = - [UIImage imageWithContentsOfFile:[NSString stringWithFormat:@"%@/%@",[[NSBundle mainBundle] bundlePath],imageName]]; - + _backgroundColorImage.image = nil; + _backgroundColorImage.backgroundColor = outgoing ? [UIColor color:@"A"] : [UIColor color:@"D"]; + + // set maskedCorners if (@available(iOS 11.0, *)) { _backgroundColorImage.layer.cornerRadius = 10; @@ -255,6 +248,7 @@ frame.origin.y = _isFirst ? 20 : 0; _innerView.frame = frame; + [_messageText setAccessibilityLabel:outgoing ? @"Outgoing message" : @"Incoming message"]; if (outgoing && (state == LinphoneChatMessageStateDeliveredToUser || state == LinphoneChatMessageStateDisplayed || @@ -262,6 +256,23 @@ [self displayImdmStatus:state]; } else [self displayImdmStatus:LinphoneChatMessageStateInProgress]; + + if (linphone_chat_message_is_reply(_message)) { + if (_replyView == nil) { + _replyView = [[UIChatReplyBubbleView alloc] initWithNibName:@"UIChatReplyBubbleView" bundle:nil]; + [self.innerView addSubview:_replyView.view]; + } + _replyView.view.hidden = false; + CGRect replyFrame = CGRectMake(_contactDateLabel.frame.origin.x, _contactDateLabel.frame.origin.y+_contactDateLabel.frame.size.height,self.contactDateLabel.frame.size.width, REPLY_CHAT_BUBBLE_HEIGHT); + _replyView.view.frame = replyFrame; + [_replyView configureForMessage:linphone_chat_message_get_reply_message(_message) withDimissBlock:^{} hideDismiss:true withClickBlock:^{ + [_tableController scrollToMessage:linphone_chat_message_get_reply_message(_message)]; + }]; + } else { + if (_replyView) + _replyView.view.hidden = true; + } + } - (void)setEditing:(BOOL)editing { @@ -269,7 +280,6 @@ } - (void)setEditing:(BOOL)editing animated:(BOOL)animated { - _messageText.userInteractionEnabled = !editing; _resendRecognizer.enabled = !editing; } @@ -300,94 +310,6 @@ } } -- (void)onLime { - /*if (!_LIMEKO.hidden) - [self displayLIMEWarning];*/ -} - -- (void)onResend { - - if (!linphone_core_is_network_reachable(LC)) { - [PhoneMainView.instance presentViewController:[LinphoneUtils networkErrorView:@"send a message"] animated:YES completion:nil]; - //return; - } - - if (_message == nil || !linphone_chat_message_is_outgoing(_message)) - return; - - LinphoneChatMessageState state = linphone_chat_message_get_state(_message); - if (state != LinphoneChatMessageStateNotDelivered && state != LinphoneChatMessageStateFileTransferError) - return; - - const bctbx_list_t *contents = linphone_chat_message_get_contents(_message); - LinphoneContent *voiceContent = [UIChatBubbleTextCell voiceContent:_message]; - size_t contentCount = bctbx_list_size(contents); - if (voiceContent) - contentCount--; - - BOOL multiParts = ((linphone_chat_message_get_text_content(_message) != NULL) ? contentCount > 2 : contentCount > 1); - - if (multiParts) { - FileContext *newfileContext = [[FileContext alloc] init]; - [newfileContext clear]; - NSMutableDictionary *encrptedFilePaths = encrptedFilePaths = [LinphoneManager getMessageAppDataForKey:@"encryptedfiles" inMessage:_message]; - int i; - const bctbx_list_t *it; - for (it = contents, i=0; it != NULL; it=bctbx_list_next(it)){ - LinphoneContent *content = (LinphoneContent *)it->data; - if (linphone_content_is_voice_recording(content)) { - continue; - } - if (linphone_content_is_file_transfer(content) || linphone_content_is_file(content)){ - NSString *name = [NSString stringWithUTF8String:linphone_content_get_name(content)]; - NSString *filePath = [encrptedFilePaths valueForKey:name]; - if (filePath == NULL) { - filePath = [LinphoneManager validFilePath:name]; - } - [newfileContext addObject:[NSData dataWithContentsOfFile:filePath] name:name type:[NSString stringWithUTF8String:linphone_content_get_type(content)]]; - } - } - [self onDelete]; - dispatch_async(dispatch_get_main_queue(), ^ { - const char *text = linphone_chat_message_get_text_content(_message); - [_chatRoomDelegate resendMultiFiles:newfileContext message: text? [NSString stringWithUTF8String:text]: NULL voiceContent:voiceContent]; - }); - return; - } - if (!voiceContent && contentCount == 1 && linphone_chat_message_get_file_transfer_information(_message) != NULL) { - NSString *localImage = [LinphoneManager getMessageAppDataForKey:@"localimage" inMessage:_message]; - NSString *localVideo = [LinphoneManager getMessageAppDataForKey:@"localvideo" inMessage:_message]; - NSString *localFile = [LinphoneManager getMessageAppDataForKey:@"localfile" inMessage:_message]; - NSString *filePath = [LinphoneManager getMessageAppDataForKey:@"encryptedfile" inMessage:self.message]; - - [self onDelete]; - dispatch_async(dispatch_get_main_queue(), ^ { - NSData *data = NULL; - if (filePath) { - data = [NSData dataWithContentsOfFile:filePath]; - } - const char *text = linphone_chat_message_get_text_content(_message); - NSString *str = text ? [NSString stringWithUTF8String:text] : NULL; - if (localImage) { - [_chatRoomDelegate resendFile: (data?:[ChatConversationView getFileData:localImage]) withName:localImage type:@"image" key:@"localimage" message:str voiceContent:voiceContent]; - } else if (localVideo) { - [_chatRoomDelegate resendFile:(data?:[ChatConversationView getFileData:localVideo]) withName:localVideo type:@"video" key:@"localvideo" message:str voiceContent:voiceContent]; - } else { - [_chatRoomDelegate resendFile:(data?:[ChatConversationView getFileData:localFile]) withName:localFile type:@"image" key:@"localfile" message:str voiceContent:voiceContent]; - } - }); - } else { - [self onDelete]; - double delayInSeconds = 0.4; - dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); - dispatch_after(popTime, dispatch_get_main_queue(), ^(void) { - NSString *text = self.textMessage; - if (voiceContent && [text isEqualToString:@"🗻"]) - text = nil; - [_chatRoomDelegate resendChat:text withExternalUrl:nil voiceContent:voiceContent]; - }); - } -} #pragma mark - State changed handling static void message_status(LinphoneChatMessage *msg, LinphoneChatMessageState state) { LOGI(@"State for message [%p] changed to %s", msg, linphone_chat_message_state_to_string(state)); @@ -442,9 +364,15 @@ static const CGFloat CELL_MIN_HEIGHT = 65.0f; static const CGFloat CELL_MIN_WIDTH = 190.0f; static const CGFloat CELL_MESSAGE_X_MARGIN = 68 + 10.0f; static const CGFloat CELL_MESSAGE_Y_MARGIN = 44; +static const CGFloat REPLY_CHAT_BUBBLE_HEIGHT = 120; +static const CGFloat REPLY_OR_FORWARD_TAG_HEIGHT = 18; + + (CGSize)ViewHeightForMessage:(LinphoneChatMessage *)chat withWidth:(int)width { - return [self ViewHeightForMessageText:chat withWidth:width textForImdn:nil]; + CGSize size = [self ViewHeightForMessageText:chat withWidth:width textForImdn:nil]; + size.height += linphone_chat_message_is_forward(chat) || linphone_chat_message_is_reply(chat) ? REPLY_OR_FORWARD_TAG_HEIGHT : 0; + size.height += linphone_chat_message_is_reply(chat) ? REPLY_CHAT_BUBBLE_HEIGHT+5 : 0; + return size; } + (CGSize)ViewHeightForFile:(int)width { @@ -456,20 +384,38 @@ static const CGFloat CELL_MESSAGE_Y_MARGIN = 44; } ++(NSString *)formattedDuration:(long)valueMs { + return [NSString stringWithFormat:@"%02ld:%02ld", valueMs/ 60, (valueMs % 60) ]; +} + ++(NSString *) recordingDuration:(NSString *) _voiceRecordingFile{ + NSError *error = nil; + AVAudioPlayer* utilityPlayer = [[AVAudioPlayer alloc]initWithContentsOfURL:[NSURL URLWithString:_voiceRecordingFile] error:&error]; // Workaround as opening multiple linphone_players at the same time can cause crash (here for example layout refreshed whilst a voice memo is playing + return [self formattedDuration:utilityPlayer.duration]; + utilityPlayer = nil; +} + + (UIImage *)getImageFromFileName:(NSString *)fileName { NSString *extension = [[fileName.lowercaseString componentsSeparatedByString:@"."] lastObject]; UIImage *image; - if ([extension isEqualToString:@"pdf"]) - image = [UIImage imageNamed:@"file_pdf_default"]; - else if ([@[@"png", @"jpg", @"jpeg", @"bmp", @"heic"] containsObject:extension]) - image = [UIImage imageNamed:@"file_picture_default"]; - else if ([@[@"mkv", @"avi", @"mov", @"mp4"] containsObject:extension]) - image = [UIImage imageNamed:@"file_video_default"]; - else if ([@[@"wav", @"au", @"m4a"] containsObject:extension]) - image = [UIImage imageNamed:@"file_audio_default"]; - else - image = [UIImage imageNamed:@"file_default"]; - return [SwiftUtil textToImageWithDrawText:fileName inImage:image]; + NSString * text = fileName; + if ([fileName containsString:@"voice-recording"]) { + image = [UIImage imageNamed:@"file_voice_default"]; + text = [self recordingDuration:[LinphoneManager validFilePath:fileName]]; + } else { + if ([extension isEqualToString:@"pdf"]) + image = [UIImage imageNamed:@"file_pdf_default"]; + else if ([@[@"png", @"jpg", @"jpeg", @"bmp", @"heic"] containsObject:extension]) + image = [UIImage imageNamed:@"file_picture_default"]; + else if ([@[@"mkv", @"avi", @"mov", @"mp4"] containsObject:extension]) + image = [UIImage imageNamed:@"file_video_default"]; + else if ([@[@"wav", @"au", @"m4a"] containsObject:extension]) + image = [UIImage imageNamed:@"file_audio_default"]; + else + image = [UIImage imageNamed:@"file_default"]; + } + + return [SwiftUtil textToImageWithDrawText:text inImage:image]; } + (UIImage *)getImageFromContent:(LinphoneContent *)content filePath:(NSString *)filePath; { @@ -630,7 +576,8 @@ static const CGFloat CELL_MESSAGE_Y_MARGIN = 44; size = [self computeBoundingBox:messageText size:CGSizeMake(width - CELL_MESSAGE_X_MARGIN - 4, CGFLOAT_MAX) font:messageFont]; - } else { + size.width += 4; + } else { // Just file or file with text LinphoneContent *fileContent = linphone_chat_message_get_file_transfer_information(chat); NSString *localImage = [LinphoneManager getMessageAppDataForKey:@"localimage" inMessage:chat]; NSString *localFile = [LinphoneManager getMessageAppDataForKey:@"localfile" inMessage:chat]; @@ -671,10 +618,11 @@ static const CGFloat CELL_MESSAGE_Y_MARGIN = 44; } else if (voiceContent){ return [self addVoicePlayerToSize:[self ViewHeightForFile:width] withMargins:true]; } else { - image = [UIChatBubbleTextCell getImageFromFileName:fileName]; + image = nil; + originalImageSize = CGSizeMake(140, 140); } - - originalImageSize = image.size; + if (image != nil) + originalImageSize = image.size; } else { if (!localImage && !localVideo) { //We are loading the image @@ -781,15 +729,45 @@ static const CGFloat CELL_MESSAGE_Y_MARGIN = 44; int origin_x; bubbleFrame.size = [self.class ViewSizeForMessage:_message withWidth:available_width]; + if (linphone_chat_message_is_reply(_message)) { + bubbleFrame.size.width = MAX(bubbleFrame.size.width, 300); + } if (tableView.isEditing) { origin_x = 0; } else { origin_x = (is_outgoing ? self.frame.size.width - bubbleFrame.size.width : 0); } + + CGRect r = _messageText.frame; + r.origin.y = linphone_chat_message_is_reply(_message) ? _replyView.view.frame.origin.y + _replyView.view.frame.size.height + 5 : 3; + _messageText.frame = r; + + r = _messageText.frame; + r.origin.y = linphone_chat_message_is_forward(_message) ? _contactDateLabel.frame.origin.y + _contactDateLabel.frame.size.height + 5 : r.origin.y; + _messageText.frame = r; + + _replyTransferIcon.hidden = ! linphone_chat_message_is_reply(_message) && !linphone_chat_message_is_forward(_message); + _replyTransferLabel.hidden = ! linphone_chat_message_is_reply(_message) && !linphone_chat_message_is_forward(_message); + + if (linphone_chat_message_is_reply(_message)) { + CGRect replyFrame = CGRectMake(10, _replyTransferLabel.frame.origin.y+_replyTransferLabel.frame.size.height+5,MAX(self.contactDateLabel.frame.size.width-20,180), REPLY_CHAT_BUBBLE_HEIGHT); + _replyView.view.frame = replyFrame; + _replyTransferIcon.image = [UIImage imageNamed:@"menu_reply_default"]; + _replyTransferLabel.text = NSLocalizedString(@"Answer",nil); + _replyTransferLabel.textColor = [UIColor lightGrayColor]; + } + + if (linphone_chat_message_is_forward(_message)) { + _replyTransferIcon.image = [UIImage imageNamed:@"menu_forward_default"]; + _replyTransferLabel.text = NSLocalizedString(@"Transferred",nil); + _replyTransferLabel.textColor = [UIColor darkGrayColor]; + } bubbleFrame.origin.x = origin_x; _bubbleView.frame = bubbleFrame; + + } } @@ -817,4 +795,173 @@ static const CGFloat CELL_MESSAGE_Y_MARGIN = 44; return mediaSize; } + +// Message popup menu +// Copy text -> if has text +// Transfer -> always +// Reply -> always +// IMDM Status -> out +// Delete -> always + + +-(void) buildActions { + LinphoneChatMessage *message = self.message; + _messageActionsTitles = [[NSMutableArray alloc] init]; + _messageActionsBlocks = [[NSMutableArray alloc] init]; + _messageActionsIcons = [[NSMutableArray alloc] init]; + + UIChatBubbleTextCell *thiz = self; + + LinphoneChatMessageState state = linphone_chat_message_get_state(self.message); + if (state == LinphoneChatMessageStateNotDelivered || state == LinphoneChatMessageStateFileTransferError) { + [_messageActionsTitles addObject:NSLocalizedString(@"Resend", nil)]; + [_messageActionsIcons addObject:@"menu_resend_default"]; + [_messageActionsBlocks addObject:^{ + [thiz dismissPopup]; + if (!linphone_core_is_network_reachable(LC)) { + [PhoneMainView.instance presentViewController:[LinphoneUtils networkErrorView:@"send a message"] animated:YES completion:nil]; + return; + } + linphone_chat_message_send(message); + }]; + } + + + if (linphone_chat_message_get_utf8_text(message)) { + [_messageActionsTitles addObject:NSLocalizedString(@"Copy text", nil)]; + [_messageActionsIcons addObject:@"menu_copy_text_default"]; + [_messageActionsBlocks addObject:^{ + [thiz dismissPopup]; + [UIPasteboard.generalPasteboard setString:[NSString stringWithUTF8String:linphone_chat_message_get_text_content(message)]]; + }]; + } + + + [_messageActionsTitles addObject:NSLocalizedString(@"Forward", nil)]; + [_messageActionsIcons addObject:@"menu_forward_default"]; + [_messageActionsBlocks addObject:^{ + [thiz dismissPopup]; + VIEW(ChatConversationView).pendingForwardMessage = message; + [PhoneMainView.instance changeCurrentView:VIEW(ChatsListView).compositeViewDescription]; + }]; + + + + [_messageActionsTitles addObject:NSLocalizedString(@"Reply", nil)]; + [_messageActionsIcons addObject:@"menu_reply_default"]; + [_messageActionsBlocks addObject:^{ + [thiz dismissPopup]; + [VIEW(ChatConversationView) initiateReplyViewForMessage:message]; + }]; + + if (linphone_chat_message_is_outgoing(self.message) && linphone_chat_room_get_nb_participants(linphone_chat_message_get_chat_room(self.message)) > 1) { + [_messageActionsTitles addObject:NSLocalizedString(@"Infos", nil)]; + [_messageActionsIcons addObject:@"menu_info"]; + [_messageActionsBlocks addObject:^{ + [thiz dismissPopup]; + ChatConversationImdnView *view = VIEW(ChatConversationImdnView); + view.msg = message; + [PhoneMainView.instance changeCurrentView:view.compositeViewDescription]; + }]; + } + + [_messageActionsTitles addObject:NSLocalizedString(@"Delete", nil)]; + [_messageActionsIcons addObject:@"menu_delete"]; + [_messageActionsBlocks addObject:^{ + [thiz dismissPopup]; + linphone_chat_room_delete_message(linphone_chat_message_get_chat_room(message), message); + [VIEW(ChatConversationView).tableController reloadData]; + }]; +} + +-(void) onPopupMenuPressed { + if (_popupMenu != nil) + [self dismissPopup]; + + if (!self.popupMenuAllowed) + return; + + + [VIEW(ChatConversationView).tableController dismissMessagesPopups]; + self.innerView.layer.borderWidth = 3; + self.innerView.layer.borderColor = [UIColor color:@"A"].CGColor; + [self buildActions]; + int width = 250; + int cellHeight = 44; + int numberOfItems = (int) _messageActionsTitles.count; + CGRect screenRect = UIScreen.mainScreen.bounds; + int menuHeight = numberOfItems * cellHeight; + + CGRect frame = CGRectMake( + linphone_chat_message_is_outgoing(self.message) ? screenRect.size.width - width - 10 : 10, + (self.frame.origin.y + self.frame.size.height) - [VIEW(ChatConversationView).tableController .tableView contentOffset].y > screenRect.size.height /2 ? self.frame.origin.y - menuHeight - 10: self.frame.origin.y + self.frame.size.height, + width, + menuHeight); + + _popupMenu = [[UITableView alloc]initWithFrame:frame]; + _popupMenu.dataSource = self; + _popupMenu.delegate = self; + _popupMenu.layer.shadowColor = [UIColor lightGrayColor].CGColor; + _popupMenu.layer.shadowOpacity = 0.5; + _popupMenu.layer.shadowOffset = CGSizeZero; + _popupMenu.layer.shadowRadius = 5; + _popupMenu.layer.masksToBounds = false; + _popupMenu.tableFooterView = [UIView new]; + _popupMenu.editing = NO; + _popupMenu.userInteractionEnabled = true; + [_popupMenu reloadData]; + [VIEW(ChatConversationView).tableController.view addSubview:_popupMenu]; + UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapOutsideMenu:)]; + tapGestureRecognizer.cancelsTouchesInView = NO; + tapGestureRecognizer.numberOfTapsRequired = 1; + [VIEW(ChatConversationView).tableController.view addGestureRecognizer:tapGestureRecognizer]; +} + +-(void) dismissPopup { + if (!_popupMenu) + return; + [_popupMenu removeFromSuperview]; + _popupMenu = nil; + self.innerView.layer.borderWidth = 0; + [self setNeedsLayout]; +} + + +-(void) tapOutsideMenu:(UITapGestureRecognizer *) g { + CGPoint p = [g locationInView:VIEW(ChatConversationView).tableController.view]; + if (!CGRectContainsPoint(_popupMenu.frame,p)) { + [self dismissPopup]; + } +} + +-(void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + void (^ myblock)(void) = [_messageActionsBlocks objectAtIndex:indexPath.row]; + [self dismissPopup]; + myblock(); +} + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return 1; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return [_messageActionsTitles count]; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + UITableViewCell *cell = [[UITableViewCell alloc] init]; + cell.imageView.image = [[UIImage imageNamed:[_messageActionsIcons objectAtIndex:indexPath.row]] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + cell.textLabel.text = [_messageActionsTitles objectAtIndex:indexPath.row]; + cell.imageView.contentMode = UIViewContentModeScaleAspectFit; + if ([[_messageActionsIcons objectAtIndex:indexPath.row] isEqualToString:@"menu_delete"]) { + cell.textLabel.textColor = UIColor.redColor; + cell.imageView.tintColor = UIColor.redColor; + } else { + cell.imageView.tintColor = PhoneMainView.instance.darkMode ? UIColor.whiteColor : UIColor.blackColor; + } + return cell; +} + + + @end diff --git a/Classes/LinphoneUI/UIChatCell.h b/Classes/LinphoneUI/UIChatCell.h index d5d0327b9..29343ea40 100644 --- a/Classes/LinphoneUI/UIChatCell.h +++ b/Classes/LinphoneUI/UIChatCell.h @@ -38,6 +38,8 @@ @property(weak, nonatomic) IBOutlet UILabel *unreadCountLabel; @property (weak, nonatomic) IBOutlet UIImageView *imdmIcon; @property (weak, nonatomic) IBOutlet UIImageView *ephemeral; +@property (weak, nonatomic) IBOutlet UIImageView *forwardIcon; + - (id)initWithIdentifier:(NSString*)identifier; diff --git a/Classes/LinphoneUI/UIChatCell.m b/Classes/LinphoneUI/UIChatCell.m index 299898dbf..3fc01de59 100644 --- a/Classes/LinphoneUI/UIChatCell.m +++ b/Classes/LinphoneUI/UIChatCell.m @@ -46,6 +46,7 @@ - (void)setChatRoom:(LinphoneChatRoom *)achat { chatRoom = achat; [self update]; + [self.forwardIcon setImageNamed:@"forward_message_default" tintColor:PhoneMainView.instance.darkMode ? UIColor.whiteColor : UIColor.darkGrayColor]; } #pragma mark - diff --git a/Classes/LinphoneUI/UIChatReplyBubbleView.h b/Classes/LinphoneUI/UIChatReplyBubbleView.h new file mode 100644 index 000000000..b9c175ae2 --- /dev/null +++ b/Classes/LinphoneUI/UIChatReplyBubbleView.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2010-2020 Belledonne Communications SARL. + * + * This file is part of linphone-iphone + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface UIChatReplyBubbleView : UIViewController +@property (weak, nonatomic) IBOutlet UILabel *senderName; +@property (weak, nonatomic) IBOutlet UIButton *dismissButton; +@property (weak, nonatomic) IBOutlet UIView *leftBar; +@property (weak, nonatomic) IBOutlet UIView *rightBar; +@property LinphoneChatMessage *message; +@property (weak, nonatomic) IBOutlet UILabel *textContent; +@property void (^ dismissAction)(void); +@property void (^ clickAction)(void); +@property (weak, nonatomic) IBOutlet UICollectionView *contentCollection; +@property NSArray *dataContent; +@property (weak, nonatomic) IBOutlet UILabel *originalMessageGone; + +-(void) configureForMessage:(LinphoneChatMessage *)message withDimissBlock:(void (^)(void))dismissBlock hideDismiss:(BOOL)hideDismiss withClickBlock:(void (^)(void))clickBlock; +@end + +NS_ASSUME_NONNULL_END diff --git a/Classes/LinphoneUI/UIChatReplyBubbleView.m b/Classes/LinphoneUI/UIChatReplyBubbleView.m new file mode 100644 index 000000000..e6e523561 --- /dev/null +++ b/Classes/LinphoneUI/UIChatReplyBubbleView.m @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2010-2020 Belledonne Communications SARL. + * + * This file is part of linphone-iphone + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#import "UIChatReplyBubbleView.h" +#import "linphoneapp-Swift.h" +#import "Utils.h" + +@interface UIChatReplyBubbleView () + +@end + +@implementation UIChatReplyBubbleView + + +- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { + return [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; +} + +- (instancetype)initWithCoder:(NSCoder *)coder +{ + self = [super initWithCoder:coder]; + return self; +} + +-(void) viewDidLoad { + _contentCollection.dataSource = self; + [_contentCollection registerClass:UICollectionViewCell.class forCellWithReuseIdentifier:@"dataContent"]; +} + + +-(void) configureForMessage:(LinphoneChatMessage *)message withDimissBlock:(void (^)(void))dismissBlock hideDismiss:(BOOL)hideDismiss withClickBlock:(void (^)(void))clickBlock{ + if (!message) { + _textContent.hidden = true; + _dismissButton.hidden = true; + _contentCollection.hidden = true; + _senderName.hidden = true; + _originalMessageGone.hidden = false; + return; + } + if (hideDismiss) { + self.view.layer.cornerRadius = 10; + self.view.layer.masksToBounds = true; + } + _originalMessageGone.hidden = true; + self.message = message; + self.dataContent = [self loadDataContent]; + NSString *sender = [FastAddressBook displayNameForAddress:linphone_chat_message_get_from_address(message)]; + _senderName.text = sender; + const char * text = linphone_chat_message_get_text_content(message); + if (text && strlen(text) == 0) + text = nil; + _textContent.text = text ? [NSString stringWithUTF8String:text] : @""; + _dismissButton.hidden = hideDismiss; + _dismissAction = dismissBlock; + _clickAction = clickBlock; + if (hideDismiss) { + UITapGestureRecognizer *singleFingerTap = + [[UITapGestureRecognizer alloc] initWithTarget:self + action:@selector(onClick)]; + [self.view addGestureRecognizer:singleFingerTap]; + } + else + [_dismissButton addTarget:self action:@selector(dismissClick) forControlEvents:UIControlEventTouchUpInside]; + + + self.view.backgroundColor = hideDismiss ? UIColor.whiteColor :(linphone_chat_message_is_outgoing(message) ? [[UIColor color:@"A"] colorWithAlphaComponent:0.2] : [[UIColor color:@"D"] colorWithAlphaComponent:0.2]); + _leftBar.backgroundColor = linphone_chat_message_is_outgoing(message) ? [UIColor color:@"A"] : [UIColor color:@"D"]; + _leftBar.hidden = !hideDismiss; + _rightBar.backgroundColor = self.view.backgroundColor; + + + // Resize frame -> text or content only = 100, 145 otherwise + _contentCollection.hidden = self.dataContent.count == 0; + + CGRect r = self.view.frame ; + r.size.width = self.view.superview.frame.size.width; + self.view.frame = r; + + if (self.dataContent.count == 0) { + CGRect r = _textContent.frame; + r.origin.y = _contentCollection.frame.origin.y; + r.size.height = 87; + _textContent.frame = r; + } + + if (text == nil) { + CGRect r = _contentCollection.frame; + r.origin.y = 30; + _contentCollection.frame = r; + } +} + + +-(NSArray *) loadDataContent { + NSMutableArray *result = [[NSMutableArray alloc] init]; + const bctbx_list_t *contents = linphone_chat_message_get_contents(_message); + const char * text = linphone_chat_message_get_utf8_text(_message); + if (text && bctbx_list_size(contents) == 1) + return result; + + for (const bctbx_list_t * it = contents; it != NULL; it=bctbx_list_next(it)){ + LinphoneContent *content = (LinphoneContent *)it->data; + if (linphone_content_is_text(content)) + continue; + NSString *name = [NSString stringWithUTF8String:linphone_content_get_name(content)]; + NSMutableDictionary *encrptedFilePaths = encrptedFilePaths = [LinphoneManager getMessageAppDataForKey:@"encryptedfiles" inMessage:_message]; + NSString *filePath = encrptedFilePaths ? [encrptedFilePaths valueForKey:name] : nil; + if (filePath == NULL) { + filePath = [LinphoneManager validFilePath:name]; + } + [result addObject:[UIChatBubbleTextCell getImageFromContent:content filePath:filePath]]; + } + return result; +} + +-(void) dismissClick { + _dismissAction(); +} + +-(void) onClick { + _clickAction(); +} + +-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { + return 1; +} + +- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { + return self.dataContent.count; +} + +- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { + UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"dataContent" forIndexPath:indexPath]; + UIImageView *img = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 60, 60)]; + img.image = [self.dataContent objectAtIndex:indexPath.row]; + [cell.contentView addSubview:img]; + return cell; +} + + + + +@end diff --git a/Classes/LinphoneUI/UIConfirmationDialog.h b/Classes/LinphoneUI/UIConfirmationDialog.h index ad0a0b5b0..c2d9feadc 100644 --- a/Classes/LinphoneUI/UIConfirmationDialog.h +++ b/Classes/LinphoneUI/UIConfirmationDialog.h @@ -45,6 +45,7 @@ typedef void (^UIConfirmationBlock)(void); @property(weak, nonatomic) IBOutlet UIRoundBorderedButton *cancelButton; @property (weak, nonatomic) IBOutlet UIImageView *securityImage; +@property (weak, nonatomic) IBOutlet UIImageView *forwardImage; @property(weak, nonatomic) IBOutlet UIRoundBorderedButton *confirmationButton; @property (weak, nonatomic) IBOutlet UIView *authView; @property(weak, nonatomic) IBOutlet UILabel *titleLabel; diff --git a/Classes/LinphoneUI/en.lproj/UIChatReplyBubbleView.strings b/Classes/LinphoneUI/en.lproj/UIChatReplyBubbleView.strings new file mode 100644 index 000000000..cfa28ba85 --- /dev/null +++ b/Classes/LinphoneUI/en.lproj/UIChatReplyBubbleView.strings @@ -0,0 +1,9 @@ + +/* Class = "UILabel"; text = "Original message removed"; ObjectID = "B26-sw-o4w"; */ +"B26-sw-o4w.text" = "Original message does not exist anymore."; + +/* Class = "UILabel"; text = "Label"; ObjectID = "Czc-VH-qiH"; */ +"Czc-VH-qiH.text" = "Label"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "uuW-tW-1Sj"; */ +"uuW-tW-1Sj.text" = "Label"; diff --git a/Classes/LinphoneUI/fr.lproj/UIChatReplyBubbleView.strings b/Classes/LinphoneUI/fr.lproj/UIChatReplyBubbleView.strings new file mode 100644 index 000000000..de5a5c443 --- /dev/null +++ b/Classes/LinphoneUI/fr.lproj/UIChatReplyBubbleView.strings @@ -0,0 +1,9 @@ + +/* Class = "UILabel"; text = "Original message removed"; ObjectID = "B26-sw-o4w"; */ +"B26-sw-o4w.text" = "Le message original n'existe plus."; + +/* Class = "UILabel"; text = "Label"; ObjectID = "Czc-VH-qiH"; */ +"Czc-VH-qiH.text" = "Label"; + +/* Class = "UILabel"; text = "Label"; ObjectID = "uuW-tW-1Sj"; */ +"uuW-tW-1Sj.text" = "Label"; diff --git a/Classes/PhoneMainView.h b/Classes/PhoneMainView.h index 60633724e..adfe87b5a 100644 --- a/Classes/PhoneMainView.h +++ b/Classes/PhoneMainView.h @@ -121,6 +121,7 @@ - (BOOL)isIphoneXDevice; + (int)iphoneStatusBarHeight; +-(BOOL) darkMode; @end diff --git a/Classes/PhoneMainView.m b/Classes/PhoneMainView.m index 680712541..793243495 100644 --- a/Classes/PhoneMainView.m +++ b/Classes/PhoneMainView.m @@ -957,4 +957,15 @@ void main_view_chat_room_state_changed(LinphoneChatRoom *cr, LinphoneChatRoomSta [controller dismissModalViewControllerAnimated:YES]; } +#pragma mark - Light/Dark mode + +-(BOOL) darkMode { + if (@available(iOS 13.0, *)) { + UITraitCollection *collection = [UITraitCollection currentTraitCollection]; + return collection.userInterfaceStyle == UIUserInterfaceStyleDark; + } else { + return false; + } +} + @end diff --git a/Classes/SwiftUtil.swift b/Classes/SwiftUtil.swift index ff1701b87..ff9278263 100644 --- a/Classes/SwiftUtil.swift +++ b/Classes/SwiftUtil.swift @@ -1,46 +1,69 @@ -// -// SwiftUtil.swift -// linphone -// -// Created by Tof on 23/07/2021. -// +/* +* Copyright (c) 2010-2020 Belledonne Communications SARL. +* +* This file is part of linphone-iphone +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*/ import UIKit @objc class SwiftUtil: NSObject { - + @objc static func textToImage(drawText text: String, inImage image: UIImage) -> UIImage { let textColor = UIColor.black - let textFont = UIFont(name: "Helvetica", size: 12)! + let fontMax = UIFont.systemFont(ofSize: 30) let backgroundColor = UIColor.white let size = CGSize(width: 120, height: 120) - + let scale = UIScreen.main.scale UIGraphicsBeginImageContextWithOptions(size, false, scale) let context = UIGraphicsGetCurrentContext() backgroundColor.setFill() context!.fill(CGRect(x: 0, y: 0, width: size.width, height: size.height)) - - let paragraph = NSMutableParagraphStyle() - paragraph.alignment = .center - let textFontAttributes = [ - NSAttributedString.Key.font: textFont, - NSAttributedString.Key.foregroundColor: textColor, - NSAttributedString.Key.paragraphStyle: paragraph, - ] as [NSAttributedString.Key : Any] + image.draw(in: CGRect(origin: CGPoint(x: size.width/2 - (image.size.width)/2,y: 5), size: image.size)) + + let label = UILabel(frame: CGRect(x: 0,y: 0,width: size.width,height: 50)) + label.numberOfLines = 0 + label.font = fontMax + label.adjustsFontSizeToFitWidth = true + label.text = text + label.textColor = textColor + label.textAlignment = .center + label.allowsDefaultTighteningForTruncation = true + label.lineBreakMode = .byTruncatingTail + imageWithLabel(label: label).draw(in: CGRect(origin: CGPoint(x:0,y: 60), size: CGSize(width: size.width,height: 50))) + let view = UIView(frame: CGRect(x: 0,y: 0,width: size.width,height: 50)) + view.addSubview(label) + label.sizeToFit() + - image.draw(in: CGRect(origin: CGPoint(x: size.width/2 - (image.size.width)/2,y: 15), size: image.size)) - - let rect = CGRect(origin: CGPoint(x: 0,y: 70), size: CGSize(width: size.width, height: 30)) - text.draw(in: rect, withAttributes: textFontAttributes) - let newImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() - + return newImage! } + static func imageWithLabel(label: UILabel) -> UIImage { + UIGraphicsBeginImageContextWithOptions(label.frame.size, false, 0.0) + label.layer.render(in: UIGraphicsGetCurrentContext()!) + let img = UIGraphicsGetImageFromCurrentImageContext()! + UIGraphicsEndImageContext() + return img + } + } diff --git a/Classes/Utils/FileTransferDelegate.h b/Classes/Utils/FileTransferDelegate.h index 21ff1d605..3fa22bb55 100644 --- a/Classes/Utils/FileTransferDelegate.h +++ b/Classes/Utils/FileTransferDelegate.h @@ -24,11 +24,10 @@ @interface FileTransferDelegate : NSObject -- (void)uploadFileContent: (FileContext *)context forChatRoom:(LinphoneChatRoom *)chatRoom andVoiceContent:(LinphoneContent *)voiceContent; -- (void)uploadData:(NSData *)data forChatRoom:(LinphoneChatRoom *)chatRoom type:(NSString *)type subtype:(NSString *)subtype name:(NSString *)name key:(NSString *)key voiceContent:(LinphoneContent *)voiceContent; +- (void)uploadFileContent: (FileContext *)context forChatRoom:(LinphoneChatRoom *)chatRoom rootMessage:(LinphoneChatMessage *)rootMessage; +- (void)uploadData:(NSData *)data forChatRoom:(LinphoneChatRoom *)chatRoom type:(NSString *)type subtype:(NSString *)subtype name:(NSString *)name key:(NSString *)key rootMessage:(LinphoneChatMessage *)rootMessage; - (void)uploadImage:(UIImage *)image 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)uploadFile:(NSData *)data forChatRoom:(LinphoneChatRoom *)chatRoom withName:(NSString *)name rootMessage:(LinphoneChatMessage *)rootMessage; - (void)cancel; - (BOOL)download:(LinphoneChatMessage *)message; - (void)stopAndDestroy; diff --git a/Classes/Utils/FileTransferDelegate.m b/Classes/Utils/FileTransferDelegate.m index 9ad65457e..0738c54b4 100644 --- a/Classes/Utils/FileTransferDelegate.m +++ b/Classes/Utils/FileTransferDelegate.m @@ -102,7 +102,7 @@ static void file_transfer_progress_indication_send(LinphoneChatMessage *message, } } -- (void)uploadData:(NSData *)data forChatRoom:(LinphoneChatRoom *)chatRoom type:(NSString *)type subtype:(NSString *)subtype name:(NSString *)name key:(NSString *)key voiceContent:(LinphoneContent *)voiceContent{ +- (void)uploadData:(NSData *)data forChatRoom:(LinphoneChatRoom *)chatRoom type:(NSString *)type subtype:(NSString *)subtype name:(NSString *)name key:(NSString *)key rootMessage:(LinphoneChatMessage *)rootMessage{ if ([[LinphoneManager.instance fileTransferDelegates] containsObject:self]) { LOGW(@"fileTransferDelegates has already added %p", self); return; @@ -115,7 +115,8 @@ static void file_transfer_progress_indication_send(LinphoneChatMessage *message, linphone_content_set_subtype(content, [subtype UTF8String]); linphone_content_set_name(content, [name UTF8String]); linphone_content_set_file_path(content, [[LinphoneManager imagesDirectory] stringByAppendingPathComponent:name].UTF8String); - _message = linphone_chat_room_create_file_transfer_message(chatRoom, content); + _message = rootMessage; + linphone_chat_message_add_file_content(_message, content); BOOL isOneToOneChat = linphone_chat_room_get_capabilities(chatRoom) & LinphoneChatRoomCapabilitiesOneToOne; if (!isOneToOneChat && (_text!=nil && ![_text isEqualToString:@""])) linphone_chat_message_add_text_content(_message, [_text UTF8String]); @@ -125,17 +126,16 @@ static void file_transfer_progress_indication_send(LinphoneChatMessage *message, [LinphoneManager setValueInMessageAppData:name forKey:key inMessage:_message]; - if (voiceContent) - linphone_chat_message_add_content(_message, voiceContent); + LOGI(@"%p Uploading content from message %p", self, _message); linphone_chat_message_send(_message); } -- (void)uploadFileContent: (FileContext *)context forChatRoom:(LinphoneChatRoom *)chatRoom andVoiceContent:(LinphoneContent *)voiceContent{ +- (void)uploadFileContent: (FileContext *)context forChatRoom:(LinphoneChatRoom *)chatRoom rootMessage:(LinphoneChatMessage *)rootMessage{ [LinphoneManager.instance.fileTransferDelegates addObject:self]; - _message = linphone_chat_room_create_empty_message(chatRoom); + _message = rootMessage; NSMutableArray *names = [[NSMutableArray alloc] init]; NSMutableArray *types = [[NSMutableArray alloc] init]; @@ -165,8 +165,7 @@ static void file_transfer_progress_indication_send(LinphoneChatMessage *message, // todo indication progress [LinphoneManager setValueInMessageAppData:names forKey:@"multiparts" inMessage:_message]; [LinphoneManager setValueInMessageAppData:types forKey:@"multipartstypes" inMessage:_message]; - if (voiceContent) - linphone_chat_message_add_content(_message, voiceContent); + LOGI(@"%p Uploading content from message %p", self, _message); linphone_chat_message_send(_message); } @@ -175,21 +174,17 @@ static void file_transfer_progress_indication_send(LinphoneChatMessage *message, - (void)uploadImage:(UIImage *)image forChatRoom:(LinphoneChatRoom *)chatRoom withQuality:(float)quality { NSString *name = [NSString stringWithFormat:@"%li-%f.jpg", (long)image.hash, [NSDate timeIntervalSinceReferenceDate]]; NSData *data = UIImageJPEGRepresentation(image, quality); - [self uploadData:data forChatRoom:chatRoom type:@"image" subtype:@"jpg" name:name key:@"localimage" voiceContent:nil]; + [self uploadData:data forChatRoom:chatRoom type:@"image" subtype:@"jpg" name:name key:@"localimage" rootMessage:nil]; } -- (void)uploadVideo:(NSData *)data withassetId:(NSString *)phAssetId forChatRoom:(LinphoneChatRoom *)chatRoom { - NSString *name = [NSString stringWithFormat:@"IMG-%f.MOV", [NSDate timeIntervalSinceReferenceDate]]; - [self uploadData:data forChatRoom:chatRoom type:@"video" subtype:@"mov" name:name key:@"localvideo" voiceContent:nil]; -} -- (void)uploadFile:(NSData *)data forChatRoom:(LinphoneChatRoom *)chatRoom withName:(NSString *)name { +- (void)uploadFile:(NSData *)data forChatRoom:(LinphoneChatRoom *)chatRoom withName:(NSString *)name rootMessage:(LinphoneChatMessage *)rootMessage { NSURL *url = [ChatConversationView getFileUrl:name]; AVAsset *asset = [AVURLAsset URLAssetWithURL:url options:nil]; NSString *fileType = [[asset tracksWithMediaType:AVMediaTypeVideo] count] > 0 ? @"video" : @"file"; NSString *key = [ChatConversationView getKeyFromFileType:fileType fileName:name]; - [self uploadData:data forChatRoom:chatRoom type:fileType subtype:name.lastPathComponent name:name key:key voiceContent:nil]; + [self uploadData:data forChatRoom:chatRoom type:fileType subtype:name.lastPathComponent name:name key:key rootMessage:rootMessage]; } - (BOOL)download:(LinphoneChatMessage *)message { diff --git a/Classes/Utils/Utils.h b/Classes/Utils/Utils.h index d515926af..207521dc9 100644 --- a/Classes/Utils/Utils.h +++ b/Classes/Utils/Utils.h @@ -74,6 +74,13 @@ typedef enum { @end +@interface UIImageView (ImageWithTint) + +- (void)setImageNamed:(NSString *)name tintColor:(UIColor *)color; +- (void)setImageNamed:(NSString *)name tintColorLetter:(NSString *)letter; + +@end + @interface NSString (linphoneExt) - (NSString *)md5; @@ -111,6 +118,8 @@ typedef enum { - (UIColor *)darkerColor; ++(UIColor *)color:(NSString *)letter; + @end @interface UIImage (ForceDecode) diff --git a/Classes/Utils/Utils.m b/Classes/Utils/Utils.m index 7ae1fedca..b9cbfaaa1 100644 --- a/Classes/Utils/Utils.m +++ b/Classes/Utils/Utils.m @@ -575,6 +575,23 @@ @end + + +@implementation UIImageView (ImageWithTint) + +- (void)setImageNamed:(NSString *)name tintColor:(UIColor *)color { + self.image = [[UIImage imageNamed:name] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + self.tintColor = color; +} + +- (void)setImageNamed:(NSString *)name tintColorLetter:(NSString *)letter { + UIColor *color = [UIColor color:letter]; + self.image = [[UIImage imageNamed:name] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + self.tintColor = color; +} + +@end + @implementation NSString (md5) - (NSString *)md5 { @@ -804,6 +821,19 @@ return [self lumColor:0.75]; } + +static NSMutableDictionary *letterColors = nil; + ++(UIColor *)color:(NSString *)letter { + if (letterColors == nil) + letterColors = [[NSMutableDictionary alloc] init]; + if (![letterColors objectForKey:letter]) { + UIImage *colorImage = [UIImage imageWithContentsOfFile:[NSString stringWithFormat:@"%@/color_%@.png",[[NSBundle mainBundle] bundlePath],letter]]; + [letterColors setObject:[UIColor colorWithPatternImage:colorImage] forKey:letter]; + } + return [letterColors objectForKey:letter]; +} + @end @implementation UIImage (ForceDecode) diff --git a/Podfile b/Podfile index d5ec02b57..7c061bc30 100644 --- a/Podfile +++ b/Podfile @@ -5,7 +5,7 @@ source "https://github.com/CocoaPods/Specs.git" def all_pods if ENV['PODFILE_PATH'].nil? - pod 'linphone-sdk', '~> 5.1.0-alpha.11+950fc62' + pod 'linphone-sdk', '5.1.0-alpha.50+724b895' else pod 'linphone-sdk', :path => ENV['PODFILE_PATH'] # local sdk end diff --git a/Resources/fr.lproj/Localizable.strings b/Resources/fr.lproj/Localizable.strings index 318063fa67dcd23b218657727217bd4acbf5c768..42321344b9eb946a3366888fca31bb9287c3b85e 100644 GIT binary patch literal 29538 zcmd^|-E!PUlJB2j-T|Xn2ki}!3h(Td&e$WGqDJ(RvX-Rm@Cik1fZZSos~czo4N}zA z-tHsp-Q2A;ufaDtPjY_$%qpPJ-K6N4@y7Ysh*>v*LRM8)R^~rHs`M!PvF@5IpG@km zT4rUNO`0NK7So;Vha$VouZpZKc#&t5x;ihLdA=;`Dl6uBIm?!H_Sv(Y?Dee3+ajA> z7L#|`jmYV3tJhcgteis5ABSFcvhS)%k(Jinw62QB5T6&>S7NgF;{Kse8CKQ3=SGVI%rw;y-1S9RBB`Bh!DSy5fpH@xcN zc&GX*nZ@m2ve{NXZHkuRKh<2`RUg-wo4}!Bsd@a|L92!CjG5G(&E$Xl?$Kw@vK%tkO}Q+qi=7>x9#3yS zPMY!xw(FTUyv*C|r@E|)sqsLc(w&|xMfIYoyG4<-ofYTHq2gs|iE9vm>zWj%0Z>vN0L*5}Z2p=SP`%nMyOkaIoRwg_d zV!@(*Vc&c{N5b+8MBZN(U(ob-PyR96_ia=z8QOW>WQ(F+@bfyaBoP`nn{UcepLG{3 z_o8l=**`w{-J?H-EjKM%B;_n@uSHWc$9V=Jc42mnux7j~FRHpJreE7o{`foy*M}h7 z_R;MxY`=0+KF->*V!|%>zRBC7$(_dS$FDWTx7A*{iP~b)A;C9qtGCrjej`TpE!eyH z()T`sS~RnIg7i0Ut2VzGSju+{N<9}^{GF$pZmx5 zcrqy#p|$-in()|^GyCSeyyzNN!DZWag^_rzb+;vq3a)Q7?Sh_6=@jo5rG(QyJ{&Y@ zBc-FcSOe?DKATRBaV8EvW+uyOqFC8GL4EpL%{Vk)T%y^ts+*rN3+e6M{wqDb{n)@! z7=8P3cKgv9r=6V~yoR*VUj2A-na>w}zcdWrNsXgk>v#S^RdspU_#!wVwx5i~tXMkV zcEixadT3)2(h< z&dL^yq5ZcEujVzDroa$m+2b%SXmg$4`1taU{m#y{7EOU!_hw7v`bPSo>8eUPn7zM~ zoh<8x+YXp5Sw{b#&JA~UXB>%aZCUg03h3TX~xoD0u?}ttXtibAHraG!?NYP0z63h zm+kFGW43>@1zai`w5XJ_QGeb4*{^Ppe<$U$skWBcd5LjcUb0%}Ox`{|n4@A_Erux< zV37F0;0R<8q^tbm{&JU1bMqDmXCo{hNzc_Vt$dA703ix+0?sB5q7XH%@{J% zwk{#jgH>>`(8mp9G^Do78xWZ-vW*qEKe;q}hoy&EPqU�W<-hgZUz)3Hn(00=qiT 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 B?;ZdE literal 57484 zcmeI5+j3P`c82$Lzd`QQuDFc50$rWXmD2~yLT<|umz~Jzczr-ybcG598W*?Wg1TS9jcL>6i5VZp|MwQ(#?NowatAH$g(uzp zU0it_*ACh&+?k{MwZQdP!Na{q|4~`pLF4WI;vZrJYk$&4+zpDFA5K_5(s>#)F^W0$ zsn+$P@w>IS8wYjl~lrU1@ z_vv*YJ#67+(-6A+LtHs-zkjz}H&p}SOL5ohP?Tc`qJ2w;EqwO3+wi% zN)I1kP5%+IK?@HV(!nQ~)nizJI6aK(@cKMH{a`UgmvIkb?kSqApEbyK)=`u%W1+`k zJ>%6Pb2xhztG?HGIvMaJ3QuDM{3G4N;10Rt8T@+?-h&i|+&&G9GyUgrFFf?)>)LZ3 z>6)34EVd#98qzoIgYXL3zQ@n6;nfCwC*Au?D|orM5IlUmmiyD#*R*B5YL@Z1Ar5%+ zZOm)j!yfO1olEE6#i#iFvyt478x3p}I}zH*hMW?B90WlkTj8wIH~&{#jJQ9 z^BydIQ>5~%#pP!8Mk;W(qIAyl{W7)@yB@W8cih%0-PU_5;t?&ZqNkoUIgwin!h)K;+usnX-os@W4A~*V>+6_j*mhz;FM7Qs8Pf!-h{MGGp$qe zfehw@SS#H)=0Azgo(7NDZAK(>d%1CsF1MES)86~FEAA;W^?I1%op>qyM9+_6Wq8>^ zo6R%z-m)E^gKeOfd>9={gU=#TRB7D_3w<6}gr{T?%AbrJ#B7jQxI&&I`*sE++e6aK zri_bh=~ZyRRq!6_;8#lf>&D+!iv*%g&Oz+cS`$;@3m-!6!ZKsRcKogwo^)BcY?*tG zm!AH?VmqvFCp`Vy;%4EZUfm58OUgtye7DBb?YQQvSXVz9PL$ndUUk@3leQu$lZOz= zo%dY}TJmxG-Y`EK(>Vwlwo2uQbKKltypDB{Wec%#-uZYEcU``#BXN9>*l03xzpr2Ua2Vd^H~yj)3f;VZMP~i zo0rWm88>$)`n5KRFG#%dncO46e)RXt(Uv^9_p7+SO80t`7a9w;G_)#@B{%r0p{1ne6 z_9unicFpmF`d9U#WIj)whzZgw&!I-I5ls?qdd5ye;DpyE3l*Qqa4qou4tmI1k{&E@36=9PG z(j#w79UxgEI#z^#C>uBm3Sf^ShdlaK#PO}j%MQOc2i85h<2a&I+H>XZqp&ztadaB7Y-hu%t!quE{ikD(trmE% ztzR6~mC?n(Z$V9U0>MQ26W8Q*_L9BeNSP`;xmK6nsc-QKSK;bP3XP#Sv_kbQdy7@( zFtph1#nqM#_g-){u6{k?1<;U=ShMO)bpca;bL*&0=^0TAU`@&W>70;pD3%QSqjY~W(O-lSU7P6KwkVzKUo-~9tW8@l<_5ZC3GKJL~iOxAT4PN&y_@79d05+$%y#r z+FMmKd}?3eUi^f8*Hx*8WM$ul?E1(&)z`+$=+@9xXGN0pAL82S0WFv5F z30F%#orK9^gs8PvT!*Cel)W3`^>NMnMAKfAbJ4@-tC%@?PR+z>@w=>hrss1f7@dG` z?p>$NPd(8FQesjg|tq0Tj|4<5%nRM~SeJTaznU-QBD%RQMu=7B`( ztZ(=%r3UVO-_W;a2vq3Wov6O*ESb(axSA5)2`at=%T<(Km#61l8FBCiJZ-bN$d%Mt zI?aq=IQfHW54@S|TKyD7b0KuUZ;Pjb_}ITAL`uM2d&ao0NJMmRk=#d;2hCU?UjN@q z93a{WV};g|!4!6beD<;#qkz1}H#3hBn{8FN9u9b0lnZ|vD=QX5mK zF{;mdT&2?OdEMEV;{d%kI8!{R^#=JgSj(CepNa3*LTU@kBd;K{#M-j+R!2K$s8pRN zRCIs}S@|@5)uwm_ITDDc+D6)yoQb?iF{O(&J!tlZos$vOIrJ$}JU=zh`ivb1Y^iE? z^V$k-WdUT?LrOy~J;J`+cNWHcdwk_Btkqcw@00^}cqSWc^f*c-<#um&Q90+TXh!6y)E?7XKs?s*f+62A__bg&LQXcO{*a z=B>E9KQoXTWBrb2^73a(WH#SEUx2SLYvzr$-?EBNt&@G8ReL<+oJhGUQ3}LG2Fj&X zLm*}CPPq%jo{s$blNkBoqTkioiSevJ-cJ0XADMmE>Nl4MpBbUE8<~`P>r49UaaCII z9YZ?UPruXQ75I~iYFe8=ab)lJtH<{nZ!!<)xtPM_jChvH)Jo4U)>V#d>#lhv!9*S` zBzH1R!#`lze&eyW(XM5pGbKQtBr>Yg~bH*~&u;Z#rnQ(IX z?$yEN@F(rG=L*>6CZ-<7ANP|LEf}pcS?nO?9oOTx2Vtkmc&sZ%6T9%#9DAdARowyT z$LkajeqQrNX83d5S;x|aNV;=8>_$iCb~kQ!@p<%UZgibWc!hiTC9;EXedqFKNQBNa zm_db`W90!zBz(`$e9VF72~i!au;=#eZ~TuUQIEjFH~W^*Krp()N_Od*oNTTJL6!_ur`!* zoR0~87$@tWssk^#b>)c;M=7oMAnvpsDl50#B|XW?2>ja?kN29U^K1y#K!?danMxB4 zJP1jtN=W3%eNlB!*>|ZOAMaAu9?n^I&x{@!jacQBb$M-%gJbzBncGS8b7YcLQom29 zj<=(E7)sLA<-SL#|9~(W~5R zHfc>N_HU+5XgiDfVd>Lo@UA<`vc%k>StU(mfyGi=X_o=qpyTP>Gxx{d&02+~>WQc_ zK$nT4XY+p6MqLN>+MlvFuU`Av*5(*e<6uS8an5*ND^-3^&Yycw^DC4coc9`$ta{vs z?Wgn6;gIT_|1aK1`ptrg78 z_)WXuPa}8AytY5DnW%oVU?_s0!Jo-#Ok3aXKW};_(6yw=ny413cC-Dhc`*EDr z!ppvk-(>;R#TSEPyn#v%8kqjcRK*e%EQlD+~T?wNCeo_?MM^ z(W381!PAox5$Epv)8t)f?A;`~A1k8@q5}8XbudibH}$^QKSqx~HhEuJBUe6uKN6(c z$EvUz?YLWpKR&C)Zr?2bq>oS8X+KhEvKul`?u7jaefrK76`9Y=BDZ?l(z{8u-o(!n zDUhdnEACoRv(n+Do(2!pt5xDSad#U*|I~rgMr_LV+PJSA5 zyl%+CocCjtYQ_3=$*s6P*NN8Nfg&q=Ev{}rX!XLYOw3>1wN%E(xjOIWAQS9PS+aV_ zNI&;-Y6Y9p*ExRqJlKcS!CGutcOx;M0Rs)rB9*_SU0^#p4S-ej`RCtkL*BB9ZH!MX z$aaSkeK!m7X$!2bc9O98M568Z3=7q6BDOz1r?DGaU)6!z4y?d?loze;z3s+qt9Fi^ zALTf@GGuq)0~qKN0nT-52aTOvPSLAo&Q*$TlFds_10b-Q|Vd>+Ic z^?4j~>q;0D7vg0H3dHa_mCXvhIMGWFmmE$zvYhJDGbo%wgF=6z35|kW+~KZf&yOl1 z&62a;2@YJtWJgsysXDv2AE)47@nOZ6SLzJt?4juVS zo)470OsxWHT4T?9b~V$A*o9=L3Vc0fs5n(=+T+we!ETiSvO27Hcv^#Ts)6JoxpVV*@N+f}{XDeob6hCcG7TxYF8Zp zE_VC=J+S!3;&)}Fp6S37)eqx*&`IclJ()Xip9|zSPCGfLm9;2-Q|UVw&bhP`$iKr1 zBDo|FxEC|wC14)7^E&>Y+JR2WWCNV4u)mMFz&}}$ckta;LnA~hoy4}atUE;@AFgkB zfHFCm%}gi=1BOfpa$ohWul8=jQmZ@zrT#noY{h)qU9aI z;ml|i1YsQVT21@EVua#wc4p`}&fg`ZvbAs4E#7IB72LqBun^nB%63BD)8~&l4Rp6v zC&*@1AC#^O^~evm>r7Yqld!AWQLfh%cdMPFJ=U!FiAAhGf2SvrEZHY z27;ze6m@bw=n{wenZN+uIqddHPOM9KdGL0pN2sf-AZ7L`RAniD)E>#Y69AWj`d;v0 zsgBP{s<%P*s60ZsT~>v}9VCdKVj1WYe|a1~>#Zzz;wO3URNi^F-R0+GB~9Y1GGu8$ z*)6_L{mF00`Z(g+R-7y%VoF}sU_}K`L6#2aOP~AbCKx2K+h0mm$%xjRQ-o3$kq-*p7 zZ77VjO~uaY58KSlQ|l_8nMa3=@N9Jjs)00XeMZq1u4g6S63SQ%`G#UX+>oB_RtEzv?Z?GSNbI74Yu5 zPb2%#TRFhY>;NYsY?rH=k$>x*AW%}x>S#C{9A{rb6v$H93xZGRfMn{|BnSH4nv}ez zI}n?5qMW)`*4LZvR0C{E$NNZ|%Fg3ArLX-p&P}Qh;Jp=aL{-YuQt@AHS7j4;5|JP2 zVhQd7vTjD?{b+MDeky+K&FFkTW|P;N=hS2J$DH^E&-^R4IwEnuygBcqqpjUvqL7{o zRF1@3Bevu16}v?WzYqJ_3%tD(?JL(oo@CgW<0iLBjk|f@uX+q{4i-~s)Hf$lGb$Q`^+xuZLnZ$7JNm}{QnaNGw>J~Xeo zwD*F8N<%ZAg!Ys_Vu$XgC1)Ao_hTG6B0Ggt?3`mih&vt@t@FMiK7$t7jA|>iXk6|q zJa-Sk{O!2od32gVQ>+Gi`j_}0`~F#U8F|O%m7oc(XP*`+*Ik1iEw@9eXWy05d{5ii zGX7O2bg{)2G8$!B-~!P@&ug)ERtxeBdC@rcYa}I|@WzVqMB*n=RM87-S4=&5OSATf zk;c1mH;3xmF&}GscV6cZ9^Q|d&$|;&7rN#g8X*qo3d<`cC8DiZDSKYrwJZ2E6;A( zS29Aw`B{0wM~Pj1q&^@1R_Qa>+iRd+R|drDbm~(Th~$!YT?;vpXI?CC8K5sYovU4m z(b>gOPZu4j%Beb<8c9DWZ3*15){>tqajjnWDjeIWq*r%Iu!pD3pQ?^^zpzexWA8f8 zRcV5`;7dJKvlG|IaPzZDfrv}Y$$6O{IcNWcRUx7Q*Ss~`uX=fpcm$Y+Qk z+MmUbrA4%}6;BRtNA-heIVaMU3vun^xH`p2;+U)g`uR+(eB6|cgjZwCi^%cdg3R1| zyh*F(R_vR>DKTx1!fsGvo%Mu+uDOFSM}@4D{k6NXb1Vr@y-kAnrhQ?!&>f5i5!eH) z^(A9-CHzL_AyntLVFsUL>#C9Uu=<#~+gi!aQlB}@(H!5GvBUFV%^)2(KE)-LL{8)} z*+UZrM=f{G)jP}mgejU(%rnNItT?5PH@<%mmg>5KevI~FPcK*15#)d5Nurdy44Q*% zncACubf1No=6iZ%)w|@R9UClqoJ|mSn2W5A(e67=&8Yjy@363>N6yh}0-v0sAgi3i zq^Cg_`{wx>>47|f4A%V$_xO<@NJGR&TgLiC5$~!G?@z9VPr>)r2p?ol(qO){C+I$dnEO$*R0WH>2y1h8Ipx7_1xKK zP`_TYpbg%mn-W`ff(zc&q15`&5RyYbIvJoY5z^wF_2h+oM#Z?AE7joe>FPc0XS&Wl z9#z_3#g$x#ev_Yk6}(Wzq%?Z@A_d!|>Lp}MW`-@eHo+J2*%8IM>P^r@>dxWN8X8dF z8cSmi?C2oYsduvysfm@K3~%BPU&?x4xAD#xxjR=krzOs8ypF%-UK*m?2d8A}yaSE- zeELHjfn1w9o3eIt2Y9hgK8Ww68cR|-)*d@D1JBr7)SGkV=~WIOTFv3XLF6pC(VMJq zj%7wVK2>+<3!fA(qLLHhFjh#dyZRaECvgRz9KO}c7ASt4C0u(OY5Lw> zjlx$$c2AnT+4YtmPxaV(n(7i_`%~|O+zxq;-`|)~SA2y_rQtED;yLKsT2N1qUP*m(68cdmM$aAayE-evJoVwRHheWvTr*0?wbPnX z*a=EGr*~0#vmO1+M1Q_zW!NRnw=#&A+GWLR_+}b7!?t~HOJ1va>iE-kW!b^{OGvKz z*I$S1wpv}sjyy9yv-|1_a!gUr|^7Hl*!DtI>G5?0R0Y8lx2u-fRg zKU{%gW$y8&wQt5D_@UnAN5K!d52#dkeW(4m=R>Fa;6DAUN)fN5!m|V{@6EZDXZ15k z(p7$}rq+jw24nfJUsGBDd9HROHs`kT)K_L;MAB2XHLv01Vm-R8(|6@*GXy$~l;-Vl zH*A7jcM1pJ-S$#KLk^T7l8v6->Ux>2e0YVi|gmzOqe_B3DS{@wsfWVV0lJj^~Uv3WFWU3yzQz|I>qT zm8kVh`LSe9uC0^g=;C*=_jx7$&)OUb>Uo_i7xpmt_Jc<;R{T|JUYTn|okJ9Ds@AL{ z>9(nFq~{C&P%4vaF$3SUgdHi*hf^wPc7Z`H?0H>3uRi&zcZ{%mhsBxmO*8-xuAQo>7TU(G(|o#CgrjK^-h&`ps0Pu@m>t^&k4rvhUDem*nUxC`RP@{U!IzV^#ABCx$g7a+5FR*}L3rp^y4$>!#{V%I(Ib z>{fN8ZAaEQL|8E9ut`LH6wNk`Fz*LhHkZ6sg*4Q4Q5|Jj;-9rEgiq*%8d$B}KK{o) zK?b_gWFbf?CCg{@2bJ-CxX`ytB4NEJSW>>#Mu0FH`(4oF{ASh@#=v2oBLts|HDH1` z`~Sg5tYNrk2)YR+SNjhV0s(yX?2pYy@O0zJy(V4ag1u=qWv~H>VzE;_@8rky&@uNg z6a2XH&JG*jN4eSiQIlKsTxA)=M)>i5ko!$wB|R;7Y{58Ro1&C*npy>t)ZO(&2{dIX zSf!{=#qlBSsWIqe9=kW0MSV4WJ13nW-sell6e;j#d3Ac!Rdn8xol&mWxG5=Y#;Mk> zPLR5@8M$2j>x3q8jk6`rp4bd0&5%2m!pm8md1_HCaXzF?8YodaiwM$DUTcRiOwj{hsK)0XehA5OVm^s zSN*UmVb0g#Q^=l?iYPkgLZsK-_zn33@@9t2AWl2;IG@lHE&a)&vvQ>suo80jx<3n_ z$OMbWSMd6j?#-qfb<b;m?|_i7HYm zl=}QY&c!&e0MvDUnCnT~*o{$)6K}|v?7|)`8#Ep7JIUvdiN{is*nDBhz(ZcUew%lU9G9&ID!_LIhL}p|uVp;LRqxn#1`Go~|3gC{<{x0-=~y2|h4>=hJkR zM&IH_jCb}j_Q>jm`ymhUrP=kInKEGJw;g3Q)pO?e4val?T)}{FvH@}EZcyL(I4W7% z`;psUN+M&-%6wES#aBQ=q@)~4oxnW#r7FO^He1j532-V&@;6)Z*5aFZTI%!m_6ODY z-^3NZqf`|al<+a_s%VzX&fyAs;@_Gnr+@5)*TC~fOY~=?Py5@#)W6Bvt)4IgSy^|V zL9z;F#0tsCgaXj3t0B`O265GEl0<5EYrH0?Y4?ZEoSpyJbyB_iZ(4cZo&=KYd>zZz?RC$5MR ziVi&vgPzZt3=}u0#{*h9t&_~un_}=kcoJ@`*A{&^dMKx}Rq>toiW71O>}71L+4@swBwVueI!Ce4a`>oUGx+vOc)7yo>$@dLi$DB2tqjd*qGL z=fdo&$y-hD)(_oDDjuH1a;|2Jm%rEgM7+^U)eZ-_5*n{)g}mS_&q?SVDC1f{+1Xfz zQ0+6dF(z^^v$)SOg-Xc*^y(>mXVXOGicms?VNY5zM@3$p=(Qj#NWuF zYQeFqKIfbqxQ@v^@+qFM`}~HUl*!!LA!}*giiapeCmZ=ne$-az0(qlhP_>@Cgh%=d z!`)*%9RCy2W=A!%1tZ=tA7MV9h|wDfHLL1NxR3{W3=)}J{n!FGI~4^o4YP{gQ(5;I zt@>SDXT;nI-`Uv_QsX;WPF$NHnJ|<43>H4?%F8_rsNlQM za@1UX{z5y?)N-Icp5v1d)dKh9dc`1Qz&-M`lbDOBiKT!+>iJ-2+hN~$q;;r!6cgc; z+^j}hC{SC9I{v|LjKJ>n)?K>O%zV1HLwy}Izm30o7j5clugwK|;$PNDKUyotC)`D2 z1!yMefWXqU_fC|(XsxSgueTuZCh6n}q#>6{iT6*koAq&*<@R%jS5{{Gu%w(u}y%o_m`rSgxr{G_@@yz`lp9Ec2S?h)R z{i}Wwkd>ybkP(93TZ><|`n(Px95{7CsEzE89Udx-mb zLa*kQ=nH@AyFlA*Ey6(AE>=o(>#bB(0X`;7zp=ku8P`_Q{EL2p*K zU-R6%ZE1fw0(w}p#w%vdKT(=@_Zi6er1a3f^Uk?%uw`y6Pc|zH0F}}zp|P_ZwR)XG zPiRO-_5*$wdU`heV%h_Al8QY<@pT~(+Suv4b!rW+B{_95WTni-YRH+GljzS1K*Om_ z9na_e=69xowNe2YEK&`-X{`)1NQ?s=qXkt(SuDz2Hhw=3Vj6Z;wHm$b%?6g>6Dz z>D(QnM2@j^T^*bsV(5JqXTuC|9DSb-2aq(L$Q!FJhILXYz8{}Ut9FM_o;L4!_A?{J zyoaYZjI|`GoR=36OY&)SMJDVN6!mF|%rm$O-ec){(pr&0*r@d@>QGgni<`|x?WyQ= z*e}V7+uZ}Q9dJfSOSh!YS%{5&Re}FQm%9{?w-)~tKV!+9Y-C07AP%9gjQW$3-CvE* zg*(J8@D%GyG^28rC0i~T-*i1^m_I3^b5MPchPWzufx_1sA6ma9ZFwZJRSL)hS@pG$ zWKuD^$^fKBLc(wLA|2%(h36nCP{lGqQtCr|)9eL_W=B$Q!<^To@>irwf5LkMeeV%0 zr+ccnh!+v1>N*|E#=RQlROAc1`|x6DoSdk3fzV!d6>qoIWv2gClbu(1JN77Ugyr(? z++pA6i@@>vPF?4Z!|tQ{2lx@IAdkU1u#lb3$yOz$DD3luvVHo|qOR;8UBIW#2_$c)W^#D&6zpIr+uj$p%iE4a&yOmviFOnUF{S8r0) z^y?lzJBdXIX|aAk;fM8)_q(LAo?ee(_&K`PNIa&l5DDT*_52-o&>2ADUZeBjA*L$N zQ=bxqraBqRkr>y{A^c`_jAr*F=R)qpRN3Uzs)-)j(68jPqoxD2V*(lDMU7SWd~L(ubz%X z)**1N!91M(JM6|hx*|7kdhR(fJEP^4RLL_;fpuVg`aZKc#7`?>EZ?-6x!b0*=JP># z)M@7Qlccsm{89+H&VC#8P;T>^%~wuEx5E$*1Lj`>;*= zHud{yhI0r5_p749o&xApMm6M|}PimCkdU;R9+Ia*S z+I=GDc5p@ZOCCmSF>1h@B|glIc`vtieZ51APq>R~dz3E59Vq!_Vy$uj)ppKO6_ey; ztexGQXR!;aEa+zZd>DU(uIuKLSKf%#&^MD z-$?>!4wRVh3d`cPX4O@U#|w0ic5~GQ_0t;L!5y62%2!d(w`yDuuR=yTW$P7db@VUL zFYDBM?#OD0SW^;6{6>mMUAx~?6#;U z-cm=TgAS6bh?V?NDT1Pm&`*q0cfR%%)>&U_QZWyDvPGyX-&ZEAsKw3!n(R;B;Rh8} zGdo4oL_6I#&OoQ;Q|0GM5(@hY2rPO=hG|onW;>#1G?t=<0dd&E(P3 zNvshqvTba19Gk3np%}O@?$Tyu41Y+C|FgGi-D(d`z14B$UiGL5HsAU5jO?Pz_PK+Z zu`18=ee-}ZKa8C6Cs74qhffV!dwxqB(${kU*cnV7^5YkwEcD$SwH`Y7MWBvw?<57!!zJU~x2<%w}{VR*uKOhex8 zE)H4vI9H}W3{s*yw3a6|WaT_-P%WxPX5}QRjWx?uG#m0o4w$(HYa`Rp&bguxNH|>U zt+>+f^$h9C{-$DQ(jpfDH_7Ivx{hR?%#N**5zHZQr6@jps%MPir;936l+QPM)ETBQ zHnG3g_4?-@h6vl|%2?QVZsHN|(!m0$Kj-s6DmK?^cxk0&j2Lo&CRz?MQ2Mm@ra$!LxOhA z`!X-5uCAYM7S)u!_veqOy_G*{9~Ak)bEU4Vkz8LHJKe786^u8AB`#4*^NBjg{T{Jp zFsv`IcjjPr?oG+FqeBjEx%X$9hUZSWUs~z;VXc4j&L6XPj#0{$vU?YaP8DPNd=c~o zHG~SnR`*H9HrZn;NV(T?ej^Jwq&i&khfBmTI?Bdp2Z0@^9ig|q$BzvoosE>1s0eJ?2EQPm0s-DkiIrL~e_(s^h zXe&89HkKlZL&uJYW(_Njja)M`(PuA~V zy1l%Ho#ehrAOEIm*J1Rsrh1Q3*LLberRCJy`E;)^->=`lUm)YMVlUOj^LWCFo-5z1`u5z34X2%DSQRT@$!{Y$Z|^6oS^aG#Ipg`_z<6b+vz+yO zpT=tDxY^XlH<6?H+05J5akSps&&Jb~-}~GHvCccv87tE_wkJMyWHAkQe*8OG0}+bO zP3{EfW}z$P*c+x)%tq%$fHxled2s)O`XGs>-Q$n%#4rfoGk(Pr>KMi@)0< z8;7SS@tdEmlRluXUW_`l>{96j0i6%fgWr<#FN0d;hq&5z0@x?A`73tczzT#nySOQijE{N0Z$@XaUvN)(&mO5La7*-_b` z?q-!dy`&gGWad+R243h63$uOxUO6-T$%Dwuxq}Q#W2q@l;&*Zj&K_sRd%4l_IVo~G z)^WMbi3g!=&Pv->ei4*7CqWK@EHr+6RS}iz@IXeBBhd#}@)-ceX=gk~d3W10|MKk^ z&IrLJS3D};Kn*RrruXCgQc^?ys&tT|X--+=PW&Nm diff --git a/Resources/images/cancel_forward.png b/Resources/images/cancel_forward.png new file mode 100644 index 0000000000000000000000000000000000000000..8043430fea59b053066d668f0a2997665f25e688 GIT binary patch literal 9030 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 OAcB>>Wx2Ue^8W!G54Feu literal 0 HcmV?d00001 diff --git a/Resources/images/file_voice_default.png b/Resources/images/file_voice_default.png new file mode 100644 index 0000000000000000000000000000000000000000..a492d31a5d0ee22e138b9fb34d1e80d61029eef1 GIT binary patch literal 8605 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 V*ioj|%~AqDSNnukzUHaG{{kLLEu8=W literal 0 HcmV?d00001 diff --git a/Resources/images/forward_message_default.png b/Resources/images/forward_message_default.png new file mode 100644 index 0000000000000000000000000000000000000000..b51542ec8cbeb4c074a3bfba2864aab3cbecbb69 GIT binary patch literal 3732 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 literal 0 HcmV?d00001 diff --git a/Resources/images/menu_forward_default.png b/Resources/images/menu_forward_default.png new file mode 100644 index 0000000000000000000000000000000000000000..910450bf9fbc0a091f529b26aa125c040487a93e GIT binary patch literal 1910 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 Bjv4>} literal 0 HcmV?d00001 diff --git a/Resources/images/menu_reply_default.png b/Resources/images/menu_reply_default.png new file mode 100644 index 0000000000000000000000000000000000000000..8aa5b5e1e0df769b1f684fc4788e8d94170a6aa2 GIT binary patch literal 9498 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 literal 0 HcmV?d00001 diff --git a/Resources/images/menu_resend_default.png b/Resources/images/menu_resend_default.png new file mode 100644 index 0000000000000000000000000000000000000000..5f5ab4ddd4264a772202482f47b7fdf74c075ebb GIT binary patch literal 8229 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 PXo0XM`;ChXk464JQ^NjN literal 0 HcmV?d00001 diff --git a/Resources/images/reply_cancel.png b/Resources/images/reply_cancel.png new file mode 100644 index 0000000000000000000000000000000000000000..8156bd6298c949ef93eba808588863a1b0b78029 GIT binary patch literal 2884 zcmV-K3%m4*P)EX>4Tx04R}tkv&MmKpe$iQ>AKK9PA+CkfAzR5EXIMDionYs1;guFuC*#nlvOS zE{=k0!NHHks)LKOt`4q(Aou~|=H{g6A|?JWEwqU7;J6>}?mh0_0YbgZG^=YI&~)2O zCE{WxyDA1=(Tgw!5JXUBmN6$uNqCO0d-(Wz7vovp=l&dFHD@uvClb#x!?cMvh^IGg zgY!OdgcW6#_?&pmqze*1a$RZi8|Q+{0?&+?>C`-Ngjg(eu+qV-Xlle$#8Fk#DPPDm zS>?RNSu0mr>z@3Dp`5<5%ypW>NMI35kRU=q6(y8mBSx!EiiH&I$36T*u3sXTLaq`R zITlcX2HEw4|H1EWt^DLfGbt1Yx?ddUV+0870*#vEd>=bb;{@gF zj20++-Q(RooxS~grq$mM3Sn}6n*Cq|00006VoOIv0001%06=yDKCJ)%010qNS#tmY z3ljhU3ljkVnw%H_000McNlirueSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{00|sPL_t(&-rZYma8y?re%^Bv!%SO4XDC?64#XeI=uo3G zfhLhm?SzjsRq~;bHc%?naXQLazHDh7O5-3crcpc88LT!)h$h)YBt$G29j3OF7?o`J zVa84y9AI}^WtY@c<0SXI{30ZV5Mu9*A0E6g^!2|CU&QF>-|+V3FPuib#+KoKd-fMh_htER7jttLrg>s3oR`KlZA#+yp>#d z{gehA)`o=W_{8Drx`sHgM3j`0IRUPV76?!Q{OIKlR5xto==j88t^J1JG1vpJ?%_oJ zfDnH7A6)l96aF}SNk{4w8kz-f$8~-NR(^T~PW+++ix(~+aAI)~-cQlMM91H+Z)n_% zp^KN|USdoHc*ZL8!ojw6_pC<$iEdI@wf&vk1Ew%}4+2zDuzlAa3#{9^Hn4G)&ml1TyU4t( zF`OZA=G-5!eb*jVNwH0S>@%ilSi7!kml}=W$O|n@I%z=qh1t8v{#{co^FAf0|KlIF zuo{hMxT0aGG40cuc&i7QSyhgS$tkBIrBEj#@>E5Chotl?+%_*Cp~3Sg4lE&S zo?&p=q%z^j{mpoPa>{!yJWGW$svrOeOiWJU$^HAG>fOMch-j3)3N;o+U$BGovZP%g z5a3`i=pU8!t`rOgIS>fM)7D09=WoFRib?IhU^}ZZtIL{zr(+RRmaX*cH|c;4g+eT) zG(o!?l+D%C)5FZnp-?CeG$r|N|6N&D%G0q3IC|9yxWB8N72QHQ0Wtz>FMfDB7z{d| z8488CsHiBVs5yTNRsinrYG*^3H3f@CP+wi8SM&z#<*`5@z#}6g>{YtEyZr$xouEDG z?fD!H2LW7nUzP4<9q^kYf424A^gZnMf*+s_8Ye25u}1@}0Q~025UbIM2KZp}J_ilb z1FY5Q?&*t=?X8^v6ii!$f#N}o{rwcY+b$8!eUAB^^V-aBd@+kZm zMs5bQZ(x%ERsg9Ga6`54ll^pnd za5K7~7S^)4upn;tfPNDWv4xUcItQ|J9zZV581X){~xHWjUEcXk8ax>M`aI6JByaK^dO0wcy6SV4bJn|6_pV0Y$==h87u`X?w3D&y^hn&WWIwQBzb{E(`_6Ci zk+$Pdks=|SvdKLXiL?D|N}**23V^4z^x=m$<8bStw0oQ_*AhscSx!(%;N@fOyzBA3 zn3$Z>FgP;eW!99;BtMG)+)|K-)1J!0-5_wxh0NJa8TUfOR~_%?#tUM)%O`k#UTtx(eGK-iq~W*Af}0SvV6qd5RWN z1GVOpnpJHtm54v6#U92_x1FCYFGbC|wfJIftv*GxP3ma|PNp$4NQjg#GLGVnkN>Ow z{Dlj>7egZ$9UaBnV}C~^5sR}`}msO4B&rLW?M~e!tgWz0000