filetransfer: rework file transfer to use latest API

This commit is contained in:
Gautier Pelloux-Prayer 2015-06-10 13:38:20 +02:00
parent c501f59859
commit 06375f58af
25 changed files with 808 additions and 862 deletions

View file

@ -1,31 +1,26 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="6751" systemVersion="14B25" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="7706" systemVersion="14D136" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6736"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="7703"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="ChatRoomViewController">
<connections>
<outlet property="addressLabel" destination="40" id="43"/>
<outlet property="avatarImage" destination="41" id="44"/>
<outlet property="cancelTransferButton" destination="75" id="85"/>
<outlet property="chatView" destination="49" id="54"/>
<outlet property="composeIndicatorView" destination="fx4-ao-53M" id="xk5-nK-lur"/>
<outlet property="composeLabel" destination="fpY-Fv-ht2" id="4L6-ik-ZAe"/>
<outlet property="editButton" destination="10" id="35"/>
<outlet property="headerView" destination="39" id="45"/>
<outlet property="imageTransferProgressBar" destination="74" id="79"/>
<outlet property="messageBackgroundImage" destination="66" id="90"/>
<outlet property="messageField" destination="63" id="64"/>
<outlet property="messageView" destination="14" id="89"/>
<outlet property="pictureButton" destination="73" id="84"/>
<outlet property="sendButton" destination="15" id="27"/>
<outlet property="tableController" destination="29" id="32"/>
<outlet property="transferBackgroundImage" destination="83" id="88"/>
<outlet property="transferView" destination="72" id="86"/>
<outlet property="view" destination="6" id="11"/>
<outlet property="waitView" destination="91" id="93"/>
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
@ -90,46 +85,11 @@
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view>
<view hidden="YES" contentMode="scaleToFill" id="72" userLabel="transferView">
<rect key="frame" x="0.0" y="359" width="320" height="57"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="chat_progressbar_background.png" id="83" userLabel="transfertBackgroundImage">
<rect key="frame" x="0.0" y="0.0" width="320" height="57"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</imageView>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" adjustsImageWhenHighlighted="NO" lineBreakMode="middleTruncation" id="75" userLabel="cancelTransferButton">
<rect key="frame" x="262" y="0.0" width="58" height="57"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="Cancel"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
<state key="normal" backgroundImage="chat_cancel_default.png">
<color key="titleColor" red="0.19607843459999999" green="0.30980393290000002" blue="0.52156865600000002" alpha="1" colorSpace="calibratedRGB"/>
<color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
</state>
<state key="highlighted" backgroundImage="chat_cancel_over.png">
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</state>
<connections>
<action selector="onTransferCancelClick:" destination="-1" eventType="touchUpInside" id="78"/>
</connections>
</button>
<progressView opaque="NO" contentMode="scaleToFill" progress="0.5" id="74" userLabel="transferProgressBar">
<rect key="frame" x="20" y="27" width="222" height="2"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="Upload or download progression">
<accessibilityTraits key="traits" none="YES" notEnabled="YES" updatesFrequently="YES"/>
</accessibility>
</progressView>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view>
<view contentMode="scaleToFill" id="14" userLabel="messageView">
<rect key="frame" x="0.0" y="359" width="320" height="57"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="chat_message_background.png" id="66" userLabel="messageBackgroundImage">
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" id="66" userLabel="messageBackgroundImage">
<rect key="frame" x="0.0" y="0.0" width="320" height="57"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
@ -235,17 +195,6 @@
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view>
<view hidden="YES" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="91" userLabel="waitView">
<rect key="frame" x="0.0" y="0.0" width="320" height="460"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<activityIndicatorView opaque="NO" clearsContextBeforeDrawing="NO" userInteractionEnabled="NO" contentMode="scaleToFill" animating="YES" style="whiteLarge" id="92" userLabel="activityIndicator">
<rect key="frame" x="142" y="211" width="37" height="37"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
</activityIndicatorView>
</subviews>
<color key="backgroundColor" white="1" alpha="0.66000000000000003" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<gestureRecognizers/>
@ -264,16 +213,12 @@
<image name="avatar_unknown_small.png" width="131" height="131"/>
<image name="chat_back_default.png" width="320" height="88"/>
<image name="chat_back_over.png" width="320" height="88"/>
<image name="chat_cancel_default.png" width="116" height="115"/>
<image name="chat_cancel_over.png" width="116" height="115"/>
<image name="chat_edit_default.png" width="320" height="88"/>
<image name="chat_edit_over.png" width="320" height="88"/>
<image name="chat_message_background.png" width="640" height="117"/>
<image name="chat_ok_default.png" width="320" height="88"/>
<image name="chat_photo_default.png" width="71" height="115"/>
<image name="chat_photo_disabled.png" width="71" height="115"/>
<image name="chat_photo_over.png" width="71" height="115"/>
<image name="chat_progressbar_background.png" width="524" height="115"/>
<image name="chat_send_default.png" width="117" height="115"/>
<image name="chat_send_disabled.png" width="117" height="115"/>
<image name="chat_send_over.png" width="117" height="115"/>

View file

@ -23,7 +23,6 @@
@protocol ChatRoomDelegate <NSObject>
- (BOOL)chatRoomStartImageDownload:(LinphoneChatMessage*)msg;
- (BOOL)chatRoomStartImageUpload:(UIImage*)image url:(NSURL*)url;
- (void)resendChat:(NSString*)message withExternalUrl:(NSString*)url;

View file

@ -63,6 +63,16 @@
if( !chatRoom ) return;
[self clearMessageList];
self->messageList = linphone_chat_room_get_history(chatRoom, 0);
// also append transient upload messages because they are not in history yet!
for (FileTransferDelegate *ftd in [[LinphoneManager instance] fileTransferDelegates]) {
if (linphone_chat_room_get_peer_address(linphone_chat_message_get_chat_room(ftd.message)) ==
linphone_chat_room_get_peer_address(chatRoom) &&
linphone_chat_message_is_outgoing(ftd.message)) {
LOGI(@"Appending transient upload message %p", ftd.message);
self->messageList = ms_list_append(self->messageList, ftd.message);
}
}
}
- (void)reloadData {
@ -76,9 +86,10 @@
messageList = ms_list_append(messageList, linphone_chat_message_ref(chat));
int pos = ms_list_size(messageList) - 1;
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:pos inSection:0]] withRowAnimation:UITableViewRowAnimationFade];
[self.tableView endUpdates];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:pos inSection:0];
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:@[ indexPath ] withRowAnimation:UITableViewRowAnimationFade];
[self.tableView endUpdates];
}
- (void)updateChatEntry:(LinphoneChatMessage*)chat {

View file

@ -24,14 +24,12 @@
#import "ChatRoomTableViewController.h"
#import "HPGrowingTextView.h"
#import "ImagePickerViewController.h"
#import "ImageSharing.h"
#import "OrderedDictionary.h"
#include "linphone/linphonecore.h"
@interface ChatRoomViewController : UIViewController<HPGrowingTextViewDelegate, UICompositeViewDelegate, ImagePickerDelegate, ChatRoomDelegate, LinphoneChatContentTransferDelegate> {
@interface ChatRoomViewController : UIViewController<HPGrowingTextViewDelegate, UICompositeViewDelegate, ImagePickerDelegate, ChatRoomDelegate> {
LinphoneChatRoom *chatRoom;
ImageSharing *imageSharing;
OrderedDictionary *imageQualities;
BOOL scrollOnGrowingEnabled;
BOOL composingVisible;
@ -54,17 +52,12 @@
@property (strong, nonatomic) IBOutlet UIView *composeIndicatorView;
@property (nonatomic, strong) IBOutlet UIButton* pictureButton;
@property (nonatomic, strong) IBOutlet UIButton* cancelTransferButton;
@property (nonatomic, strong) IBOutlet UIProgressView* imageTransferProgressBar;
@property (nonatomic, strong) IBOutlet UIView* transferView;
@property (nonatomic, strong) IBOutlet UIView* waitView;
- (IBAction)onBackClick:(id)event;
- (IBAction)onEditClick:(id)event;
- (IBAction)onMessageChange:(id)sender;
- (IBAction)onSendClick:(id)event;
- (IBAction)onPictureClick:(id)event;
- (IBAction)onTransferCancelClick:(id)event;
- (IBAction)onListTap:(id)sender;
- (void)setChatRoom:(LinphoneChatRoom*)room;

View file

@ -21,20 +21,14 @@
#import "PhoneMainView.h"
#import "DTActionSheet.h"
#import "UILinphone.h"
//#import "UIAlertView+Blocks.h"
#import "DTAlertView.h"
#import "Utils/FileTransferDelegate.h"
#import <NinePatch.h>
#import <MobileCoreServices/UTCoreTypes.h>
#import "Utils.h"
#import "UIChatRoomCell.h"
@implementation ChatRoomViewController {
/* Message transfer transient storage */
NSData* upload_data;
size_t upload_bytes_sent;
NSMutableData* download_data;
}
@implementation ChatRoomViewController
@synthesize tableController;
@synthesize sendButton;
@ -52,32 +46,23 @@
@synthesize listTapGestureRecognizer;
@synthesize listSwipeGestureRecognizer;
@synthesize pictureButton;
@synthesize imageTransferProgressBar;
@synthesize cancelTransferButton;
@synthesize transferView;
@synthesize waitView;
#pragma mark - Lifecycle Functions
- (id)init {
self = [super initWithNibName:@"ChatRoomViewController" bundle:[NSBundle mainBundle]];
if (self != nil) {
self->scrollOnGrowingEnabled = TRUE;
self->chatRoom = NULL;
self->imageSharing = NULL;
self->listTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onListTap:)];
self.listSwipeGestureRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(onListSwipe:)];
self->imageQualities = [[OrderedDictionary alloc] initWithObjectsAndKeys:
[NSNumber numberWithFloat:0.9], NSLocalizedString(@"Maximum", nil),
[NSNumber numberWithFloat:0.5], NSLocalizedString(@"Average", nil),
[NSNumber numberWithFloat:0.0], NSLocalizedString(@"Minimum", nil), nil];
self->composingVisible = TRUE;
self->upload_data = nil;
self->upload_bytes_sent = 0;
self->download_data = nil;
}
return self;
self = [super initWithNibName:@"ChatRoomViewController" bundle:[NSBundle mainBundle]];
if (self != nil) {
self->scrollOnGrowingEnabled = TRUE;
self->chatRoom = NULL;
self->listTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onListTap:)];
self.listSwipeGestureRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(onListSwipe:)];
self->imageQualities = [[OrderedDictionary alloc] initWithObjectsAndKeys:
[NSNumber numberWithFloat:0.9], NSLocalizedString(@"Maximum", nil),
[NSNumber numberWithFloat:0.5], NSLocalizedString(@"Average", nil),
[NSNumber numberWithFloat:0.0], NSLocalizedString(@"Minimum", nil), nil];
self->composingVisible = TRUE;
}
return self;
}
- (void)dealloc {
@ -168,6 +153,7 @@ static UICompositeViewDescription *compositeDescription = nil;
selector:@selector(textComposeEvent:)
name:kLinphoneTextComposeEvent
object:nil];
if([tableController isEditing])
[tableController setEditing:FALSE animated:FALSE];
[editButton setOff];
@ -179,39 +165,16 @@ static UICompositeViewDescription *compositeDescription = nil;
BOOL fileSharingEnabled = [[LinphoneManager instance] lpConfigStringForKey:@"sharing_server_preference"] != NULL
&& [[[LinphoneManager instance] lpConfigStringForKey:@"sharing_server_preference"] length]>0;
[pictureButton setEnabled:fileSharingEnabled];
[waitView setHidden:TRUE];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if(upload_data || download_data ) {
// TODO: when the API permits it, we should cancel the transfer.
}
[messageField resignFirstResponder];
[self setComposingVisible:FALSE withDelay:0]; // will hide the "user is composing.." message
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIApplicationDidBecomeActiveNotification
object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIKeyboardWillShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIKeyboardWillHideNotification
object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:kLinphoneTextReceived
object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UITextViewTextDidChangeNotification
object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:kLinphoneTextComposeEvent
object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
@ -298,9 +261,9 @@ static UICompositeViewDescription *compositeDescription = nil;
}
static void message_status(LinphoneChatMessage* msg,LinphoneChatMessageState state,void* ud) {
ChatRoomViewController* thiz = (__bridge ChatRoomViewController*)ud;
const char*text = linphone_chat_message_get_text(msg);
const char *text = (linphone_chat_message_get_file_transfer_information(msg) != NULL) ? "photo transfer" : linphone_chat_message_get_text(msg);
LOGI(@"Delivery status for [%s] is [%s]",text,linphone_chat_message_state_to_string(state));
ChatRoomViewController* thiz = (__bridge ChatRoomViewController*)ud;
[thiz.tableController updateChatEntry:msg];
}
@ -332,8 +295,6 @@ static void message_status(LinphoneChatMessage* msg,LinphoneChatMessageState sta
}
- (void)chooseImageQuality:(UIImage*)image url:(NSURL*)url {
[waitView setHidden:FALSE];
DTActionSheet *sheet = [[DTActionSheet alloc] initWithTitle:NSLocalizedString(@"Choose the image size", nil)];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//UIImage *image = [original_image normalizedImage];
@ -349,7 +310,6 @@ static void message_status(LinphoneChatMessage* msg,LinphoneChatMessageState sta
}
[sheet addCancelButtonWithTitle:NSLocalizedString(@"Cancel", nil) block:nil];
dispatch_async(dispatch_get_main_queue(), ^{
[waitView setHidden:TRUE];
[sheet showInView:[PhoneMainView instance].view];
});
});
@ -482,20 +442,8 @@ static void message_status(LinphoneChatMessage* msg,LinphoneChatMessageState sta
#pragma mark - Action Functions
- (IBAction)onBackClick:(id)event {
if( upload_data != nil || download_data != nil ){
DTAlertView *alertView = [[DTAlertView alloc] initWithTitle:NSLocalizedString(@"Cancel transfer?", nil)
message:NSLocalizedString(@"You have a transfer in progress, leaving this view will cancel it. Are you sure?", nil)];
[alertView addCancelButtonWithTitle:NSLocalizedString(@"Cancel", nil)
block:nil];
[alertView addButtonWithTitle:NSLocalizedString(@"Yes", nil)
block:^{
[self.tableController setChatRoom:NULL];
[[PhoneMainView instance] popCurrentView];
}];
} else {
[self.tableController setChatRoom:NULL];
[[PhoneMainView instance] popCurrentView];
}
[self.tableController setChatRoom:NULL];
[[PhoneMainView instance] popCurrentView];
}
- (IBAction)onEditClick:(id)event {
@ -573,76 +521,21 @@ static void message_status(LinphoneChatMessage* msg,LinphoneChatMessageState sta
[sheet showInView:[PhoneMainView instance].view];
}
- (IBAction)onTransferCancelClick:(id)event {
if(imageSharing) {
[imageSharing cancel];
}
}
#pragma mark ChatRoomDelegate
- (BOOL)chatRoomStartImageDownload:(LinphoneChatMessage*)msg {
if(self->download_data == nil) {
const char* url = linphone_chat_message_get_external_body_url(msg);
LOGI(@"Content to download: %s", url);
if( url == nil ) return FALSE;
download_data = [[NSMutableData alloc] init];
linphone_chat_message_set_user_data(msg, (void*)CFBridgingRetain(self));
linphone_chat_message_start_file_download(msg,NULL,NULL);
[messageView setHidden:TRUE];
[transferView setHidden:FALSE];
return TRUE;
}
return FALSE;
}
- (BOOL)chatRoomStartImageUpload:(UIImage*)image url:(NSURL*)url{
if( self->upload_data == nil) {
LinphoneContent* content = linphone_core_create_content(linphone_chat_room_get_lc(chatRoom));
self->upload_data = UIImageJPEGRepresentation(image, 1.0);
linphone_content_set_type(content, "image");
linphone_content_set_subtype(content, "jpeg");
linphone_content_set_name(content, [[NSString stringWithFormat:@"%li-%f.jpg", (long)[image hash],[NSDate timeIntervalSinceReferenceDate]] UTF8String]);
linphone_content_set_size(content, [self->upload_data length]);
LinphoneChatMessage* message = linphone_chat_room_create_file_transfer_message(chatRoom, content);
linphone_chat_message_set_user_data(message, (void*)CFBridgingRetain(self));
if ( url ) {
// internal url is saved in the appdata for display and later save
[LinphoneManager setValueInMessageAppData:[url absoluteString] forKey:@"localimage" inMessage:message];
}
// TODO: in the user data, we should maybe put a standalone delegate alloced and retained, instead of self.
// This will make sure that when receiving a memory alert or go to another view, we still send the message
linphone_chat_room_send_message2(chatRoom, message, message_status, (void*)CFBridgingRetain(self));
[tableController addChatEntry:linphone_chat_message_ref(message)];
[tableController scrollToBottom:true];
[messageView setHidden:TRUE];
[transferView setHidden:FALSE];
return TRUE;
}
return FALSE;
FileTransferDelegate * fileTransfer = [[FileTransferDelegate alloc] init];
[[[LinphoneManager instance] fileTransferDelegates] addObject:fileTransfer];
[fileTransfer upload:image withURL:url forChatRoom:chatRoom];
[tableController addChatEntry:linphone_chat_message_ref(fileTransfer.message)];
[tableController scrollToBottom:true];
return TRUE;
}
- (void)resendChat:(NSString *)message withExternalUrl:(NSString *)url {
[self sendMessage:message withExterlBodyUrl:[NSURL URLWithString:url] withInternalURL:nil];
}
#pragma mark ImageSharingDelegate
- (void)imageSharingAborted:(ImageSharing*)aimageSharing {
[messageView setHidden:FALSE];
[transferView setHidden:TRUE];
imageSharing = nil;
}
#pragma mark ImagePickerDelegate
- (void)imagePickerDelegateImage:(UIImage*)image info:(NSDictionary *)info {
@ -659,94 +552,6 @@ static void message_status(LinphoneChatMessage* msg,LinphoneChatMessageState sta
[self chooseImageQuality:image url:url];
}
#pragma mark - LinphoneChatContentTransferDelegate
- (void)onProgressReport:(LinphoneChatMessage *)msg forContent:(const LinphoneContent *)content percent:(int)percent {
[imageTransferProgressBar setProgress:percent/100.0f];
}
- (void)onDataRequested:(LinphoneChatMessage *)msg forContent:(const LinphoneContent *)content buffer:(char *)buffer withSize:(size_t *)size {
if( self->upload_data ){
size_t to_send = *size;
size_t remaining = [upload_data length] - self->upload_bytes_sent;
LOGI(@"Asking %ld bytes, sent %ld of %d, %ld remaining", to_send, self->upload_bytes_sent, [upload_data length], remaining);
if( remaining < to_send ) to_send = remaining;
@try {
[upload_data getBytes:(void*)buffer range:NSMakeRange(upload_bytes_sent, to_send)];
upload_bytes_sent += to_send;
*size = to_send;
}
@catch (NSException *exception) {
LOGE(@"Exception: %@", exception);
}
if( to_send == 0 || upload_bytes_sent == [upload_data length] ){
LOGI(@"Upload finished, cleanup..");
upload_data = nil;
upload_bytes_sent = 0;
// update UI
dispatch_async(dispatch_get_main_queue(), ^{
[messageView setHidden:FALSE];
[transferView setHidden:TRUE];
});
}
} else {
LOGE(@"Error: no upload data in progress!");
}
}
- (void)onDataReceived:(LinphoneChatMessage *)msg forContent:(const LinphoneContent *)content buffer:(const char *)buffer withSize:(size_t)size {
if( download_data ){
LOGI(@"Receiving data for %s : %zu bytes, already got %d of %zu", linphone_content_get_name(content), size, [download_data length], linphone_content_get_size(content));
if( size != 0 ){
[download_data appendBytes:buffer length:size];
}
if( size == 0 && [download_data length] == linphone_content_get_size(content)){
LOGI(@"Transfer is finished, save image and update chat");
dispatch_async(dispatch_get_main_queue(), ^{
//we're finished, save the image and update the message
[messageView setHidden:FALSE];
[transferView setHidden:TRUE];
UIImage* image = [UIImage imageWithData:download_data];
download_data = nil;
[[LinphoneManager instance].photoLibrary
writeImageToSavedPhotosAlbum:image.CGImage
orientation:(ALAssetOrientation)[image imageOrientation]
completionBlock:^(NSURL *assetURL, NSError *error){
if (error) {
LOGE(@"Cannot save image data downloaded [%@]", [error localizedDescription]);
UIAlertView* errorAlert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Transfer error", nil)
message:NSLocalizedString(@"Cannot write image to photo library", nil)
delegate:nil
cancelButtonTitle:NSLocalizedString(@"Ok",nil)
otherButtonTitles:nil ,nil];
[errorAlert show];
return;
}
LOGI(@"Image saved to [%@]", [assetURL absoluteString]);
[LinphoneManager setValueInMessageAppData:[assetURL absoluteString] forKey:@"localimage" inMessage:msg];
[tableController updateChatEntry:msg];
}];
});
}
}
}
#pragma mark - Keyboard Event Functions
- (void)keyboardWillHide:(NSNotification *)notif {

View file

@ -64,43 +64,44 @@ static int sorted_history_comparison(LinphoneChatRoom *to_insert, LinphoneChatRo
LinphoneChatMessage* last_elem_message = linphone_chat_room_get_user_data(elem);
if( last_new_message && last_elem_message ){
time_t new = linphone_chat_message_get_time(last_new_message);
time_t old = linphone_chat_message_get_time(last_elem_message);
if ( new < old ) return 1;
else if ( new > old) return -1;
}
return 0;
time_t new = linphone_chat_message_get_time(last_new_message);
time_t old = linphone_chat_message_get_time(last_elem_message);
if (new < old)
return 1;
else if (new > old)
return -1;
}
return 0;
}
- (MSList*)sortChatRooms {
MSList* sorted = nil;
MSList* unsorted = linphone_core_get_chat_rooms([LinphoneManager getLc]);
MSList* iter = unsorted;
MSList *sorted = nil;
MSList *unsorted = linphone_core_get_chat_rooms([LinphoneManager getLc]);
MSList *iter = unsorted;
while (iter) {
// store last message in user data
MSList* history = linphone_chat_room_get_history(iter->data, 1);
LinphoneChatMessage* last_msg = history? history->data : NULL;
if( last_msg ){
linphone_chat_room_set_user_data(iter->data, linphone_chat_message_ref(last_msg));
}
ms_list_free_with_data(history, (void (*)(void *))linphone_chat_message_unref);
while (iter) {
// store last message in user data
LinphoneChatRoom *chat_room = iter->data;
MSList *history = linphone_chat_room_get_history(iter->data, 1);
assert(ms_list_size(history) <= 1);
LinphoneChatMessage *last_msg = history ? history->data : NULL;
if (last_msg) {
linphone_chat_message_ref(last_msg);
linphone_chat_room_set_user_data(chat_room, last_msg);
}
sorted = ms_list_insert_sorted(sorted, chat_room, (MSCompareFunc)sorted_history_comparison);
sorted = ms_list_insert_sorted(sorted,
iter->data,
(MSCompareFunc)sorted_history_comparison);
iter = iter->next;
}
return sorted;
iter = iter->next;
}
return sorted;
}
static void chatTable_free_chatrooms(void *data){
LinphoneChatMessage* lastMsg = linphone_chat_room_get_user_data(data);
if( lastMsg ){
linphone_chat_message_unref(lastMsg);
linphone_chat_room_set_user_data(data, NULL);
}
LinphoneChatMessage *lastMsg = linphone_chat_room_get_user_data(data);
if (lastMsg) {
linphone_chat_message_unref(lastMsg);
linphone_chat_room_set_user_data(data, NULL);
}
}
- (void)loadData {

View file

@ -1,52 +0,0 @@
/* ImageSharing.h
*
* Copyright (C) 2012 Belledonne Comunications, Grenoble, France
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#import <Foundation/Foundation.h>
@class ImageSharing;
@protocol ImageSharingDelegate <NSObject>
- (void)imageSharingProgress:(ImageSharing*)imageSharing progress:(float)progress;
- (void)imageSharingAborted:(ImageSharing*)imageSharing;
- (void)imageSharingError:(ImageSharing*)imageSharing error:(NSError *)error;
- (void)imageSharingUploadDone:(ImageSharing*)imageSharing url:(NSURL*)url;
- (void)imageSharingDownloadDone:(ImageSharing*)imageSharing image:(UIImage *)image;
@end
@interface ImageSharing : NSObject<NSURLConnectionDataDelegate> {
@private
NSInteger totalBytesExpectedToRead;
id<ImageSharingDelegate> delegate;
NSInteger statusCode;
}
+ (id)newImageSharingUpload:(NSURL*)url image:(UIImage*)image delegate:(id<ImageSharingDelegate>)delegate userInfo:(id)userInfo;
+ (id)newImageSharingDownload:(NSURL*)url delegate:(id<ImageSharingDelegate>)delegate userInfo:(id)userInfo;
- (void)cancel;
@property (nonatomic, strong) id userInfo;
@property (nonatomic, readonly) BOOL upload;
@property (nonatomic, readonly) NSMutableData* data;
@property (nonatomic, readonly) NSURLConnection* connection;
@end

View file

@ -1,176 +0,0 @@
/* ImageSharing.m
*
* Copyright (C) 2012 Belledonne Comunications, Grenoble, France
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#import "ImageSharing.h"
#import "Utils.h"
#import "LinphoneManager.h"
@implementation ImageSharing
@synthesize connection;
@synthesize data;
@synthesize upload;
@synthesize userInfo;
#pragma mark - Lifecycle Functions
+ (id)newImageSharingUpload:(NSURL*)url image:(UIImage*)image delegate:(id<ImageSharingDelegate>)delegate userInfo:(id)auserInfo{
ImageSharing *imgs = [[ImageSharing alloc] init];
if(imgs != nil) {
imgs.userInfo = auserInfo;
imgs->upload = TRUE;
imgs->delegate = delegate;
imgs->data = [[NSMutableData alloc] init];
if(delegate) {
[delegate imageSharingProgress:imgs progress:0];
}
[imgs uploadImageTo:url image:image];
}
return imgs;
}
+ (id)newImageSharingDownload:(NSURL*)url delegate:(id<ImageSharingDelegate>)delegate userInfo:(id)auserInfo{
ImageSharing *imgs = [[ImageSharing alloc] init];
if(imgs != nil) {
imgs.userInfo = auserInfo;
imgs->upload = FALSE;
imgs->delegate = delegate;
imgs->data = [[NSMutableData alloc] init];
if(delegate) {
[delegate imageSharingProgress:imgs progress:0];
}
[imgs downloadImageFrom:url];
}
return imgs;
}
#pragma mark -
- (void)cancel {
[connection cancel];
LOGI(@"File transfer interrupted by user");
if(delegate) {
[delegate imageSharingAborted:self];
}
}
- (void)downloadImageFrom:(NSURL*)url {
LOGI(@"downloading [%@]", [url absoluteString]);
NSURLRequest* request = [NSURLRequest requestWithURL:url
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
connection = [[NSURLConnection alloc] initWithRequest:request delegate: self];
}
- (void)uploadImageTo:(NSURL*)url image:(UIImage*)image {
LOGI(@"downloading [%@]", [url absoluteString]);
// setting up the request object now
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setURL:url];
[request setHTTPMethod:@"POST"];
/*
add some header info now
we always need a boundary when we post a file
also we need to set the content type
You might want to generate a random boundary.. this is just the same
as my output from wireshark on a valid html post
*/
NSString *boundary = @"---------------------------14737809831466499882746641449";
NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@",boundary];
[request addValue:contentType forHTTPHeaderField: @"Content-Type"];
/*
now lets create the body of the post
*/
NSMutableData *body = [NSMutableData data];
NSString *imageName = [NSString stringWithFormat:@"%lu-%f.jpg", (unsigned long)[image hash],[NSDate timeIntervalSinceReferenceDate]];
[body appendData:[[NSString stringWithFormat:@"\r\n--%@\r\n",boundary] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"userfile\"; filename=\"%@\"\r\n",imageName] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[@"Content-Type: application/octet-stream\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[NSData dataWithData:UIImageJPEGRepresentation(image, 1.0)]];
[body appendData:[[NSString stringWithFormat:@"\r\n--%@--\r\n",boundary] dataUsingEncoding:NSUTF8StringEncoding]];
[request setHTTPBody:body];
connection = [[NSURLConnection alloc] initWithRequest:(NSURLRequest *)request delegate:self];
}
#pragma mark - NSURLConnectionDelegate
- (void)connection:(NSURLConnection *)aconnection didFailWithError:(NSError *)error {
if(delegate) {
[delegate imageSharingError:self error:error];
}
}
- (void)connection:(NSURLConnection *)connection didSendBodyData:(NSInteger)bytesWritten totalBytesWritten:(NSInteger)totalBytesWritten totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite {
if(upload && delegate) {
[delegate imageSharingProgress:self progress:(float)totalBytesWritten/(float)totalBytesExpectedToWrite];
}
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)adata {
[data appendData:adata];
if(!upload && delegate) {
[delegate imageSharingProgress:self progress:(float)data.length/(float)totalBytesExpectedToRead];
}
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
NSHTTPURLResponse * httpResponse = (NSHTTPURLResponse *) response;
statusCode = httpResponse.statusCode;
LOGI(@"File transfer status code [%i]", statusCode);
if (statusCode == 200 && !upload) {
totalBytesExpectedToRead = (int)[response expectedContentLength];
}
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
if(statusCode >= 400) {
NSError *error = [NSError errorWithDomain:@"ImageSharing" code:statusCode userInfo:nil];
if(delegate) {
[delegate imageSharingError:self error:error];
}
return;
}
if (upload) {
NSString* imageRemoteUrl = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
LOGI(@"File can be downloaded from [%@]", imageRemoteUrl);
if(delegate) {
[delegate imageSharingUploadDone:self url:[NSURL URLWithString:imageRemoteUrl]];
}
} else {
UIImage* image = [UIImage imageWithData:data];
LOGI(@"File downloaded");
if(delegate) {
[delegate imageSharingDownloadDone:self image:image];
}
}
}
@end

View file

@ -693,7 +693,7 @@ extern void linphone_iphone_log_handler(int lev, const char *fmt, va_list args);
NSString* sharing_server = [self stringForKey:@"sharing_server_preference"];
[[LinphoneManager instance] lpConfigSetString:sharing_server forKey:@"sharing_server_preference"];
linphone_core_set_file_transfer_server(lc, [sharing_server UTF8String]);
//Tunnel
if (linphone_core_tunnel_available()){

View file

@ -52,6 +52,8 @@ extern NSString *const kLinphoneBluetoothAvailabilityUpdate;
extern NSString *const kLinphoneConfiguringStateUpdate;
extern NSString *const kLinphoneGlobalStateUpdate;
extern NSString *const kLinphoneNotifyReceived;
extern NSString *const kLinphoneFileTransferSendUpdate;
extern NSString *const kLinphoneFileTransferRecvUpdate;
typedef enum _NetworkType {
network_none = 0,
@ -98,14 +100,6 @@ struct NetworkReachabilityContext {
};
@end
@protocol LinphoneChatContentTransferDelegate <NSObject>
-(void)onProgressReport:(LinphoneChatMessage*)msg forContent:(const LinphoneContent*)content percent:(int)percent;
-(void)onDataRequested:(LinphoneChatMessage*)msg forContent:(const LinphoneContent*)content buffer:(char*)buffer withSize:(size_t*)size;
-(void)onDataReceived:(LinphoneChatMessage*)msg forContent:(const LinphoneContent*)content buffer:(const char*)buffer withSize:(size_t)size;
@end
typedef struct _LinphoneManagerSounds {
SystemSoundID vibrate;
} LinphoneManagerSounds;
@ -211,5 +205,6 @@ typedef struct _LinphoneManagerSounds {
@property (readonly) BOOL wasRemoteProvisioned;
@property (readonly) LpConfig *configDb;
@property (readonly) InAppProductsManager *iapManager;
@end
@property(strong, nonatomic) NSMutableArray *fileTransferDelegates;
@end

View file

@ -68,7 +68,8 @@ NSString *const kLinphoneBluetoothAvailabilityUpdate = @"LinphoneBluetoothAvaila
NSString *const kLinphoneConfiguringStateUpdate = @"LinphoneConfiguringStateUpdate";
NSString *const kLinphoneGlobalStateUpdate = @"LinphoneGlobalStateUpdate";
NSString *const kLinphoneNotifyReceived = @"LinphoneNotifyReceived";
NSString *const kLinphoneFileTransferSendUpdate = @"LinphoneFileTransferSendUpdate";
NSString *const kLinphoneFileTransferRecvUpdate = @"LinphoneFileTransferRecvUpdate";
const int kLinphoneAudioVbrCodecDefaultBitrate=36; /*you can override this from linphonerc or linphonerc-factory*/
@ -273,6 +274,7 @@ struct codec_name_pref_table codec_pref_table[]={
bluetoothEnabled = FALSE;
tunnelMode = FALSE;
_fileTransferDelegates = [[NSMutableArray alloc] init];
pushCallIDs = [[NSMutableArray alloc] init ];
photoLibrary = [[ALAssetsLibrary alloc] init];
@ -915,29 +917,6 @@ static void linphone_iphone_notify_received(LinphoneCore *lc, LinphoneEvent *lev
[(__bridge LinphoneManager*)linphone_core_get_user_data(lc) onNotifyReceived:lc event:lev notifyEvent:notified_event content:body];
}
#pragma mark - FileTransfer functions
static void linphone_iphone_file_transfer_recv(LinphoneCore *lc, LinphoneChatMessage *message, const LinphoneContent* content, const char* buff, size_t size) {
id <LinphoneChatContentTransferDelegate> delegate = (__bridge id<LinphoneChatContentTransferDelegate>)linphone_chat_message_get_user_data(message);
LOGI(@"Transfer of %s, incoming data (%d bytes)", linphone_content_get_name(content), size);
[delegate onDataReceived:message forContent:content buffer:buff withSize:size];
}
static void linphone_iphone_file_transfer_send(LinphoneCore *lc, LinphoneChatMessage *message, const LinphoneContent* content, char* buff, size_t* size){
id <LinphoneChatContentTransferDelegate> delegate = (__bridge id<LinphoneChatContentTransferDelegate>)linphone_chat_message_get_user_data(message);
LOGI(@"Transfer of %s, requesting data (%d bytes)", linphone_content_get_name(content), *size);
[delegate onDataRequested:message forContent:content buffer:buff withSize:size];
}
static void linphone_iphone_file_transfer_progress(LinphoneCore *lc, LinphoneChatMessage *message, const LinphoneContent* content, size_t offset, size_t total){
id <LinphoneChatContentTransferDelegate> delegate = (__bridge id<LinphoneChatContentTransferDelegate>)linphone_chat_message_get_user_data(message);
float progress = offset*100.f/total;
LOGI(@"Progress of transfer %s: %d%%", linphone_content_get_name(content), progress);
[delegate onProgressReport:message forContent:content percent:progress];
}
#pragma mark - Message composition start
- (void)onMessageComposeReceived:(LinphoneCore*)core forRoom:(LinphoneChatRoom*)room {
@ -1228,11 +1207,7 @@ static LinphoneCoreVTable linphonec_vtable = {
.is_composing_received = linphone_iphone_is_composing_received,
.configuring_status = linphone_iphone_configuring_status_changed,
.global_state_changed = linphone_iphone_global_state_changed,
.notify_received = linphone_iphone_notify_received,
.file_transfer_recv = linphone_iphone_file_transfer_recv,
.file_transfer_send = linphone_iphone_file_transfer_send,
.file_transfer_progress_indication = linphone_iphone_file_transfer_progress
.notify_received = linphone_iphone_notify_received
};
#pragma mark -

View file

@ -1,7 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="4514" systemVersion="13B42" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="7706" systemVersion="14D136" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="3747"/>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="7703"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="UIChatRoomCell">
@ -9,9 +10,11 @@
<outlet property="backgroundImage" destination="3" id="11"/>
<outlet property="backgroundView" destination="7" id="10"/>
<outlet property="bubbleView" destination="16" id="30"/>
<outlet property="cancelButton" destination="fGh-KI-efm" id="R65-2g-q7P"/>
<outlet property="dateLabel" destination="22" id="24"/>
<outlet property="deleteButton" destination="18" id="19"/>
<outlet property="downloadButton" destination="33" id="34"/>
<outlet property="fileTransferProgress" destination="Pd7-2N-Jg3" id="sct-Jr-HnV"/>
<outlet property="innerView" destination="2" id="25"/>
<outlet property="messageImageView" destination="28" id="31"/>
<outlet property="messageText" destination="43" id="44"/>
@ -54,6 +57,7 @@
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" id="33" userLabel="downloadButton">
<rect key="frame" x="81" y="33" width="132" height="37"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="Download"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
<state key="normal" title="Download image">
<color key="titleColor" red="0.19607843459999999" green="0.30980393290000002" blue="0.52156865600000002" alpha="1" colorSpace="calibratedRGB"/>
@ -66,11 +70,31 @@
<action selector="onDownloadClick:" destination="-1" eventType="touchUpInside" id="39"/>
</connections>
</button>
<button hidden="YES" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" id="fGh-KI-efm" userLabel="cancelButton">
<rect key="frame" x="81" y="34" width="132" height="37"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="Cancel transfer"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
<state key="normal" title="Cancel transfer">
<color key="titleColor" red="0.19607843459999999" green="0.30980393290000002" blue="0.52156865600000002" alpha="1" colorSpace="calibratedRGB"/>
<color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
</state>
<state key="highlighted">
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</state>
<connections>
<action selector="onCancelDownloadClick:" destination="-1" eventType="touchUpInside" id="Ag3-jA-20G"/>
</connections>
</button>
<progressView hidden="YES" opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="750" progress="0.5" id="Pd7-2N-Jg3" userLabel="fileTransferProgress">
<rect key="frame" x="15" y="94" width="264" height="2"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
</progressView>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view>
<label opaque="NO" clipsSubviews="YES" contentMode="left" text="09/09/2009 at 09:09" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="8" adjustsFontSizeToFit="NO" id="22" userLabel="timestampLabel">
<rect key="frame" x="0.0" y="104" width="280" height="10"/>
<rect key="frame" x="4" y="104" width="274" height="10"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<fontDescription key="fontDescription" type="system" pointSize="9"/>
@ -103,16 +127,23 @@
</view>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<rect key="contentStretch" x="0.0" y="0.0" width="0.59999999999999964" height="1"/>
<nil key="simulatedStatusBarMetrics"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
</view>
<view contentMode="scaleToFill" id="7" userLabel="backgroundView">
<rect key="frame" x="0.0" y="0.0" width="100" height="100"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<nil key="simulatedStatusBarMetrics"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
</view>
<view contentMode="scaleToFill" id="9" userLabel="selectedBackgroundView">
<rect key="frame" x="0.0" y="0.0" width="100" height="100"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<nil key="simulatedStatusBarMetrics"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
</view>
</objects>
<resources>
@ -121,4 +152,9 @@
<image name="list_delete_default.png" width="45" height="45"/>
<image name="list_delete_over.png" width="45" height="45"/>
</resources>
</document>
<simulatedMetricsContainer key="defaultSimulatedMetrics">
<simulatedStatusBarMetrics key="statusBar"/>
<simulatedOrientationMetrics key="orientation"/>
<simulatedScreenMetrics key="destination" type="retina4"/>
</simulatedMetricsContainer>
</document>

View file

@ -106,11 +106,12 @@
if( last_message ){
const char* text = linphone_chat_message_get_text(last_message);
const char* url = linphone_chat_message_get_external_body_url(last_message);
// Message
if(url) {
[chatContentLabel setText:@""];
const char* text = linphone_chat_message_get_text(last_message);
const char* url = linphone_chat_message_get_external_body_url(last_message);
const LinphoneContent *last_content = linphone_chat_message_get_file_transfer_information(last_message);
// Message
if(url||last_content) {
[chatContentLabel setText:@"🗻"];
} else if (text) {
NSString *message = [NSString stringWithUTF8String:text];
// shorten long messages

View file

@ -24,7 +24,7 @@
#import "UITransparentTVCell.h"
#import "UITextViewNoDefine.h"
#include "linphone/linphonecore.h"
#import "FileTransferDelegate.h"
@interface UIChatRoomCell : UITransparentTVCell {
LinphoneChatMessage* chat;
@ -41,6 +41,8 @@
@property (nonatomic, strong) IBOutlet UIButton* downloadButton;
@property (nonatomic, strong) IBOutlet UITapGestureRecognizer* imageTapGestureRecognizer;
@property (nonatomic, strong) IBOutlet UITapGestureRecognizer* resendTapGestureRecognizer;
@property (weak, nonatomic) IBOutlet UIProgressView *fileTransferProgress;
@property (weak, nonatomic) IBOutlet UIButton *cancelButton;
- (id)initWithIdentifier:(NSString*)identifier;
+ (CGFloat)height:(LinphoneChatMessage*)chatMessage width:(int)width;
@ -50,7 +52,10 @@
- (IBAction)onDeleteClick:(id)event;
- (IBAction)onDownloadClick:(id)event;
- (IBAction)onImageClick:(id)event;
- (IBAction)onCancelDownloadClick:(id)sender;
- (void)setChatMessage:(LinphoneChatMessage*)message;
- (void)connectToFileDelegate:(FileTransferDelegate*)ftd;
@end

View file

@ -28,7 +28,9 @@
#import <NinePatch.h>
#include "linphone/linphonecore.h"
@implementation UIChatRoomCell
@implementation UIChatRoomCell {
FileTransferDelegate* ftd;
}
@synthesize innerView;
@synthesize bubbleView;
@ -56,304 +58,395 @@ static UIFont *CELL_FONT = nil;
#pragma mark - Lifecycle Functions
- (id)initWithIdentifier:(NSString*)identifier {
if ((self = [super initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier]) != nil) {
[[NSBundle mainBundle] loadNibNamed:@"UIChatRoomCell"
owner:self
options:nil];
imageTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onImageClick:)];
[messageImageView addGestureRecognizer:imageTapGestureRecognizer];
if ((self = [super initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier]) != nil) {
[[NSBundle mainBundle] loadNibNamed:@"UIChatRoomCell"
owner:self
options:nil];
imageTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onImageClick:)];
[messageImageView addGestureRecognizer:imageTapGestureRecognizer];
resendTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onResendClick:)];
[dateLabel addGestureRecognizer:resendTapGestureRecognizer];
resendTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onResendClick:)];
[dateLabel addGestureRecognizer:resendTapGestureRecognizer];
[self addSubview:innerView];
[deleteButton setAlpha:0.0f];
[self addSubview:innerView];
[deleteButton setAlpha:0.0f];
// shift message box, otherwise it will collide with the bubble
CGRect messageCoords = [messageText frame];
messageCoords.origin.x += 2;
messageCoords.origin.y += 2;
messageCoords.size.width -= 5;
[messageText setFrame:messageCoords];
messageText.allowSelectAll = TRUE;
}
return self;
// shift message box, otherwise it will collide with the bubble
CGRect messageCoords = [messageText frame];
messageCoords.origin.x += 2;
messageCoords.origin.y += 2;
messageCoords.size.width -= 5;
[messageText setFrame:messageCoords];
messageText.allowSelectAll = TRUE;
}
return self;
}
- (void)dealloc {
[self disconnectFromFileDelegate];
}
#pragma mark -
- (void)setChatMessage:(LinphoneChatMessage *)message {
self->chat = message;
[self update];
if (message != self->chat) {
self->chat = message;
messageImageView.image = nil;
[self disconnectFromFileDelegate];
for (FileTransferDelegate *aftd in [[LinphoneManager instance] fileTransferDelegates]) {
if (message &&
linphone_chat_message_get_storage_id(aftd.message) == linphone_chat_message_get_storage_id(message)) {
LOGI(@"Chat message [%p] with file transfer delegate [%p], connecting to it!", message,
linphone_chat_message_get_user_data(message));
[self connectToFileDelegate:aftd];
break;
}
}
[self update];
}
}
+ (NSString*)decodeTextMessage:(const char*)text {
NSString* decoded = [NSString stringWithUTF8String:text];
if( decoded == nil ){
// couldn't decode the string as UTF8, do a lossy conversion
decoded = [NSString stringWithCString:text encoding:NSASCIIStringEncoding];
if( decoded == nil ){
decoded = @"(invalid string)";
}
}
return decoded;
NSString* decoded = [NSString stringWithUTF8String:text];
if( decoded == nil ){
// couldn't decode the string as UTF8, do a lossy conversion
decoded = [NSString stringWithCString:text encoding:NSASCIIStringEncoding];
if( decoded == nil ){
decoded = @"(invalid string)";
}
}
return decoded;
}
- (void)update {
if(chat == nil) {
LOGW(@"Cannot update chat room cell: null chat");
return;
}
const char* url = linphone_chat_message_get_external_body_url(chat);
const char* text = linphone_chat_message_get_text(chat);
BOOL is_external = url && (strstr(url, "http") == url);
NSString* localImage = [LinphoneManager getMessageAppDataForKey:@"localimage" inMessage:chat];
if(chat == nil) {
LOGW(@"Cannot update chat room cell: null chat");
return;
}
const char* url = linphone_chat_message_get_external_body_url(chat);
const char* text = linphone_chat_message_get_text(chat);
BOOL is_external =
(url && (strstr(url, "http") == url)) || linphone_chat_message_get_file_transfer_information(chat);
NSString* localImage = [LinphoneManager getMessageAppDataForKey:@"localimage" inMessage:chat];
// this is an image (either to download or already downloaded)
if (is_external || localImage) {
if(localImage) {
if (messageImageView.image == nil) {
NSURL *imageUrl = [NSURL URLWithString:localImage];
messageText.hidden = YES;
[messageImageView startLoading];
__block LinphoneChatMessage *achat = chat;
[[LinphoneManager instance]
.photoLibrary assetForURL:imageUrl
resultBlock:^(ALAsset *asset) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, (unsigned long)NULL),
^(void) {
if (achat == self->chat) { // Avoid glitch and scrolling
UIImage *image = [[UIImage alloc] initWithCGImage:[asset thumbnail]];
dispatch_async(dispatch_get_main_queue(), ^{
[messageImageView setImage:image];
[messageImageView setFullImageUrl:asset];
[messageImageView stopLoading];
messageImageView.hidden = NO;
});
}
});
}
failureBlock:^(NSError *error) {
LOGE(@"Can't read image");
}];
}
if (ftd.message != nil) {
_cancelButton.hidden = NO;
_fileTransferProgress.hidden = NO;
downloadButton.hidden = YES;
} else {
_cancelButton.hidden = _fileTransferProgress.hidden = downloadButton.hidden = YES;
}
} else {
messageText.hidden = YES;
messageImageView.hidden = _cancelButton.hidden = _fileTransferProgress.hidden = (ftd.message == nil);
downloadButton.hidden = !_cancelButton.hidden;
}
// simple text message
} else {
[messageText setHidden:FALSE];
if ( text ){
NSString* nstext = [UIChatRoomCell decodeTextMessage:text];
if(is_external && !localImage ) {
[messageText setHidden:TRUE];
[messageImageView setImage:nil];
[messageImageView setHidden:TRUE];
[downloadButton setHidden:FALSE];
/* We need to use an attributed string here so that data detector don't mess
* with the text style. See http://stackoverflow.com/a/20669356 */
} else if(localImage) {
NSAttributedString* attr_text = [[NSAttributedString alloc]
initWithString:nstext
attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:17.0],
NSForegroundColorAttributeName:[UIColor darkGrayColor]}];
messageText.attributedText = attr_text;
NSURL* imageUrl = [NSURL URLWithString:localImage];
} else {
messageText.text = @"";
}
messageImageView.hidden = YES;
_cancelButton.hidden = _fileTransferProgress.hidden = downloadButton.hidden = YES;
}
[messageText setHidden:TRUE];
[messageImageView setImage:nil];
[messageImageView startLoading];
__block LinphoneChatMessage *achat = chat;
[[LinphoneManager instance].photoLibrary assetForURL:imageUrl resultBlock:^(ALAsset *asset) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, (unsigned long)NULL), ^(void) {
if(achat == self->chat) { //Avoid glitch and scrolling
UIImage* image = [[UIImage alloc] initWithCGImage:[asset thumbnail]];
dispatch_async(dispatch_get_main_queue(), ^{
[messageImageView setImage:image];
[messageImageView setFullImageUrl:asset];
[messageImageView stopLoading];
});
}
});
} failureBlock:^(NSError *error) {
LOGE(@"Can't read image");
}];
// Date
time_t chattime = linphone_chat_message_get_time(chat);
NSDate *message_date = (chattime == 0) ? [[NSDate alloc] init] : [NSDate dateWithTimeIntervalSince1970:chattime];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setTimeStyle:NSDateFormatterMediumStyle];
[dateFormatter setDateStyle:NSDateFormatterMediumStyle];
NSLocale *locale = [NSLocale currentLocale];
[dateFormatter setLocale:locale];
[dateLabel setText:[dateFormatter stringFromDate:message_date]];
[messageImageView setHidden:FALSE];
[downloadButton setHidden:TRUE];
} else {
// simple text message
[messageText setHidden:FALSE];
if ( text ){
NSString* nstext = [UIChatRoomCell decodeTextMessage:text];
LinphoneChatMessageState state = linphone_chat_message_get_state(chat);
BOOL outgoing = linphone_chat_message_is_outgoing(chat);
/* We need to use an attributed string here so that data detector don't mess
* with the text style. See http://stackoverflow.com/a/20669356 */
NSAttributedString* attr_text = [[NSAttributedString alloc]
initWithString:nstext
attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:17.0],
NSForegroundColorAttributeName:[UIColor darkGrayColor]}];
messageText.attributedText = attr_text;
} else {
messageText.text = @"";
}
[messageImageView setImage:nil];
[messageImageView setHidden:TRUE];
[downloadButton setHidden:TRUE];
}
// Date
NSDate* message_date = [NSDate dateWithTimeIntervalSince1970:linphone_chat_message_get_time(chat)];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setTimeStyle:NSDateFormatterMediumStyle];
[dateFormatter setDateStyle:NSDateFormatterMediumStyle];
NSLocale *locale = [NSLocale currentLocale];
[dateFormatter setLocale:locale];
[dateLabel setText:[dateFormatter stringFromDate:message_date]];
LinphoneChatMessageState state = linphone_chat_message_get_state(chat);
BOOL outgoing = linphone_chat_message_is_outgoing(chat);
if( !outgoing ){
statusImage.hidden = TRUE; // not useful for incoming chats..
} else if (state== LinphoneChatMessageStateInProgress) {
if( !outgoing ){
statusImage.hidden = TRUE; // not useful for incoming chats..
} else if (state== LinphoneChatMessageStateInProgress) {
[statusImage setImage:[UIImage imageNamed:@"chat_message_inprogress.png"]];
[statusImage setAccessibilityValue:@"in progress"];
[statusImage setAccessibilityValue:@"in progress"];
statusImage.hidden = FALSE;
} else if (state == LinphoneChatMessageStateDelivered) {
} else if (state == LinphoneChatMessageStateDelivered || state == LinphoneChatMessageStateFileTransferDone) {
[statusImage setImage:[UIImage imageNamed:@"chat_message_delivered.png"]];
[statusImage setAccessibilityValue:@"delivered"];
[statusImage setAccessibilityValue:@"delivered"];
statusImage.hidden = FALSE;
} else {
[statusImage setImage:[UIImage imageNamed:@"chat_message_not_delivered.png"]];
[statusImage setAccessibilityValue:@"not delivered"];
[statusImage setAccessibilityValue:@"not delivered"];
statusImage.hidden = FALSE;
NSAttributedString* resend_text = [[NSAttributedString alloc]
initWithString:NSLocalizedString(@"Resend", @"Resend")
attributes:@{NSForegroundColorAttributeName: [UIColor redColor]}];
[dateLabel setAttributedText:resend_text];
NSAttributedString* resend_text = [[NSAttributedString alloc]
initWithString:NSLocalizedString(@"Resend", @"Resend")
attributes:@{NSForegroundColorAttributeName: [UIColor redColor]}];
[dateLabel setAttributedText:resend_text];
}
if( outgoing){
[messageText setAccessibilityLabel:@"Outgoing message"];
} else {
[messageText setAccessibilityLabel:@"Incoming message"];
}
if( outgoing){
[messageText setAccessibilityLabel:@"Outgoing message"];
} else {
[messageText setAccessibilityLabel:@"Incoming message"];
}
}
- (void)setEditing:(BOOL)editing {
[self setEditing:editing animated:FALSE];
[self setEditing:editing animated:FALSE];
}
- (void)setEditing:(BOOL)editing animated:(BOOL)animated {
if(animated) {
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.3];
}
if(editing) {
[deleteButton setAlpha:1.0f];
} else {
[deleteButton setAlpha:0.0f];
}
if(animated) {
[UIView commitAnimations];
}
if(animated) {
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.3];
}
if(editing) {
[deleteButton setAlpha:1.0f];
} else {
[deleteButton setAlpha:0.0f];
}
if(animated) {
[UIView commitAnimations];
}
}
+ (CGSize)viewSize:(LinphoneChatMessage*)chat width:(int)width {
CGSize messageSize;
const char* url = linphone_chat_message_get_external_body_url(chat);
const char* text = linphone_chat_message_get_text(chat);
NSString* messageText = text ? [UIChatRoomCell decodeTextMessage:text] : @"";
if(url == nil) {
if(CELL_FONT == nil) {
CELL_FONT = [UIFont systemFontOfSize:CELL_FONT_SIZE];
}
CGSize messageSize;
const char* url = linphone_chat_message_get_external_body_url(chat);
const char* text = linphone_chat_message_get_text(chat);
NSString* messageText = text ? [UIChatRoomCell decodeTextMessage:text] : @"";
if (url == nil && linphone_chat_message_get_file_transfer_information(chat) == NULL) {
if(CELL_FONT == nil) {
CELL_FONT = [UIFont systemFontOfSize:CELL_FONT_SIZE];
}
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000
if( [[[UIDevice currentDevice] systemVersion] doubleValue] >= 7){
messageSize = [messageText
boundingRectWithSize:CGSizeMake(width - CELL_MESSAGE_X_MARGIN, CGFLOAT_MAX)
options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingTruncatesLastVisibleLine|NSStringDrawingUsesFontLeading)
attributes:@{NSFontAttributeName: CELL_FONT}
context:nil].size;
} else
if( [[[UIDevice currentDevice] systemVersion] doubleValue] >= 7){
messageSize = [messageText
boundingRectWithSize:CGSizeMake(width - CELL_MESSAGE_X_MARGIN, CGFLOAT_MAX)
options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingTruncatesLastVisibleLine|NSStringDrawingUsesFontLeading)
attributes:@{NSFontAttributeName: CELL_FONT}
context:nil].size;
} else
#endif
{
messageSize = [messageText sizeWithFont: CELL_FONT
constrainedToSize: CGSizeMake(width - CELL_MESSAGE_X_MARGIN, 10000.0f)
lineBreakMode: NSLineBreakByTruncatingTail];
}
} else {
messageSize = CGSizeMake(CELL_IMAGE_WIDTH, CELL_IMAGE_HEIGHT);
}
messageSize.height += CELL_MESSAGE_Y_MARGIN;
if(messageSize.height < CELL_MIN_HEIGHT)
messageSize.height = CELL_MIN_HEIGHT;
messageSize.width += CELL_MESSAGE_X_MARGIN;
if(messageSize.width < CELL_MIN_WIDTH)
messageSize.width = CELL_MIN_WIDTH;
return messageSize;
{
messageSize = [messageText sizeWithFont: CELL_FONT
constrainedToSize: CGSizeMake(width - CELL_MESSAGE_X_MARGIN, 10000.0f)
lineBreakMode: NSLineBreakByTruncatingTail];
}
} else {
messageSize = CGSizeMake(CELL_IMAGE_WIDTH, CELL_IMAGE_HEIGHT);
}
messageSize.height += CELL_MESSAGE_Y_MARGIN;
if(messageSize.height < CELL_MIN_HEIGHT)
messageSize.height = CELL_MIN_HEIGHT;
messageSize.width += CELL_MESSAGE_X_MARGIN;
if(messageSize.width < CELL_MIN_WIDTH)
messageSize.width = CELL_MIN_WIDTH;
return messageSize;
}
+ (CGFloat)height:(LinphoneChatMessage*)chatMessage width:(int)width {
return [UIChatRoomCell viewSize:chatMessage width:width].height;
return [UIChatRoomCell viewSize:chatMessage width:width].height;
}
#pragma mark - View Functions
- (void)layoutSubviews {
[super layoutSubviews];
if(chat != nil) {
// Resize inner
CGRect innerFrame;
BOOL is_outgoing = linphone_chat_message_is_outgoing(chat);
innerFrame.size = [UIChatRoomCell viewSize:chat width:[self frame].size.width];
if(!is_outgoing) { // Inverted
innerFrame.origin.x = 0.0f;
innerFrame.origin.y = 0.0f;
} else {
innerFrame.origin.x = [self frame].size.width - innerFrame.size.width;
innerFrame.origin.y = 0.0f;
}
[innerView setFrame:innerFrame];
[super layoutSubviews];
if(chat != nil) {
// Resize inner
CGRect innerFrame;
BOOL is_outgoing = linphone_chat_message_is_outgoing(chat);
innerFrame.size = [UIChatRoomCell viewSize:chat width:[self frame].size.width];
if(!is_outgoing) { // Inverted
innerFrame.origin.x = 0.0f;
innerFrame.origin.y = 0.0f;
} else {
innerFrame.origin.x = [self frame].size.width - innerFrame.size.width;
innerFrame.origin.y = 0.0f;
}
[innerView setFrame:innerFrame];
CGRect messageFrame = [bubbleView frame];
messageFrame.origin.y = ([innerView frame].size.height - messageFrame.size.height)/2;
if(!is_outgoing) { // Inverted
UIImage* image = [UIImage imageNamed:@"chat_bubble_incoming"];
image = [image resizableImageWithCapInsets:UIEdgeInsetsMake(26, 32, 34, 56)];
[backgroundImage setImage:image];
messageFrame.origin.y += 5;
} else {
UIImage* image = [UIImage imageNamed:@"chat_bubble_outgoing"];
image = [image resizableImageWithCapInsets:UIEdgeInsetsMake(14, 15, 25, 40)];
[backgroundImage setImage:image];
messageFrame.origin.y -= 5;
}
[bubbleView setFrame:messageFrame];
}
CGRect messageFrame = [bubbleView frame];
messageFrame.origin.y = ([innerView frame].size.height - messageFrame.size.height)/2;
if(!is_outgoing) { // Inverted
UIImage* image = [UIImage imageNamed:@"chat_bubble_incoming"];
image = [image resizableImageWithCapInsets:UIEdgeInsetsMake(26, 32, 34, 56)];
[backgroundImage setImage:image];
messageFrame.origin.y += 5;
} else {
UIImage* image = [UIImage imageNamed:@"chat_bubble_outgoing"];
image = [image resizableImageWithCapInsets:UIEdgeInsetsMake(14, 15, 25, 40)];
[backgroundImage setImage:image];
messageFrame.origin.y -= 5;
}
[bubbleView setFrame:messageFrame];
}
}
#pragma mark - Action Functions
- (IBAction)onDeleteClick:(id)event {
if(chat != NULL) {
UIView *view = [self superview];
// Find TableViewCell
while(view != nil && ![view isKindOfClass:[UITableView class]]) view = [view superview];
if(view != nil) {
UITableView *tableView = (UITableView*) view;
NSIndexPath *indexPath = [tableView indexPathForCell:self];
[[tableView dataSource] tableView:tableView commitEditingStyle:UITableViewCellEditingStyleDelete forRowAtIndexPath:indexPath];
}
}
if(chat != NULL) {
if (ftd.message != nil) {
[ftd cancel];
}
UIView *view = [self superview];
// Find TableViewCell
while(view != nil && ![view isKindOfClass:[UITableView class]]) view = [view superview];
if(view != nil) {
UITableView *tableView = (UITableView*) view;
NSIndexPath *indexPath = [tableView indexPathForCell:self];
[[tableView dataSource] tableView:tableView commitEditingStyle:UITableViewCellEditingStyleDelete forRowAtIndexPath:indexPath];
}
}
}
- (IBAction)onDownloadClick:(id)event {
[chatRoomDelegate chatRoomStartImageDownload:chat];
if (ftd.message == nil) {
ftd = [[FileTransferDelegate alloc] init];
[[[LinphoneManager instance] fileTransferDelegates] addObject:ftd];
[self connectToFileDelegate:ftd];
[ftd download:chat];
_cancelButton.hidden = NO;
downloadButton.hidden = YES;
}
}
- (IBAction)onCancelDownloadClick:(id)sender {
[ftd cancel];
[self disconnectFromFileDelegate];
[self update];
}
- (IBAction)onImageClick:(id)event {
if(![messageImageView isLoading]) {
ImageViewController *controller = DYNAMIC_CAST([[PhoneMainView instance] changeCurrentView:[ImageViewController compositeViewDescription] push:TRUE], ImageViewController);
if(controller != nil) {
CGImageRef fullScreenRef = [[messageImageView.fullImageUrl defaultRepresentation] fullScreenImage];
UIImage* fullScreen = [UIImage imageWithCGImage:fullScreenRef];
[controller setImage:fullScreen];
}
}
// if(![messageImageView isLoading]) {
// ImageViewController *controller = DYNAMIC_CAST([[PhoneMainView instance]
// changeCurrentView:[ImageViewController compositeViewDescription] push:TRUE], ImageViewController);
// if(controller != nil) {
// CGImageRef fullScreenRef = [[messageImageView.fullImageUrl defaultRepresentation] fullScreenImage];
// UIImage* fullScreen = [UIImage imageWithCGImage:fullScreenRef];
// [controller setImage:fullScreen];
// }
// }
[LinphoneManager setValueInMessageAppData:nil forKey:@"localimage" inMessage:chat];
}
- (IBAction)onResendClick:(id)event {
if( chat == nil ) return;
if( chat == nil ) return;
LinphoneChatMessageState state = linphone_chat_message_get_state(self->chat);
if (state == LinphoneChatMessageStateNotDelivered) {
const char* text = linphone_chat_message_get_text(self->chat);
const char* url = linphone_chat_message_get_external_body_url(self->chat);
NSString* message = text ? [NSString stringWithUTF8String:text] : nil;
NSString* exturl = url ? [NSString stringWithUTF8String:url] : nil;
LinphoneChatMessageState state = linphone_chat_message_get_state(self->chat);
if (state == LinphoneChatMessageStateNotDelivered) {
const char* text = linphone_chat_message_get_text(self->chat);
const char* url = linphone_chat_message_get_external_body_url(self->chat);
NSString* message = text ? [NSString stringWithUTF8String:text] : nil;
NSString* exturl = url ? [NSString stringWithUTF8String:url] : nil;
[self onDeleteClick:nil];
[self onDeleteClick:nil];
double delayInSeconds = 0.4;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[chatRoomDelegate resendChat:message withExternalUrl:exturl];
});
}
double delayInSeconds = 0.4;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[chatRoomDelegate resendChat:message withExternalUrl:exturl];
});
}
}
#pragma mark - LinphoneFileTransfer Notifications Handling
- (void)connectToFileDelegate:(FileTransferDelegate*)aftd {
ftd = aftd;
_fileTransferProgress.progress = 0;
[[NSNotificationCenter defaultCenter] removeObserver:self];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(onFileTransferSendUpdate:)
name:kLinphoneFileTransferSendUpdate
object:ftd];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(onFileTransferRecvUpdate:)
name:kLinphoneFileTransferRecvUpdate
object:ftd];
}
- (void)disconnectFromFileDelegate {
[[NSNotificationCenter defaultCenter] removeObserver:self];
ftd = nil;
}
- (void)onFileTransferSendUpdate:(NSNotification*)notif {
LinphoneChatMessageState state = [[[notif userInfo] objectForKey:@"state"] intValue];
if (state == LinphoneChatMessageStateInProgress) {
float progress = [[[notif userInfo] objectForKey:@"progress"] floatValue];
// When uploading a file, the message file is first uploaded to the server,
// so we are in progress state. Then state goes to filetransfertdone. Then,
// the exact same message is sent to the other chat participant and we come
// back to in progress again. This second time is NOT an upload, so we must
// not update progress!
_fileTransferProgress.progress = MAX(_fileTransferProgress.progress, progress);
_fileTransferProgress.hidden = _cancelButton.hidden = (_fileTransferProgress.progress == 1.f);
} else {
[self update];
}
}
- (void)onFileTransferRecvUpdate:(NSNotification*)notif {
LinphoneChatMessageState state = [[[notif userInfo] objectForKey:@"state"] intValue];
if (state == LinphoneChatMessageStateInProgress) {
float progress = [[[notif userInfo] objectForKey:@"progress"] floatValue];
_fileTransferProgress.progress = MAX(_fileTransferProgress.progress, progress);
_fileTransferProgress.hidden = _cancelButton.hidden = (_fileTransferProgress.progress == 1.f);
} else {
[self update];
}
}
@end

View file

@ -0,0 +1,20 @@
//
// FileTransferDelegate.h
// linphone
//
// Created by Gautier Pelloux-Prayer on 10/06/15.
//
//
#import <Foundation/Foundation.h>
#import "LinphoneManager.h"
@interface FileTransferDelegate : NSObject
- (void)upload:(UIImage *)image withURL:(NSURL *)url forChatRoom:(LinphoneChatRoom *)chatRoom;
- (void)cancel;
- (BOOL)download:(LinphoneChatMessage *)message;
@property() LinphoneChatMessage *message;
@end

View file

@ -0,0 +1,207 @@
//
// FileTransferDelegate.m
// linphone
//
// Created by Gautier Pelloux-Prayer on 10/06/15.
//
//
#import "FileTransferDelegate.h"
@interface FileTransferDelegate ()
@property(strong) NSMutableData *data;
@end
@implementation FileTransferDelegate
- (void)dealloc {
if (_message != nil) {
[self cancel];
}
}
static void linphone_iphone_file_transfer_recv(LinphoneChatMessage *message, const LinphoneContent *content,
const LinphoneBuffer *buffer) {
FileTransferDelegate *thiz = (__bridge FileTransferDelegate *)linphone_chat_message_get_user_data(message);
size_t size = linphone_buffer_get_size(buffer);
if (!thiz.data) {
thiz.data = [[NSMutableData alloc] initWithCapacity:linphone_content_get_size(content)];
}
if (size == 0) {
LOGI(@"Transfer of %s (%d bytes): download finished", linphone_content_get_name(content), size);
assert([thiz.data length] == linphone_content_get_size(content));
// we're finished, save the image and update the message
UIImage *image = [UIImage imageWithData:thiz.data];
[[[LinphoneManager instance] fileTransferDelegates] removeObject:thiz];
[[LinphoneManager instance]
.photoLibrary
writeImageToSavedPhotosAlbum:image.CGImage
orientation:(ALAssetOrientation)[image imageOrientation]
completionBlock:^(NSURL *assetURL, NSError *error) {
if (error) {
LOGE(@"Cannot save image data downloaded [%@]", [error localizedDescription]);
UIAlertView *errorAlert = [[UIAlertView alloc]
initWithTitle:NSLocalizedString(@"Transfer error", nil)
message:NSLocalizedString(@"Cannot write image to photo library", nil)
delegate:nil
cancelButtonTitle:NSLocalizedString(@"Ok", nil)
otherButtonTitles:nil, nil];
[errorAlert show];
} else {
LOGI(@"Image saved to [%@]", [assetURL absoluteString]);
[LinphoneManager setValueInMessageAppData:[assetURL absoluteString]
forKey:@"localimage"
inMessage:message];
}
thiz.message = NULL;
[[NSNotificationCenter defaultCenter]
postNotificationName:kLinphoneFileTransferRecvUpdate
object:thiz
userInfo:@{
@"state" : @(linphone_chat_message_get_state(message)),
@"image" : image,
@"progress" :
@([thiz.data length] * 1.f / linphone_content_get_size(content)),
}];
CFRelease((__bridge CFTypeRef)thiz);
}];
} else {
LOGI(@"Transfer of %s (%d bytes): already %ld sent, adding %ld", linphone_content_get_name(content),
linphone_content_get_size(content), [thiz.data length], size);
[thiz.data appendBytes:linphone_buffer_get_string_content(buffer) length:size];
[[NSNotificationCenter defaultCenter]
postNotificationName:kLinphoneFileTransferRecvUpdate
object:thiz
userInfo:@{
@"state" : @(linphone_chat_message_get_state(message)),
@"progress" : @([thiz.data length] * 1.f / linphone_content_get_size(content)),
}];
}
}
static LinphoneBuffer *linphone_iphone_file_transfer_send(LinphoneChatMessage *message, const LinphoneContent *content,
size_t offset, size_t size) {
FileTransferDelegate *thiz = (__bridge FileTransferDelegate *)linphone_chat_message_get_user_data(message);
size_t total = thiz.data.length;
if (thiz.data) {
size_t remaining = total - offset;
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithDictionary:@{
@"state" : @(linphone_chat_message_get_state(message)),
@"progress" : @(offset * 1.f / total),
}];
LOGI(@"Transfer of %s (%d bytes): already sent %ld, remaining %ld", linphone_content_get_name(content), total,
offset, remaining);
[[NSNotificationCenter defaultCenter] postNotificationName:kLinphoneFileTransferSendUpdate
object:thiz
userInfo:dict];
@try {
return linphone_buffer_new_from_data([thiz.data subdataWithRange:NSMakeRange(offset, size)].bytes, size);
} @catch (NSException *exception) {
LOGE(@"Exception: %@", exception);
}
} else {
LOGE(@"Transfer of %s (%d bytes): %d Error - no upload data in progress!", linphone_content_get_name(content),
total, offset);
}
return NULL;
}
static void message_status(LinphoneChatMessage *msg, LinphoneChatMessageState state) {
FileTransferDelegate *thiz = (__bridge FileTransferDelegate *)linphone_chat_message_get_user_data(msg);
NSString *notification =
linphone_chat_message_is_outgoing(msg) ? kLinphoneFileTransferSendUpdate : kLinphoneFileTransferRecvUpdate;
const char *text = (linphone_chat_message_get_file_transfer_information(msg) != NULL)
? "photo transfer"
: linphone_chat_message_get_text(msg);
LOGI(@"Delivery status for [%s] is [%s]", text, linphone_chat_message_state_to_string(state));
NSDictionary *dict = @{ @"state" : @(state), @"progress" : @0.f };
if (state == LinphoneChatMessageStateFileTransferDone || state == LinphoneChatMessageStateFileTransferError) {
thiz.message = NULL;
}
[[NSNotificationCenter defaultCenter] postNotificationName:notification object:thiz userInfo:dict];
if (linphone_chat_message_is_outgoing(msg)) {
[thiz stopAndDestroy];
}
}
- (void)upload:(UIImage *)image withURL:(NSURL *)url forChatRoom:(LinphoneChatRoom *)chatRoom {
LinphoneContent *content = linphone_core_create_content(linphone_chat_room_get_lc(chatRoom));
_data = [NSMutableData dataWithData:UIImageJPEGRepresentation(image, 1.0)];
linphone_content_set_type(content, "image");
linphone_content_set_subtype(content, "jpeg");
linphone_content_set_name(content,
[[NSString stringWithFormat:@"%li-%f.jpg", (long)[image hash],
[NSDate timeIntervalSinceReferenceDate]] UTF8String]);
linphone_content_set_size(content, [_data length]);
CFTypeRef myself = (__bridge CFTypeRef)self;
_message = linphone_chat_room_create_file_transfer_message(chatRoom, content);
linphone_chat_message_ref(_message);
linphone_chat_message_set_user_data(_message, (void *)CFRetain(myself));
linphone_chat_message_cbs_set_file_transfer_send(linphone_chat_message_get_callbacks(_message),
linphone_iphone_file_transfer_send);
linphone_chat_message_cbs_set_msg_state_changed(linphone_chat_message_get_callbacks(_message), message_status);
if (url) {
// internal url is saved in the appdata for display and later save
[LinphoneManager setValueInMessageAppData:[url absoluteString] forKey:@"localimage" inMessage:_message];
}
linphone_chat_room_send_chat_message(chatRoom, _message);
}
- (BOOL)download:(LinphoneChatMessage *)message {
_message = message;
// we need to keep a ref on the message to continue downloading even if user quit a chatroom which destroy all chat
// messages
linphone_chat_message_ref(_message);
const char *url = linphone_chat_message_get_external_body_url(_message);
LOGI(@"Content to download: %s", url);
if (url == nil)
return FALSE;
linphone_chat_message_set_user_data(_message, (void *)CFBridgingRetain(self));
linphone_chat_message_cbs_set_file_transfer_recv(linphone_chat_message_get_callbacks(_message),
linphone_iphone_file_transfer_recv);
linphone_chat_message_cbs_set_msg_state_changed(linphone_chat_message_get_callbacks(_message), message_status);
linphone_chat_message_download_file(_message);
return TRUE;
}
- (void)stopAndDestroy {
[[[LinphoneManager instance] fileTransferDelegates] removeObject:self];
if (_message != NULL) {
linphone_chat_message_set_user_data(_message, NULL);
linphone_chat_message_cbs_set_file_transfer_progress_indication(linphone_chat_message_get_callbacks(_message),
NULL);
linphone_chat_message_cbs_set_file_transfer_recv(linphone_chat_message_get_callbacks(_message), NULL);
linphone_chat_message_cbs_set_msg_state_changed(linphone_chat_message_get_callbacks(_message), NULL);
linphone_chat_message_cancel_file_transfer(_message);
linphone_chat_message_unref(_message);
}
_message = nil;
_data = nil;
}
- (void)cancel {
[self stopAndDestroy];
}
@end

View file

@ -29,7 +29,10 @@
OrtpLogLevel ortp_severity;
int filesize = 20;
if (severity <= LinphoneLoggerDebug) {
ortp_severity = ORTP_DEBUG;
// lol: ortp_debug(XXX) can be disabled at compile time, but ortp_log(ORTP_DEBUG, xxx) will always be valid even
// not in debug build...
ortp_debug("%*s:%3d - %s", filesize, file+MAX((int)strlen(file)-filesize,0), line, [str UTF8String]);
return;
} else if(severity <= LinphoneLoggerLog) {
ortp_severity = ORTP_MESSAGE;
} else if(severity <= LinphoneLoggerWarning) {

View file

@ -8,6 +8,7 @@
#import "ChatTester.h"
#include "LinphoneManager.h"
#import "UIChatRoomCell.h"
@implementation ChatTester
@ -17,12 +18,36 @@
- (void)beforeAll {
[super beforeAll];
[self switchToValidAccountIfNeeded];
[tester tapViewWithAccessibilityLabel:LOCALIZED(@"Chat")];
}
- (void)beforeEach {
[super beforeEach];
if ([tester tryFindingTappableViewWithAccessibilityLabel:LOCALIZED(@"Back") error:nil]) {
[self goBackFromChat];
}
[tester tapViewWithAccessibilityLabel:LOCALIZED(@"Chat")];
[self removeAllRooms];
}
- (void)afterAll {
// at the end of tests, go back to chat rooms to display main bar
if ([tester tryFindingTappableViewWithAccessibilityLabel:LOCALIZED(@"Back") error:nil]) {
[self goBackFromChat];
}
}
#pragma mark - tools
- (void)removeAllRooms {
[tester tapViewWithAccessibilityLabel:@"Edit" traits:UIAccessibilityTraitButton];
while (
[tester tryFindingTappableViewWithAccessibilityLabel:@"Delete" traits:UIAccessibilityTraitButton error:nil]) {
[tester tapViewWithAccessibilityLabel:@"Delete" traits:UIAccessibilityTraitButton];
}
[tester tapViewWithAccessibilityLabel:@"Edit"
traits:UIAccessibilityTraitButton]; // same as the first but it is "OK" on screen
}
- (void)goBackFromChat {
[tester tapViewWithAccessibilityLabel:LOCALIZED(@"Back")];
}
@ -114,29 +139,91 @@
for( int i =0; i< uuids.count; i++){
[tester tapViewWithAccessibilityLabel:@"Delete" traits:UIAccessibilityTraitButton];
}
// then we try to delete all the rest of chatrooms
while ( [tester tryFindingTappableViewWithAccessibilityLabel:@"Delete" traits:UIAccessibilityTraitButton error:nil] )
{
[tester tapViewWithAccessibilityLabel:@"Delete" traits:UIAccessibilityTraitButton];
NSLog(@"Deleting an extra chat");
}
[tester tapViewWithAccessibilityLabel:@"Edit" traits:UIAccessibilityTraitButton]; // same as the first but it is "OK" on screen
// check that the tableview is empty
UITableView* tv = nil;
NSError* err = nil;
if( [tester tryFindingAccessibilityElement:nil view:&tv withIdentifier:@"ChatRoom list" tappable:false error:&err] ){
XCTAssert(tv != nil);
XCTAssert([tv numberOfRowsInSection:0] == 0); // no more chat rooms
} else {
NSLog(@"Error: %@",err);
}
// test that there's no more chatrooms in the core
XCTAssert(linphone_core_get_chat_rooms([LinphoneManager getLc]) == nil);
[tester tapViewWithAccessibilityLabel:@"Edit"
traits:UIAccessibilityTraitButton]; // same as the first but it is "OK" on screen
// check that the tableview is empty
UITableView *tv = [self findTableView:@"ChatRoom list"];
XCTAssert([tv numberOfRowsInSection:0] == 0);
// test that there's no more chatrooms in the core
XCTAssert(linphone_core_get_chat_rooms([LinphoneManager getLc]) == nil);
}
- (UITableView *)findTableView:(NSString *)table {
UITableView *tv = nil;
NSError *err = nil;
if ([tester tryFindingAccessibilityElement:nil view:&tv withIdentifier:table tappable:false error:&err]) {
XCTAssertNotNil(tv);
} else {
XCTFail(@"Error: %@", err);
}
return tv;
}
- (void)uploadImage {
NSString *user = @"testios";
[self startChatWith:user];
[tester tapViewWithAccessibilityLabel:LOCALIZED(@"Send picture")];
[tester tapViewWithAccessibilityLabel:LOCALIZED(@"Photo library")];
// if popup "Linphone would access your photo" pops up, click OK.
if ([ALAssetsLibrary authorizationStatus] == ALAuthorizationStatusNotDetermined) {
[tester acknowledgeSystemAlert];
}
[tester choosePhotoInAlbum:@"Camera Roll" atRow:1 column:1];
// TODO: do not harcode size!
[tester tapViewWithAccessibilityLabel:LOCALIZED(@"Minimum (108.9 KB)")];
UITableView *tv = [self findTableView:@"Chat list"];
XCTAssertEqual([tv numberOfRowsInSection:0], 1);
XCTAssertEqual([[[LinphoneManager instance] fileTransferDelegates] count], 1);
}
- (void)testUploadImage {
NSString *user = @"testios";
[self uploadImage];
[self goBackFromChat];
// if we go back to the same chatroom, the message should be still there
[self startChatWith:user];
UITableView *tv = [self findTableView:@"Chat list"];
XCTAssertEqual([tv numberOfRowsInSection:0], 1);
[tester waitForViewWithAccessibilityLabel:LOCALIZED(@"Download")];
XCTAssertEqual([tv numberOfRowsInSection:0], 2);
XCTAssertEqual([[[LinphoneManager instance] fileTransferDelegates] count], 0);
}
- (void)testCancelUploadImage {
[self uploadImage];
[tester tapViewWithAccessibilityLabel:LOCALIZED(@"Cancel transfer")];
XCTAssertEqual([[[LinphoneManager instance] fileTransferDelegates] count], 0);
}
- (void)downloadImage {
[self uploadImage];
[tester tapViewWithAccessibilityLabel:LOCALIZED(@"Download")];
[tester waitForTimeInterval:.5f]; // just wait a few secs to start download
XCTAssertEqual([[[LinphoneManager instance] fileTransferDelegates] count], 1);
}
- (void)testDownloadImage {
[self downloadImage];
[tester waitForAbsenceOfViewWithAccessibilityLabel:@"Cancel transfer"];
XCTAssertEqual([[[LinphoneManager instance] fileTransferDelegates] count], 0);
}
- (void)testCancelDownloadImage {
[self downloadImage];
[tester tapViewWithAccessibilityLabel:LOCALIZED(@"Cancel transfer")];
XCTAssertEqual([[[LinphoneManager instance] fileTransferDelegates] count], 0);
}
@end

View file

@ -73,7 +73,7 @@ username_length=4
expires=1314000
push_notification=1
transport=tls
sharing_server=https://www.linphone.org:444/upload.php
sharing_server=https://www.linphone.org:444/lft.php
ice=1
stun=stun.linphone.org

View file

@ -73,7 +73,7 @@ username_length=4
expires=1314000
push_notification=1
transport=tls
sharing_server=https://www.linphone.org:444/upload.php
sharing_server=https://www.linphone.org:444/lft.php
ice=1
stun=stun.linphone.org

View file

@ -120,6 +120,7 @@
636316D41A1DEC650009B839 /* SettingsViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 636316D61A1DEC650009B839 /* SettingsViewController.xib */; };
636316D91A1DECC90009B839 /* PhoneMainView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 636316D71A1DECC90009B839 /* PhoneMainView.xib */; };
636316DE1A1DEF2F0009B839 /* UIButtonShrinkable.m in Sources */ = {isa = PBXBuildFile; fileRef = 636316DD1A1DEF2F0009B839 /* UIButtonShrinkable.m */; };
637157A11B283FE200C91677 /* FileTransferDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 637157A01B283FE200C91677 /* FileTransferDelegate.m */; };
639CEAFD1A1DF4D9004DE38F /* UIStateBar.xib in Resources */ = {isa = PBXBuildFile; fileRef = 639CEAFF1A1DF4D9004DE38F /* UIStateBar.xib */; };
639CEB001A1DF4E4004DE38F /* UIHistoryCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 639CEB021A1DF4E4004DE38F /* UIHistoryCell.xib */; };
639CEB031A1DF4EB004DE38F /* UICompositeViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 639CEB051A1DF4EB004DE38F /* UICompositeViewController.xib */; };
@ -306,7 +307,6 @@
D36C43F7158F61EA0048BA40 /* call_state_play_over.png in Resources */ = {isa = PBXBuildFile; fileRef = D36C43F0158F61EA0048BA40 /* call_state_play_over.png */; };
D36FB2D51589EF7C0036F6F2 /* UIPauseButton.m in Sources */ = {isa = PBXBuildFile; fileRef = D36FB2D41589EF7C0036F6F2 /* UIPauseButton.m */; };
D37295DB158B3C9600D2C0C7 /* video_off_disabled.png in Resources */ = {isa = PBXBuildFile; fileRef = D37295DA158B3C9600D2C0C7 /* video_off_disabled.png */; };
D374D3FD16071762003D25FF /* ImageSharing.m in Sources */ = {isa = PBXBuildFile; fileRef = D374D3FC16071762003D25FF /* ImageSharing.m */; };
D377BBFA15A19DA6002B696B /* video_on_disabled.png in Resources */ = {isa = PBXBuildFile; fileRef = D377BBF915A19DA6002B696B /* video_on_disabled.png */; };
D378906515AC373B00BD776C /* ContactDetailsLabelViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D378906315AC373B00BD776C /* ContactDetailsLabelViewController.m */; };
D378AB2A15DCDB4A0098505D /* ImagePickerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D378AB2915DCDB490098505D /* ImagePickerViewController.m */; };
@ -892,7 +892,6 @@
/* Begin PBXFileReference section */
045B5CB218D72E9A0088350C /* libbzrtp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libbzrtp.a; path = "liblinphone-sdk/apple-darwin/lib/libbzrtp.a"; sourceTree = "<group>"; };
15017E6F1773578400784ACB /* libxml2.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libxml2.a; path = "liblinphone-sdk/apple-darwin/lib/libxml2.a"; sourceTree = "<group>"; };
152F22331B15E83B008C0621 /* libilbcrfc3951.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libilbcrfc3951.a; path = "liblinphone-sdk/apple-darwin/lib/libilbcrfc3951.a"; sourceTree = "<group>"; };
152F22351B15E889008C0621 /* libxml2.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libxml2.dylib; path = usr/lib/libxml2.dylib; sourceTree = SDKROOT; };
1560821E18EEF26100765332 /* libmsopenh264.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libmsopenh264.a; path = "liblinphone-sdk/apple-darwin/lib/mediastreamer/plugins/libmsopenh264.a"; sourceTree = "<group>"; };
@ -1039,6 +1038,8 @@
636316DB1A1DEDD80009B839 /* ru */ = {isa = PBXFileReference; fileEncoding = 2483028224; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/HistoryDetailsViewController.strings; sourceTree = "<group>"; };
636316DC1A1DEECB0009B839 /* UIButtonShrinkable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UIButtonShrinkable.h; sourceTree = "<group>"; };
636316DD1A1DEF2F0009B839 /* UIButtonShrinkable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIButtonShrinkable.m; sourceTree = "<group>"; };
6371579F1B283FE200C91677 /* FileTransferDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FileTransferDelegate.h; path = Utils/FileTransferDelegate.h; sourceTree = "<group>"; };
637157A01B283FE200C91677 /* FileTransferDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FileTransferDelegate.m; path = Utils/FileTransferDelegate.m; sourceTree = "<group>"; };
639CEAFE1A1DF4D9004DE38F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/UIStateBar.xib; sourceTree = "<group>"; };
639CEB011A1DF4E4004DE38F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/UIHistoryCell.xib; sourceTree = "<group>"; };
639CEB041A1DF4EB004DE38F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/UICompositeViewController.xib; sourceTree = "<group>"; };
@ -1258,8 +1259,6 @@
D36FB2D31589EF7C0036F6F2 /* UIPauseButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UIPauseButton.h; sourceTree = "<group>"; };
D36FB2D41589EF7C0036F6F2 /* UIPauseButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIPauseButton.m; sourceTree = "<group>"; };
D37295DA158B3C9600D2C0C7 /* video_off_disabled.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = video_off_disabled.png; path = Resources/video_off_disabled.png; sourceTree = "<group>"; };
D374D3FB16071762003D25FF /* ImageSharing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ImageSharing.h; sourceTree = "<group>"; };
D374D3FC16071762003D25FF /* ImageSharing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ImageSharing.m; sourceTree = "<group>"; };
D377BBF915A19DA6002B696B /* video_on_disabled.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = video_on_disabled.png; path = Resources/video_on_disabled.png; sourceTree = "<group>"; };
D378906215AC373B00BD776C /* ContactDetailsLabelViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ContactDetailsLabelViewController.h; sourceTree = "<group>"; };
D378906315AC373B00BD776C /* ContactDetailsLabelViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ContactDetailsLabelViewController.m; sourceTree = "<group>"; };
@ -2041,8 +2040,6 @@
D38187D415FE346B00C3EDCA /* HistoryViewController.xib */,
D378AB2815DCDB480098505D /* ImagePickerViewController.h */,
D378AB2915DCDB490098505D /* ImagePickerViewController.m */,
D374D3FB16071762003D25FF /* ImageSharing.h */,
D374D3FC16071762003D25FF /* ImageSharing.m */,
22405EFD1601C19000B92522 /* ImageViewController.h */,
22405EFE1601C19100B92522 /* ImageViewController.m */,
D37EE11016035793003608A6 /* ImageViewController.xib */,
@ -2319,7 +2316,6 @@
D30BF33216A427BC00AF0026 /* libtunnel.a */,
7066FC0B13E830E400EFC6DC /* libvpx.a */,
22AA8AFB13D7125500B30535 /* libx264.a */,
15017E6F1773578400784ACB /* libxml2.a */,
F0B89C2118DC89E30050B60E /* MediaPlayer.framework */,
D37DC7171594AF3400B2A5EB /* MessageUI.framework */,
226EF06B15FA256B005865C7 /* MobileCoreServices.framework */,
@ -2345,6 +2341,8 @@
D37EE15F160377D7003608A6 /* DTFoundation */,
D32B9DFA15A2F131000B6DEC /* FastAddressBook.h */,
D32B9DFB15A2F131000B6DEC /* FastAddressBook.m */,
6371579F1B283FE200C91677 /* FileTransferDelegate.h */,
637157A01B283FE200C91677 /* FileTransferDelegate.m */,
D3ED40141602172200BF332B /* GrowingTextView */,
D3807FC715C2894A005BE9BC /* InAppSettingsKit */,
D3B90E1115C2CB5700F64F8C /* NinePatch.xcodeproj */,
@ -4059,13 +4057,13 @@
D380800515C28A7A005BE9BC /* UILinphone.m in Sources */,
D380801315C299D0005BE9BC /* ColorSpaceUtilites.m in Sources */,
6359DE7F1ADEB54200EA15C0 /* InAppProductsViewController.m in Sources */,
637157A11B283FE200C91677 /* FileTransferDelegate.m in Sources */,
D378AB2A15DCDB4A0098505D /* ImagePickerViewController.m in Sources */,
6359DE841ADEB64100EA15C0 /* InAppProductsCell.m in Sources */,
22405F001601C19200B92522 /* ImageViewController.m in Sources */,
D3ED40191602172200BF332B /* HPGrowingTextView.m in Sources */,
D3ED401B1602172200BF332B /* HPTextViewInternal.m in Sources */,
D37EE162160377D7003608A6 /* DTActionSheet.m in Sources */,
D374D3FD16071762003D25FF /* ImageSharing.m in Sources */,
D35E91F4160CA10B0023116B /* UILinphoneTextField.m in Sources */,
D35E91F8160CA4FF0023116B /* UILinphoneButton.m in Sources */,
D306459E1611EC2A00BB571E /* UILoadingImageView.m in Sources */,

@ -1 +1 @@
Subproject commit 611762072933bf7bcbac9848bfead5a367a22ded
Subproject commit 778fbca580812d759979c8431b01899feafa93cd

@ -1 +1 @@
Subproject commit ad1d7c12c9b459660b34d63408b144bf5890f3b6
Subproject commit 869938486e66225ded9d46d534a24df30a84d953

@ -1 +1 @@
Subproject commit 172e97a83aa2a868fd3dbbd6e7d57ad7d55f3054
Subproject commit 40c29ef7736feb11af1e34f587788e49fa18b5c8