Audio chat messages

This commit is contained in:
Christophe Deschamps 2021-07-23 12:06:05 +02:00
parent bcf19d6742
commit b66c3ad916
28 changed files with 868 additions and 98 deletions

View file

@ -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)")
}
}
}

View file

@ -34,9 +34,18 @@
<outlet property="sendButton" destination="15" id="27"/>
<outlet property="tableController" destination="29" id="32"/>
<outlet property="toggleMenuButton" destination="CPn-Oc-9PX" id="QdS-xO-bfA"/>
<outlet property="toggleRecord" destination="aTi-pm-fAG" id="RYV-0Q-v5p"/>
<outlet property="toggleSelectionButton" destination="c9z-aq-2UP" id="kiK-wF-8iU"/>
<outlet property="topBar" destination="7" id="JH8-F4-Bdq"/>
<outlet property="view" destination="6" id="11"/>
<outlet property="vrDeleteButton" destination="wi9-en-JCZ" id="09B-Bm-ECJ"/>
<outlet property="vrDurationLabel" destination="dMW-Ix-4k0" id="Ugl-f5-r2m"/>
<outlet property="vrInnerView" destination="eXD-Gd-FXA" id="wBh-N1-Au9"/>
<outlet property="vrPlayButton" destination="FNM-bb-AlC" id="NOD-GE-eIA"/>
<outlet property="vrView" destination="Tru-Zm-4EZ" id="Rx2-ls-nMc"/>
<outlet property="vrWave" destination="m9m-2e-T7E" id="F07-qS-Yj6"/>
<outlet property="vrWaveMask" destination="TzM-ND-yp4" id="FKT-Gu-CyA"/>
<outlet property="vrWaveMaskPlayer" destination="OTf-Od-TDn" id="g9D-K1-Gf0"/>
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
@ -209,7 +218,7 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES" flexibleMaxY="YES"/>
<subviews>
<tableView clipsSubviews="YES" tag="13" contentMode="scaleToFill" fixedFrame="YES" alwaysBounceVertical="YES" style="plain" separatorStyle="none" allowsSelection="NO" allowsSelectionDuringEditing="YES" allowsMultipleSelectionDuringEditing="YES" rowHeight="60" sectionHeaderHeight="22" sectionFooterHeight="22" translatesAutoresizingMaskIntoConstraints="NO" id="8" userLabel="messagesTableView">
<rect key="frame" x="0.0" y="0.0" width="414" height="634"/>
<rect key="frame" x="0.0" y="0.0" width="414" height="574"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<gestureRecognizers/>
@ -221,7 +230,7 @@
</connections>
</tableView>
<view hidden="YES" tag="14" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="fx4-ao-53M" userLabel="composeIndicatorView">
<rect key="frame" x="0.0" y="634" width="414" height="22"/>
<rect key="frame" x="0.0" y="574" width="414" height="22"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<subviews>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" tag="15" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="%@ is composing..." lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="fpY-Fv-ht2" userLabel="composeLabel">
@ -236,7 +245,7 @@
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
</view>
<label hidden="YES" opaque="NO" userInteractionEnabled="NO" tag="16" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="No conversation." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="p7C-WH-uR1" userLabel="emptyTableLabel">
<rect key="frame" x="0.0" y="0.0" width="414" height="625"/>
<rect key="frame" x="0.0" y="0.0" width="414" height="574"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
@ -260,6 +269,19 @@
<action selector="onPictureClick:" destination="-1" eventType="touchUpInside" id="87"/>
</connections>
</button>
<button opaque="NO" tag="9019" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="aTi-pm-fAG" userLabel="audioRecordingButton">
<rect key="frame" x="66" y="0.0" width="56" height="66"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="Send picture"/>
<inset key="imageEdgeInsets" minX="15" minY="20" maxX="15" maxY="20"/>
<state key="normal" image="vr_off.png">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="selected" image="vr_on.png"/>
<connections>
<action selector="onVrStart:" destination="-1" eventType="touchUpInside" id="6QP-19-E8o"/>
</connections>
</button>
<button opaque="NO" tag="21" contentMode="scaleToFill" fixedFrame="YES" enabled="NO" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="15" userLabel="sendButton">
<rect key="frame" x="349" y="0.0" width="66" height="66"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
@ -275,7 +297,7 @@
</connections>
</button>
<view tag="20" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="pqa-tg-5ml" userLabel="messageField" customClass="HPGrowingTextView">
<rect key="frame" x="72" y="13" width="269" height="40"/>
<rect key="frame" x="130" y="13" width="208" height="40"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<accessibility key="accessibilityConfiguration" label="Message field"/>
@ -287,6 +309,58 @@
</subviews>
<color key="backgroundColor" systemColor="secondarySystemBackgroundColor"/>
</view>
<view hidden="YES" tag="28021" contentMode="scaleToFill" id="Tru-Zm-4EZ" userLabel="VoiceRecording">
<rect key="frame" x="0.0" y="596" width="414" height="60"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES"/>
<subviews>
<button opaque="NO" tag="28022" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="wi9-en-JCZ">
<rect key="frame" x="12" y="13" width="24" height="34"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<state key="normal" image="delete_default.png"/>
<connections>
<action selector="onVrDelete:" destination="-1" eventType="touchUpInside" id="yV4-iY-mvE"/>
</connections>
</button>
<view tag="28023" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="eXD-Gd-FXA">
<rect key="frame" x="50" y="8" width="302" height="44"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<subviews>
<view tag="28024" contentMode="scaleToFill" id="OTf-Od-TDn" userLabel="vr_wave_mask_playback">
<rect key="frame" x="0.0" y="0.0" width="240" height="44"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" red="0.93333333330000001" green="0.93333333330000001" blue="0.93333333330000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="tintColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</view>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" tag="28025" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="vr_wave.png" translatesAutoresizingMaskIntoConstraints="NO" id="m9m-2e-T7E">
<rect key="frame" x="8" y="8" width="232" height="28"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
</imageView>
<view tag="28026" contentMode="scaleToFill" id="TzM-ND-yp4" userLabel="vr_wave_mask_record">
<rect key="frame" x="0.0" y="0.0" width="240" height="44"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</view>
<label opaque="NO" userInteractionEnabled="NO" tag="28027" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="00:00" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="dMW-Ix-4k0">
<rect key="frame" x="245" y="12" width="49" height="21"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
</view>
<button opaque="NO" tag="28028" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="FNM-bb-AlC">
<rect key="frame" x="366" y="13" width="35" height="35"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<state key="normal" image="vr_play.png"/>
<connections>
<action selector="onvrPlayPauseStop:" destination="-1" eventType="touchUpInside" id="Wka-en-Ic8"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" red="0.93333333330000001" green="0.93333333330000001" blue="0.93333333330000001" alpha="1" colorSpace="calibratedRGB"/>
</view>
<view clipsSubviews="YES" alpha="0.90000000000000002" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="3qd-ys-t2L" userLabel="imagesView">
<rect key="frame" x="0.0" y="625" width="414" height="0.0"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
@ -551,8 +625,21 @@
<action selector="onPictureClick:" destination="-1" eventType="touchUpInside" id="Ag3-po-DGR"/>
</connections>
</button>
<button opaque="NO" tag="9019" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="zub-Ow-smm" userLabel="audioRecordingButton">
<rect key="frame" x="66" y="0.0" width="66" height="66"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="Send picture"/>
<state key="normal" image="vr_off.png">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="selected" image="vr_on.png"/>
<connections>
<action selector="onPictureClick:" destination="-1" eventType="touchUpInside" id="qhP-B0-dkG"/>
<action selector="onVrStart:" destination="-1" eventType="touchUpInside" id="yWJ-st-yz2"/>
</connections>
</button>
<view tag="20" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="C02-2r-vXK" userLabel="messageField" customClass="HPGrowingTextView">
<rect key="frame" x="32" y="13" width="257" height="40"/>
<rect key="frame" x="59" y="13" width="230" height="40"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<accessibility key="accessibilityConfiguration" label="Message field"/>
@ -637,10 +724,14 @@
<image name="delete_disabled.png" width="34.400001525878906" height="44.799999237060547"/>
<image name="deselect_all.png" width="43.200000762939453" height="43.200000762939453"/>
<image name="ephemeral_messages_color_A.png" width="136" height="158.39999389648438"/>
<image name="more_menu_default.png" width="10.319999694824219" height="13.680000305175781"/>
<image name="more_menu_default.png" width="7.1999998092651367" height="9.3599996566772461"/>
<image name="security_1_indicator.png" width="27.5" height="32.5"/>
<image name="select_all_default.png" width="43.200000762939453" height="43.200000762939453"/>
<image name="select_all_disabled.png" width="43.200000762939453" height="43.200000762939453"/>
<image name="vr_off.png" width="40" height="40"/>
<image name="vr_on.png" width="40" height="40"/>
<image name="vr_play.png" width="200" height="200"/>
<image name="vr_wave.png" width="1078" height="90"/>
<systemColor name="secondarySystemBackgroundColor">
<color red="0.94901960784313721" green="0.94901960784313721" blue="0.96862745098039216" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>

View file

@ -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
}
}

View file

@ -39,10 +39,10 @@
@protocol ChatConversationDelegate <NSObject>
- (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

View file

@ -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

View file

@ -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

View file

@ -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;
}

View file

@ -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;

View file

@ -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;
}

View file

@ -33,11 +33,16 @@
<outlet property="plusLongGestureRecognizer" destination="daf-cW-dRj" id="O5u-t0-uMe"/>
<outlet property="resendRecognizer" destination="5ZI-Ip-lGl" id="G2r-On-6mV"/>
<outlet property="totalView" destination="8I3-n2-0kS" id="aa8-j9-saW"/>
<outlet property="vrPlayPause" destination="7Zn-bp-e0Y" id="ed8-ZL-VpS"/>
<outlet property="vrTimerLabel" destination="VUD-m6-g1J" id="tOj-bF-YHd"/>
<outlet property="vrView" destination="bhq-9n-zYF" id="PT7-3a-6tn"/>
<outlet property="vrWave" destination="B5G-6m-k8r" id="fXd-ze-WQg"/>
<outlet property="vrWaveMaskPlayback" destination="a7V-w2-tIE" id="vRZ-VF-sJV"/>
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="UGz-WT-BUv">
<rect key="frame" x="0.0" y="0.0" width="428" height="321"/>
<rect key="frame" x="0.0" y="0.0" width="428" height="381"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" fixedFrame="YES" image="avatar.png" translatesAutoresizingMaskIntoConstraints="NO" id="hD2-19-7IH" userLabel="avatarImage" customClass="UIRoundedImageView">
@ -57,11 +62,11 @@
<nil key="highlightedColor"/>
</label>
<view clipsSubviews="YES" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Y7i-Gm-AdY" userLabel="innerView">
<rect key="frame" x="39" y="20" width="382" height="297"/>
<rect key="frame" x="39" y="20" width="382" height="357"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView userInteractionEnabled="NO" alpha="0.20000000298023224" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="color_A.png" translatesAutoresizingMaskIntoConstraints="NO" id="U2P-5n-gg8" userLabel="backgroundColorImage">
<rect key="frame" x="0.0" y="0.0" width="365" height="297"/>
<rect key="frame" x="0.0" y="0.0" width="365" height="357"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES" flexibleMaxY="YES"/>
</imageView>
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="8I3-n2-0kS" userLabel="view">
@ -158,28 +163,60 @@
</connections>
</button>
<textView clipsSubviews="YES" contentMode="scaleToFill" fixedFrame="YES" scrollEnabled="NO" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" editable="NO" text="Lore ipsum..." translatesAutoresizingMaskIntoConstraints="NO" id="cx9-0K-P9L" userLabel="messageText" customClass="UITextViewNoDefine">
<rect key="frame" x="0.0" y="262" width="365" height="35"/>
<rect key="frame" x="0.0" y="322" width="365" height="35"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" heightSizable="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
<dataDetectorType key="dataDetectorTypes" link="YES"/>
</textView>
<imageView hidden="YES" userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="chat_read.png" translatesAutoresizingMaskIntoConstraints="NO" id="LPj-VT-0fH" userLabel="imdmIcon">
<rect key="frame" x="372" y="287" width="10" height="10"/>
<rect key="frame" x="372" y="347" width="10" height="10"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
<accessibility key="accessibilityConfiguration" label="Delivery failed"/>
</imageView>
<imageView hidden="YES" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="ephemeral_messages_color_A.png" id="7JB-ZL-0lZ" userLabel="ephemeralIcon">
<rect key="frame" x="351" y="286" width="10" height="10"/>
<rect key="frame" x="351" y="346" width="10" height="10"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
</imageView>
<label hidden="YES" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="00:00:00" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="IRV-qN-sRj" userLabel="ephemeralTime">
<rect key="frame" x="282" y="286" width="65" height="10"/>
<rect key="frame" x="282" y="346" width="65" height="10"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="9"/>
<color key="textColor" red="1" green="0.36862745099999999" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<view tag="28021" contentMode="scaleToFill" id="bhq-9n-zYF" userLabel="voiceRecording">
<rect key="frame" x="7" y="262" width="351" height="60"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES"/>
<subviews>
<view tag="28024" contentMode="scaleToFill" id="a7V-w2-tIE" userLabel="vr_wave_mask_playback">
<rect key="frame" x="8" y="10" width="335" height="42"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" red="0.93333333330000001" green="0.93333333330000001" blue="0.93333333330000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="tintColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</view>
<button opaque="NO" tag="28028" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="7Zn-bp-e0Y">
<rect key="frame" x="8" y="13" width="35" height="35"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<state key="normal" image="vr_play.png"/>
<connections>
<action selector="onVRPlayPauseClick:" destination="-1" eventType="touchUpInside" id="ffk-KD-q9Q"/>
</connections>
</button>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" tag="28025" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="vr_wave.png" translatesAutoresizingMaskIntoConstraints="NO" id="B5G-6m-k8r">
<rect key="frame" x="52" y="16" width="223" height="27"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" tag="28027" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="00:00" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="VUD-m6-g1J">
<rect key="frame" x="287" y="20" width="48" height="20"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</view>
</subviews>
<connections>
<outletCollection property="gestureRecognizers" destination="5ZI-Ip-lGl" appends="YES" id="1iY-46-rRR"/>
@ -217,5 +254,7 @@
<image name="color_M.png" width="2" height="2"/>
<image name="ephemeral_messages_color_A.png" width="136" height="158.39999389648438"/>
<image name="linphone_logo.png" width="41.599998474121094" height="42.400001525878906"/>
<image name="vr_play.png" width="200" height="200"/>
<image name="vr_wave.png" width="1078" height="90"/>
</resources>
</document>

View file

@ -44,6 +44,16 @@
@property (strong, nonatomic) IBOutlet UILongPressGestureRecognizer *plusLongGestureRecognizer;
@property(strong, nonatomic) NSMutableArray<UIChatContentView *> *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;

View file

@ -26,6 +26,11 @@
#import <AudioToolbox/AudioToolbox.h>
#import <AVKit/AVKit.h>
#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<NSString *, NSString *> *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<NSString *, NSString *> *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

View file

@ -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 <UIDocumentPickerDelegate>
@ -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

View file

@ -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<NSString *, NSString *> *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);

View file

@ -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;
}

View file

@ -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;

View file

@ -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 {

View file

@ -39,7 +39,7 @@
+ (UIImage *)resizeImage:(UIImage *)imageToResize newSize:(CGSize)newSize;
+ (LinphoneAddress *)normalizeSipOrPhoneAddress:(NSString *)addr;
+ (UIAlertController *)networkErrorView;
+ (UIAlertController *)networkErrorView:(NSString *)action;
typedef enum {
LinphoneDateHistoryList,

View file

@ -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)

BIN
Resources/images/vr_off.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
Resources/images/vr_on.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -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 /* BuildFile in Frameworks */ = {isa = PBXBuildFile; };
61AEBEA321906AFC00F35E7F /* (null) 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 /* BuildFile in Resources */ = {isa = PBXBuildFile; };
63E802DB1C625AEF000D5509 /* (null) 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 */; };
@ -675,6 +675,12 @@
C61B1BF22667D075001A4E4A /* menu_security_default.png in Resources */ = {isa = PBXBuildFile; fileRef = C61B1BF12667D075001A4E4A /* menu_security_default.png */; };
C61B1BF42667D202001A4E4A /* more_menu_default.png in Resources */ = {isa = PBXBuildFile; fileRef = C61B1BF32667D202001A4E4A /* more_menu_default.png */; };
C61B1BF72667EC6B001A4E4A /* ephemeral_messages_color_A.png in Resources */ = {isa = PBXBuildFile; fileRef = C61B1BF62667EC6B001A4E4A /* ephemeral_messages_color_A.png */; };
C622E3EF26A81290004F5434 /* vr_stop.png in Resources */ = {isa = PBXBuildFile; fileRef = C622E3E926A8128F004F5434 /* vr_stop.png */; };
C622E3F026A81290004F5434 /* vr_wave.png in Resources */ = {isa = PBXBuildFile; fileRef = C622E3EA26A8128F004F5434 /* vr_wave.png */; };
C622E3F126A81290004F5434 /* vr_on.png in Resources */ = {isa = PBXBuildFile; fileRef = C622E3EB26A8128F004F5434 /* vr_on.png */; };
C622E3F226A81290004F5434 /* vr_off.png in Resources */ = {isa = PBXBuildFile; fileRef = C622E3EC26A8128F004F5434 /* vr_off.png */; };
C622E3F326A81290004F5434 /* vr_pause.png in Resources */ = {isa = PBXBuildFile; fileRef = C622E3ED26A8128F004F5434 /* vr_pause.png */; };
C622E3F426A81290004F5434 /* vr_play.png in Resources */ = {isa = PBXBuildFile; fileRef = C622E3EE26A81290004F5434 /* vr_play.png */; };
C64A854E2667B67200252AD2 /* EphemeralSettingsView.m in Sources */ = {isa = PBXBuildFile; fileRef = C64A854D2667B67200252AD2 /* EphemeralSettingsView.m */; };
C64A85502667B67A00252AD2 /* EphemeralSettingsView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C64A854F2667B67A00252AD2 /* EphemeralSettingsView.xib */; };
C64A85522667B74100252AD2 /* ephemeral_messages_default.png in Resources */ = {isa = PBXBuildFile; fileRef = C64A85512667B74100252AD2 /* ephemeral_messages_default.png */; };
@ -1714,6 +1720,12 @@
C61B1BF12667D075001A4E4A /* menu_security_default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = menu_security_default.png; sourceTree = "<group>"; };
C61B1BF32667D202001A4E4A /* more_menu_default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = more_menu_default.png; sourceTree = "<group>"; };
C61B1BF62667EC6B001A4E4A /* ephemeral_messages_color_A.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = ephemeral_messages_color_A.png; sourceTree = "<group>"; };
C622E3E926A8128F004F5434 /* vr_stop.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = vr_stop.png; sourceTree = "<group>"; };
C622E3EA26A8128F004F5434 /* vr_wave.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = vr_wave.png; sourceTree = "<group>"; };
C622E3EB26A8128F004F5434 /* vr_on.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = vr_on.png; sourceTree = "<group>"; };
C622E3EC26A8128F004F5434 /* vr_off.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = vr_off.png; sourceTree = "<group>"; };
C622E3ED26A8128F004F5434 /* vr_pause.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = vr_pause.png; sourceTree = "<group>"; };
C622E3EE26A81290004F5434 /* vr_play.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = vr_play.png; sourceTree = "<group>"; };
C64A854C2667B66900252AD2 /* EphemeralSettingsView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EphemeralSettingsView.h; sourceTree = "<group>"; };
C64A854D2667B67200252AD2 /* EphemeralSettingsView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EphemeralSettingsView.m; sourceTree = "<group>"; };
C64A854F2667B67A00252AD2 /* EphemeralSettingsView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = EphemeralSettingsView.xib; sourceTree = "<group>"; };
@ -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 = "<group>";
};
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 */,