From 4accca8b3a4f6ae43268ba4d3e5e98a231021be7 Mon Sep 17 00:00:00 2001 From: Yann Diorcet Date: Tue, 18 Sep 2012 16:52:52 +0200 Subject: [PATCH] Add Image in chat --- Classes/ChatRoomTableViewController.h | 8 + Classes/ChatRoomTableViewController.m | 16 +- Classes/ChatRoomViewController.h | 7 +- Classes/ChatRoomViewController.m | 145 +++++++++++------ Classes/ImagePickerViewController.h | 12 +- Classes/ImagePickerViewController.m | 81 +++++++++- Classes/ImageSharing.h | 6 +- Classes/ImageSharing.m | 9 +- Classes/LinphoneManager.h | 3 + Classes/LinphoneManager.m | 10 +- Classes/LinphoneUI/UIChatRoomCell.h | 7 + Classes/LinphoneUI/UIChatRoomCell.m | 73 +++++++-- Classes/LinphoneUI/UIChatRoomCell.xib | 141 +++++++++++++--- Classes/LinphoneUI/UIContactDetailsHeader.m | 2 +- Classes/en.lproj/ChatRoomViewController.xib | 29 ++-- Classes/en.lproj/ImageViewController.xib | 3 +- Classes/fr.lproj/ChatRoomViewController.xib | 28 ++-- Classes/fr.lproj/ImageViewController.xib | 2 +- Resources/en.lproj/Localizable.strings | Bin 15680 -> 15502 bytes Resources/fr.lproj/Localizable.strings | Bin 16172 -> 16080 bytes linphone.ldb/Contents.plist | 153 ++++++++++-------- .../{15 => 16}/ChatRoomViewController.xib | 29 ++-- .../{3 => 4}/ImageViewController.xib | 3 +- .../Localizable/1/Localizable.strings | Bin 15680 -> 15502 bytes 24 files changed, 564 insertions(+), 203 deletions(-) rename linphone.ldb/Resources/Classes/ChatRoomViewController/{15 => 16}/ChatRoomViewController.xib (98%) rename linphone.ldb/Resources/Classes/ImageViewController/{3 => 4}/ImageViewController.xib (99%) diff --git a/Classes/ChatRoomTableViewController.h b/Classes/ChatRoomTableViewController.h index c29786f6b..aa91671ad 100644 --- a/Classes/ChatRoomTableViewController.h +++ b/Classes/ChatRoomTableViewController.h @@ -21,12 +21,20 @@ #import #import "ChatModel.h" +@protocol ChatRoomDelegate + +- (BOOL)chatRoomStartImageDownload:(NSURL*)url userInfo:(id)userInfo; +- (BOOL)chatRoomStartImageUpload:(UIImage*)image url:(NSURL*)url; + +@end + @interface ChatRoomTableViewController : UITableViewController { @private NSMutableArray *data; } @property (nonatomic, copy) NSString *remoteAddress; +@property (nonatomic, retain) id chatRoomDelegate; - (void)addChatEntry:(ChatModel*)chat; - (void)updateChatEntry:(ChatModel*)chat; diff --git a/Classes/ChatRoomTableViewController.m b/Classes/ChatRoomTableViewController.m index 608316be5..9dea07a78 100644 --- a/Classes/ChatRoomTableViewController.m +++ b/Classes/ChatRoomTableViewController.m @@ -21,15 +21,25 @@ #import "ChatRoomTableViewController.h" #import "UIChatRoomCell.h" #import "Utils.h" +#import "PhoneMainView.h" #import @implementation ChatRoomTableViewController @synthesize remoteAddress; +@synthesize chatRoomDelegate; +#pragma mark - Lifecycle Functions -#pragma mark - ViewController +- (void)dealloc { + [remoteAddress release]; + [chatRoomDelegate release]; + + [super dealloc]; +} + +#pragma mark - ViewController Functions - (void)viewDidLoad { [super viewDidLoad]; @@ -73,6 +83,7 @@ [self.tableView endUpdates]; [self scrollToLastUnread:true]; } + - (void)updateChatEntry:(ChatModel*)chat { if(data == nil) { [LinphoneLogger logc:LinphoneLoggerWarning format:"Cannot update entry: null data"]; @@ -86,6 +97,7 @@ [self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:index inSection:0]] withRowAnimation:FALSE];; //just reload return; } + - (void)scrollToLastUnread:(BOOL)animated { if(data == nil) { [LinphoneLogger logc:LinphoneLoggerWarning format:"Cannot add entry: null data"]; @@ -145,7 +157,7 @@ } [cell setChat:[data objectAtIndex:[indexPath row]]]; - + [cell setChatRoomDelegate:chatRoomDelegate]; return cell; } diff --git a/Classes/ChatRoomViewController.h b/Classes/ChatRoomViewController.h index 3abc96ce3..aeb721217 100644 --- a/Classes/ChatRoomViewController.h +++ b/Classes/ChatRoomViewController.h @@ -18,7 +18,6 @@ */ #import -#import #import "UIToggleButton.h" #import "UICompositeViewController.h" @@ -30,14 +29,11 @@ #include "linphonecore.h" -@interface ChatRoomViewController : UIViewController { - @private +@interface ChatRoomViewController : UIViewController { LinphoneChatRoom *chatRoom; ImageSharing *imageSharing; - ALAssetsLibrary *photoLibrary; } - @property (nonatomic, retain) IBOutlet ChatRoomTableViewController* tableController; @property (nonatomic, retain) IBOutlet UIToggleButton *editButton; @property (nonatomic, retain) IBOutlet HPGrowingTextView* messageField; @@ -50,6 +46,7 @@ @property (nonatomic, retain) IBOutlet UIView *messageView; @property (nonatomic, retain) IBOutlet UIImageView *messageBackgroundImage; @property (nonatomic, retain) IBOutlet UIImageView *footerBackgroundImage; +@property (nonatomic, retain) IBOutlet UIImageView *transferBackgroundImage; @property (nonatomic, retain) IBOutlet UITapGestureRecognizer *listTapGestureRecognizer; @property (nonatomic, copy) NSString *remoteAddress; diff --git a/Classes/ChatRoomViewController.m b/Classes/ChatRoomViewController.m index a8d863cee..4b7702131 100644 --- a/Classes/ChatRoomViewController.m +++ b/Classes/ChatRoomViewController.m @@ -22,7 +22,7 @@ #import "DTActionSheet.h" #import - +#import @implementation ChatRoomViewController @@ -39,6 +39,7 @@ @synthesize messageView; @synthesize messageBackgroundImage; @synthesize footerBackgroundImage; +@synthesize transferBackgroundImage; @synthesize listTapGestureRecognizer; @synthesize pictureButton; @synthesize imageTransferProgressBar; @@ -53,7 +54,6 @@ if (self != nil) { self->chatRoom = NULL; self->imageSharing = NULL; - self->photoLibrary = [[ALAssetsLibrary alloc] init]; } return self; } @@ -72,6 +72,7 @@ [messageView release]; [messageBackgroundImage release]; [footerBackgroundImage release]; + [transferBackgroundImage release]; [listTapGestureRecognizer release]; @@ -80,8 +81,6 @@ [imageTransferProgressBar release]; [cancelTransferButton release]; - [photoLibrary release]; - [super dealloc]; } @@ -110,6 +109,7 @@ static UICompositeViewDescription *compositeDescription = nil; - (void)viewDidLoad { [super viewDidLoad]; + [tableController setChatRoomDelegate:self]; // Set selected+over background: IB lack ! [editButton setImage:[UIImage imageNamed:@"chat_ok_over.png"] @@ -122,6 +122,7 @@ static UICompositeViewDescription *compositeDescription = nil; messageField.contentInset = UIEdgeInsetsZero; messageField.backgroundColor = [UIColor clearColor]; [sendButton setEnabled:FALSE]; + [listTapGestureRecognizer setEnabled:FALSE]; } @@ -150,9 +151,11 @@ static UICompositeViewDescription *compositeDescription = nil; [messageBackgroundImage setImage:[TUNinePatchCache imageOfSize:[messageBackgroundImage bounds].size forNinePatchNamed:@"chat_field"]]; - [footerBackgroundImage setImage:[TUNinePatchCache imageOfSize:[footerBackgroundImage bounds].size forNinePatchNamed:@"chat_background"]]; + [transferBackgroundImage setImage:[TUNinePatchCache imageOfSize:[transferBackgroundImage bounds].size + forNinePatchNamed:@"chat_background"]]; + BOOL fileSharingEnabled = [[LinphoneManager instance] lpConfigStringForKey:@"file_upload_url_preference"] != NULL && [[[LinphoneManager instance] lpConfigStringForKey:@"file_upload_url_preference"] length]>0; @@ -273,7 +276,7 @@ static void message_status(LinphoneChatMessage* msg,LinphoneChatMessageState sta [thiz.tableController updateChatEntry:chat]; } -- (BOOL)sendMessage:(NSString *)message withExterlBodyUrl:(NSString*) url{ +- (BOOL)sendMessage:(NSString *)message withExterlBodyUrl:(NSURL*)externalUrl withInternalUrl:(NSURL*)internalUrl { if(![LinphoneManager isLcReady]) { [LinphoneLogger logc:LinphoneLoggerWarning format:"Cannot send message: Linphone core not ready"]; return FALSE; @@ -290,7 +293,11 @@ static void message_status(LinphoneChatMessage* msg,LinphoneChatMessageState sta ChatModel *chat = [[ChatModel alloc] init]; [chat setRemoteContact:remoteAddress]; [chat setLocalContact:@""]; - [chat setMessage:message]; + if(internalUrl == nil) { + [chat setMessage:message]; + } else { + [chat setMessage:[internalUrl absoluteString]]; + } [chat setDirection:[NSNumber numberWithInt:0]]; [chat setTime:[NSDate date]]; [chat setRead:[NSNumber numberWithInt:1]]; @@ -301,9 +308,9 @@ static void message_status(LinphoneChatMessage* msg,LinphoneChatMessageState sta LinphoneChatMessage* msg = linphone_chat_room_create_message(chatRoom, [message UTF8String]); linphone_chat_message_set_user_data(msg, chat); - if (url) { - linphone_chat_message_set_external_body_url(msg, [url UTF8String]); - } + if(externalUrl) { + linphone_chat_message_set_external_body_url(msg, [[externalUrl absoluteString] UTF8String]); + } linphone_chat_room_send_message2(chatRoom, msg, message_status, self); return TRUE; } @@ -329,19 +336,6 @@ static void message_status(LinphoneChatMessage* msg,LinphoneChatMessageState sta } ms_free(fromStr); } - - if ([[notif userInfo] objectForKey:@"external_body_url"]) { - NSString *pendingFileUrl = [[[notif userInfo] objectForKey:@"external_body_url"] retain]; - - DTActionSheet *sheet = [[[DTActionSheet alloc] initWithTitle:NSLocalizedString(@"Incoming file stored to your photo library",nil)] autorelease]; - [sheet addButtonWithTitle:NSLocalizedString(@"Accept",nil) block:^(){ - imageSharing = [ImageSharing imageSharingDownload:[NSURL URLWithString:pendingFileUrl] delegate:self]; - [footerView setHidden:TRUE]; - [transferView setHidden:FALSE]; - }]; - [sheet addCancelButtonWithTitle:NSLocalizedString(@"Ignore",nil)]; - [sheet showInView:[PhoneMainView instance].view]; - } } else { [LinphoneLogger logc:LinphoneLoggerWarning format:"Invalid textReceivedEvent"]; } @@ -354,8 +348,13 @@ static void message_status(LinphoneChatMessage* msg,LinphoneChatMessageState sta if(editButton.selected) { [tableController setEditing:FALSE animated:TRUE]; [editButton setOff]; - [listTapGestureRecognizer setEnabled:TRUE]; } + [listTapGestureRecognizer setEnabled:TRUE]; + return TRUE; +} + +- (BOOL)growingTextViewShouldEndEditing:(HPGrowingTextView *)growingTextView { + [listTapGestureRecognizer setEnabled:FALSE]; return TRUE; } @@ -391,13 +390,12 @@ static void message_status(LinphoneChatMessage* msg,LinphoneChatMessageState sta } - (IBAction)onEditClick:(id)event { - [listTapGestureRecognizer setEnabled:[tableController isEditing]]; [tableController setEditing:![tableController isEditing] animated:TRUE]; [messageField resignFirstResponder]; } - (IBAction)onSendClick:(id)event { - if([self sendMessage:[messageField text] withExterlBodyUrl:nil]) { + if([self sendMessage:[messageField text] withExterlBodyUrl:nil withInternalUrl:nil]) { [messageField setText:@""]; [self onMessageChange:nil]; } @@ -428,7 +426,7 @@ static void message_status(LinphoneChatMessage* msg,LinphoneChatMessageState sta // Displays a control that allows the user to choose picture or // movie capture, if both are available: - controller.mediaTypes = [UIImagePickerController availableMediaTypesForSourceType:type]; + controller.mediaTypes = [NSArray arrayWithObject:(NSString *)kUTTypeImage]; // Hides the controls for moving & scaling pictures, or for // trimming movies. To instead show the controls, use YES. @@ -445,6 +443,30 @@ static void message_status(LinphoneChatMessage* msg,LinphoneChatMessageState sta } +#pragma mark ChatRoomDelegate + +- (BOOL)chatRoomStartImageDownload:(NSURL*)url userInfo:(id)userInfo { + if(imageSharing == nil) { + imageSharing = [ImageSharing imageSharingDownload:url delegate:self userInfo:userInfo]; + [footerView setHidden:TRUE]; + [transferView setHidden:FALSE]; + return TRUE; + } + return FALSE; +} + +- (BOOL)chatRoomStartImageUpload:(UIImage*)image url:(NSURL*)url{ + if(imageSharing == nil) { + NSString *urlString = [[LinphoneManager instance] lpConfigStringForKey:@"file_upload_url_preference"]; + imageSharing = [ImageSharing imageSharingUpload:[NSURL URLWithString:urlString] image:image delegate:self userInfo:url]; + [footerView setHidden:TRUE]; + [transferView setHidden:FALSE]; + return TRUE; + } + return FALSE; +} + + #pragma mark ImageSharingDelegate - (void)imageSharingProgress:(ImageSharing*)aimageSharing progress:(float)progress { @@ -486,7 +508,9 @@ static void message_status(LinphoneChatMessage* msg,LinphoneChatMessageState sta } - (void)imageSharingUploadDone:(ImageSharing*)aimageSharing url:(NSURL*)url{ - [self sendMessage:NSLocalizedString(@"Image sent", nil) withExterlBodyUrl:[url absoluteString]]; + NSURL *imageURL = [aimageSharing userInfo]; + + [self sendMessage:nil withExterlBodyUrl:url withInternalUrl:imageURL]; [footerView setHidden:FALSE]; [transferView setHidden:TRUE]; @@ -497,30 +521,59 @@ static void message_status(LinphoneChatMessage* msg,LinphoneChatMessageState sta [footerView setHidden:FALSE]; [transferView setHidden:TRUE]; - [photoLibrary writeImageToSavedPhotosAlbum:(CGImageRef)image - metadata:nil - completionBlock:^(NSURL *assetURL, NSError *error){ - if (error) { - [LinphoneLogger log:LinphoneLoggerError format:@"Cannot save image data downloaded [%@]",[error localizedDescription]]; - } else { - [LinphoneLogger log:LinphoneLoggerLog format:@"Image saved to [%@]",[assetURL absoluteString]]; - } - ImageViewController *controller = DYNAMIC_CAST([[PhoneMainView instance] changeCurrentView:[ImageViewController compositeViewDescription] push:TRUE], ImageViewController); - if(controller != nil) { - [controller setImage:image]; - } - }]; + ChatModel *chat = (ChatModel *)[imageSharing userInfo]; + [[LinphoneManager instance].photoLibrary writeImageToSavedPhotosAlbum:image.CGImage + metadata:nil + completionBlock:^(NSURL *assetURL, NSError *error){ + if (error) { + [LinphoneLogger log:LinphoneLoggerError format:@"Cannot save image data downloaded [%@]", [error localizedDescription]]; + + UIAlertView* errorAlert = [UIAlertView alloc]; + [errorAlert 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]; + [errorAlert release]; + return; + } + [LinphoneLogger log:LinphoneLoggerLog format:@"Image saved to [%@]", [assetURL absoluteString]]; + [chat setMessage:[assetURL absoluteString]]; + [chat update]; + [tableController updateChatEntry:chat]; + }]; imageSharing = NULL; } #pragma mark ImagePickerDelegate -- (void)imagePickerDelegateImage:(UIImage*)image { - NSString *urlString = [[LinphoneManager instance] lpConfigStringForKey:@"file_upload_url_preference"]; - imageSharing = [ImageSharing imageSharingUpload:[NSURL URLWithString:urlString] image:image delegate:self]; - [footerView setHidden:TRUE]; - [transferView setHidden:FALSE]; +- (void)imagePickerDelegateImage:(UIImage*)image info:(NSDictionary *)info { + NSURL *url = [info valueForKey:UIImagePickerControllerReferenceURL]; + if(url != nil) { + [self chatRoomStartImageUpload:image url:url]; + } else { + [[LinphoneManager instance].photoLibrary writeImageToSavedPhotosAlbum:image.CGImage + metadata:nil + completionBlock:^(NSURL *assetURL, NSError *error){ + if (error) { + [LinphoneLogger log:LinphoneLoggerError format:@"Cannot save image data downloaded [%@]", [error localizedDescription]]; + + UIAlertView* errorAlert = [UIAlertView alloc]; + [errorAlert 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]; + [errorAlert release]; + return; + } + [LinphoneLogger log:LinphoneLoggerLog format:@"Image saved to [%@]", [assetURL absoluteString]]; + [self chatRoomStartImageUpload:image url:assetURL]; + }]; + } } diff --git a/Classes/ImagePickerViewController.h b/Classes/ImagePickerViewController.h index 4acdebf21..fd15af4bc 100644 --- a/Classes/ImagePickerViewController.h +++ b/Classes/ImagePickerViewController.h @@ -21,14 +21,20 @@ @protocol ImagePickerDelegate -- (void)imagePickerDelegateImage:(UIImage*)image; +- (void)imagePickerDelegateImage:(UIImage*)image info:(NSDictionary *)info; @end -@interface ImagePickerViewController : UIImagePickerController { +@interface ImagePickerViewController : UIViewController { + @private + UIImagePickerController *pickerController; + UIPopoverController *popoverController; } -@property (nonatomic, retain) id imagePickerDelegate; +@property(nonatomic, retain) id imagePickerDelegate; +@property(nonatomic) UIImagePickerControllerSourceType sourceType; +@property(nonatomic,copy) NSArray *mediaTypes; +@property(nonatomic) BOOL allowsEditing; + (void)promptSelectSource:(void (^)(UIImagePickerControllerSourceType))block; diff --git a/Classes/ImagePickerViewController.m b/Classes/ImagePickerViewController.m index 46be9914b..c31e48906 100644 --- a/Classes/ImagePickerViewController.m +++ b/Classes/ImagePickerViewController.m @@ -24,6 +24,30 @@ @implementation ImagePickerViewController @synthesize imagePickerDelegate; +@synthesize sourceType; +@synthesize mediaTypes; +@synthesize allowsEditing; + + +#pragma mark - Lifecycle Functions + +- (id)init { + self = [super init]; + if (self != nil) { + pickerController = [[UIImagePickerController alloc] init]; + if([LinphoneManager runningOnIpad]) { + popoverController = [[UIPopoverController alloc] initWithContentViewController:pickerController]; + } + } + return self; +} + +- (void)dealloc { + [pickerController release]; + [popoverController release]; + + [super dealloc]; +} #pragma mark - UICompositeViewDelegate Functions @@ -50,7 +74,55 @@ static UICompositeViewDescription *compositeDescription = nil; - (void)viewDidLoad { [super viewDidLoad]; - [self setDelegate:self]; + + [self.view setAutoresizingMask: UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight]; + if(popoverController == nil) { + [pickerController.view setFrame:[self.view bounds]]; + [self.view addSubview:[pickerController view]]; + } else { + [popoverController setDelegate:self]; + } + [pickerController setDelegate:self]; +} + +- (void)viewDidAppear:(BOOL)animated { + [super viewDidAppear:animated]; + if(popoverController != nil) { + [popoverController presentPopoverFromRect:CGRectZero inView:self.view permittedArrowDirections:UIPopoverArrowDirectionAny animated:FALSE]; + } +} + +- (void)viewWillDisappear:(BOOL)animated { + [super viewWillDisappear:animated]; + if(popoverController != nil) { + [popoverController dismissPopoverAnimated: NO]; + } +} + +#pragma mark - Property Functions + +- (BOOL)allowsEditing { + return pickerController.allowsEditing; +} + +- (void)setAllowsEditing:(BOOL)aallowsEditing { + pickerController.allowsEditing = aallowsEditing; +} + +- (UIImagePickerControllerSourceType)sourceType { + return pickerController.sourceType; +} + +- (void)setSourceType:(UIImagePickerControllerSourceType)asourceType { + pickerController.sourceType = asourceType; +} + +- (NSArray *)mediaTypes { + return pickerController.mediaTypes; +} + +- (void)setMediaTypes:(NSArray *)amediaTypes { + pickerController.mediaTypes = amediaTypes; } @@ -88,7 +160,7 @@ static UICompositeViewDescription *compositeDescription = nil; image = [info objectForKey:UIImagePickerControllerOriginalImage]; } if(image != nil && imagePickerDelegate != nil) { - [imagePickerDelegate imagePickerDelegateImage:image]; + [imagePickerDelegate imagePickerDelegateImage:image info:info]; } [self dismiss]; } @@ -97,4 +169,9 @@ static UICompositeViewDescription *compositeDescription = nil; [self dismiss]; } +- (BOOL)popoverControllerShouldDismissPopover:(UIPopoverController *)apopoverController { + [self dismiss]; + return TRUE; +} + @end diff --git a/Classes/ImageSharing.h b/Classes/ImageSharing.h index 13cf22fc0..5949cc8a6 100644 --- a/Classes/ImageSharing.h +++ b/Classes/ImageSharing.h @@ -38,11 +38,13 @@ int statusCode; } -+ (id)imageSharingUpload:(NSURL*)url image:(UIImage*)image delegate:(id)delegate; -+ (id)imageSharingDownload:(NSURL*)url delegate:(id)delegate; ++ (id)imageSharingUpload:(NSURL*)url image:(UIImage*)image delegate:(id)delegate userInfo:(id)userInfo; ++ (id)imageSharingDownload:(NSURL*)url delegate:(id)delegate userInfo:(id)userInfo; - (void)cancel; +@property (nonatomic, retain) id userInfo; + @property (nonatomic, readonly) BOOL upload; @property (nonatomic, readonly) NSMutableData* data; @property (nonatomic, readonly) NSURLConnection* connection; diff --git a/Classes/ImageSharing.m b/Classes/ImageSharing.m index b0aee4b06..403addd20 100644 --- a/Classes/ImageSharing.m +++ b/Classes/ImageSharing.m @@ -26,13 +26,14 @@ @synthesize connection; @synthesize data; @synthesize upload; - +@synthesize userInfo; #pragma mark - Lifecycle Functions -+ (id)imageSharingUpload:(NSURL*)url image:(UIImage*)image delegate:(id)delegate { ++ (id)imageSharingUpload:(NSURL*)url image:(UIImage*)image delegate:(id)delegate userInfo:(id)auserInfo{ ImageSharing *imgs = [[ImageSharing alloc] init]; if(imgs != nil) { + imgs.userInfo = auserInfo; imgs->upload = TRUE; imgs->delegate = [delegate retain]; imgs->data = [[NSMutableData alloc] init]; @@ -44,9 +45,10 @@ return imgs; } -+ (id)imageSharingDownload:(NSURL*)url delegate:(id)delegate { ++ (id)imageSharingDownload:(NSURL*)url delegate:(id)delegate userInfo:(id)auserInfo{ ImageSharing *imgs = [[ImageSharing alloc] init]; if(imgs != nil) { + imgs.userInfo = auserInfo; imgs->upload = FALSE; imgs->delegate = [delegate retain]; imgs->data = [[NSMutableData alloc] init]; @@ -62,6 +64,7 @@ [connection release]; [data release]; [delegate release]; + [userInfo release]; [super dealloc]; } diff --git a/Classes/LinphoneManager.h b/Classes/LinphoneManager.h index 799d4bba0..7ba9318ce 100644 --- a/Classes/LinphoneManager.h +++ b/Classes/LinphoneManager.h @@ -21,6 +21,8 @@ #import #import #import +#import + #import #import "IASKSettingsReader.h" @@ -140,6 +142,7 @@ typedef struct _LinphoneManagerSounds { @property (readonly) LinphoneManagerSounds sounds; @property (readonly) NSMutableArray *logs; @property (nonatomic, assign) BOOL speakerEnabled; +@property (readonly) ALAssetsLibrary *photoLibrary; @end diff --git a/Classes/LinphoneManager.m b/Classes/LinphoneManager.m index db073f120..f376f9956 100644 --- a/Classes/LinphoneManager.m +++ b/Classes/LinphoneManager.m @@ -87,6 +87,7 @@ extern void libmsbcg729_init(); @synthesize sounds; @synthesize logs; @synthesize speakerEnabled; +@synthesize photoLibrary; struct codec_name_pref_table{ const char *name; @@ -218,6 +219,7 @@ struct codec_name_pref_table codec_pref_table[]={ [self openDatabase]; [self copyDefaultSettings]; lastRemoteNotificationTime=0; + photoLibrary = [[ALAssetsLibrary alloc] init]; } return self; } @@ -239,6 +241,7 @@ struct codec_name_pref_table codec_pref_table[]={ [LinphoneLogger logc:LinphoneLoggerError format:"cannot un register route change handler [%ld]", lStatus]; } + [photoLibrary release]; [super dealloc]; } @@ -433,7 +436,7 @@ static void linphone_iphone_registration_state(LinphoneCore *lc, LinphoneProxyCo [chat setLocalContact:@""]; [chat setRemoteContact:[NSString stringWithUTF8String:fromStr]]; if (linphone_chat_message_get_external_body_url(msg)) { - [chat setMessage:NSLocalizedString(@"Incoming file",nil)]; + [chat setMessage:[NSString stringWithUTF8String:linphone_chat_message_get_external_body_url(msg)]]; } else { [chat setMessage:[NSString stringWithUTF8String:linphone_chat_message_get_text(msg)]]; } @@ -443,17 +446,12 @@ static void linphone_iphone_registration_state(LinphoneCore *lc, LinphoneProxyCo [chat create]; ms_free(fromStr); - NSString* ext_body_url=nil; - if (linphone_chat_message_get_external_body_url(msg)) { - ext_body_url=[NSString stringWithUTF8String:linphone_chat_message_get_external_body_url(msg)]; - } // Post event NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys: [NSValue valueWithPointer:room], @"room", [NSValue valueWithPointer:linphone_chat_message_get_from(msg)], @"from", chat.message, @"message", chat, @"chat", - ext_body_url,@"external_body_url", nil]; [[NSNotificationCenter defaultCenter] postNotificationName:kLinphoneTextReceived object:self userInfo:dict]; [chat release]; diff --git a/Classes/LinphoneUI/UIChatRoomCell.h b/Classes/LinphoneUI/UIChatRoomCell.h index 3350c8718..94a6303b1 100644 --- a/Classes/LinphoneUI/UIChatRoomCell.h +++ b/Classes/LinphoneUI/UIChatRoomCell.h @@ -20,6 +20,8 @@ #import #import "ChatModel.h" +#import "ChatRoomTableViewController.h" + @interface UIChatRoomCell : UITableViewCell { } @@ -33,10 +35,15 @@ @property (nonatomic, retain) IBOutlet UIButton *deleteButton; @property (nonatomic, retain) IBOutlet UILabel *dateLabel; @property (nonatomic, retain) IBOutlet UIImageView* statusImage; +@property (nonatomic, retain) IBOutlet UIButton* downloadButton; + - (id)initWithIdentifier:(NSString*)identifier; + (CGFloat)height:(ChatModel*)chat width:(int)width; +@property (nonatomic, retain) id chatRoomDelegate; - (IBAction)onDeleteClick:(id)event; +- (IBAction)onDownloadClick:(id)event; +- (IBAction)onImageClick:(id)event; @end diff --git a/Classes/LinphoneUI/UIChatRoomCell.m b/Classes/LinphoneUI/UIChatRoomCell.m index 0ccfcce18..22862ed2e 100644 --- a/Classes/LinphoneUI/UIChatRoomCell.m +++ b/Classes/LinphoneUI/UIChatRoomCell.m @@ -19,7 +19,11 @@ #import "UIChatRoomCell.h" #import "Utils.h" +#import "LinphoneManager.h" +#import "PhoneMainView.h" +#import +#import #import #include "linphonecore.h" @@ -34,6 +38,8 @@ @synthesize dateLabel; @synthesize chat; @synthesize statusImage; +@synthesize downloadButton; +@synthesize chatRoomDelegate; static const CGFloat CELL_MIN_HEIGHT = 40.0f; static const CGFloat CELL_MIN_WIDTH = 150.0f; @@ -58,6 +64,7 @@ static UIFont *CELL_FONT = nil; } - (void)dealloc { + [chatRoomDelegate release]; [backgroundImage release]; [innerView release]; [bubbleView release]; @@ -67,6 +74,7 @@ static UIFont *CELL_FONT = nil; [dateLabel release]; [statusImage release]; [chat release]; + [downloadButton release]; [super dealloc]; } @@ -92,15 +100,39 @@ static UIFont *CELL_FONT = nil; [LinphoneLogger logc:LinphoneLoggerWarning format:"Cannot update chat room cell: null chat"]; return; } - if(true/*Change when image will be supported */) { + + if([UIChatRoomCell isExternalImage:[chat message]]) { + [messageLabel setHidden:TRUE]; + + [messageImageView setImage:nil]; + [messageImageView setHidden:TRUE]; + + [downloadButton setHidden:FALSE]; + } else if([UIChatRoomCell isInternalImage:[chat message]]) { + [messageLabel setHidden:TRUE]; + + [[LinphoneManager instance].photoLibrary assetForURL:[NSURL URLWithString:[chat message]] resultBlock:^(ALAsset *asset) { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, (unsigned long)NULL), ^(void) { + ALAssetRepresentation* representation = [asset defaultRepresentation]; + UIImage *image = [UIImage imageWithCGImage:[representation fullResolutionImage]]; + dispatch_async(dispatch_get_main_queue(), ^{ + [messageImageView setImage:image]; + }); + }); + } failureBlock:^(NSError *error) { + [LinphoneLogger log:LinphoneLoggerError format:@"Can't read image"]; + }]; + + [messageImageView setHidden:FALSE]; + [downloadButton setHidden:TRUE]; + } else { [messageLabel setHidden:FALSE]; [messageLabel setText:[chat message]]; + [messageImageView setImage:nil]; [messageImageView setHidden:TRUE]; - } else { - [messageLabel setHidden:TRUE]; - [messageImageView setHidden:FALSE]; + [downloadButton setHidden:TRUE]; } // Date @@ -112,16 +144,16 @@ static UIFont *CELL_FONT = nil; [dateLabel setText:[dateFormatter stringFromDate:[chat time]]]; [dateFormatter release]; if ([chat.state intValue] == LinphoneChatMessageStateInProgress) { - [statusImage setImage:[UIImage imageNamed:@"chat_message_inprogress.png"] ]; - statusImage.hidden=FALSE; + [statusImage setImage:[UIImage imageNamed:@"chat_message_inprogress.png"]]; + statusImage.hidden = FALSE; } else if ([chat.state intValue] == LinphoneChatMessageStateDelivered) { - [statusImage setImage:[UIImage imageNamed:@"chat_message_delivered.png"] ]; - statusImage.hidden=FALSE; + [statusImage setImage:[UIImage imageNamed:@"chat_message_delivered.png"]]; + statusImage.hidden = FALSE; } else if ([chat.state intValue] == LinphoneChatMessageStateNotDelivered) { [statusImage setImage:[UIImage imageNamed:@"chat_message_not_delivered.png"]]; - statusImage.hidden=FALSE; + statusImage.hidden = FALSE; } else { - statusImage.hidden=TRUE; + statusImage.hidden = TRUE; } } @@ -144,9 +176,17 @@ static UIFont *CELL_FONT = nil; } } ++ (BOOL)isExternalImage:(NSString *)message { + return [message hasPrefix:@"http:"] || [message hasPrefix:@"https:"]; +} + ++ (BOOL)isInternalImage:(NSString *)message { + return [message hasPrefix:@"assets-library:"]; +} + + (CGSize)viewSize:(ChatModel*)chat width:(int)width { CGSize messageSize; - if(true/*Change when image will be supported */) { + if(!([UIChatRoomCell isExternalImage:[chat message]] || [UIChatRoomCell isInternalImage:[chat message]])) { if(CELL_FONT == nil) { CELL_FONT = [UIFont systemFontOfSize:CELL_FONT_SIZE]; } @@ -218,4 +258,15 @@ static UIFont *CELL_FONT = nil; } } +- (IBAction)onDownloadClick:(id)event { + [chatRoomDelegate chatRoomStartImageDownload:[NSURL URLWithString:chat.message] userInfo:chat]; +} + +- (IBAction)onImageClick:(id)event { + ImageViewController *controller = DYNAMIC_CAST([[PhoneMainView instance] changeCurrentView:[ImageViewController compositeViewDescription] push:TRUE], ImageViewController); + if(controller != nil) { + [controller setImage:messageImageView.image]; + } +} + @end diff --git a/Classes/LinphoneUI/UIChatRoomCell.xib b/Classes/LinphoneUI/UIChatRoomCell.xib index 7710f22af..1faf62b94 100644 --- a/Classes/LinphoneUI/UIChatRoomCell.xib +++ b/Classes/LinphoneUI/UIChatRoomCell.xib @@ -15,6 +15,7 @@ IBUIButton IBUIImageView IBUILabel + IBUITapGestureRecognizer IBUIView @@ -69,7 +70,7 @@ _NS:9 1 - NO + IBCocoaTouchFramework @@ -78,7 +79,7 @@ {294, 104} - + _NS:9 3 @@ -109,6 +110,41 @@ NO + + + 301 + {{81, 33}, {132, 37}} + + + + _NS:9 + NO + IBCocoaTouchFramework + 0 + 0 + Download image + + 3 + MQA + + + 1 + MC4xOTYwNzg0MzQ2IDAuMzA5ODAzOTMyOSAwLjUyMTU2ODY1NgA + + + 3 + MC41AA + + + 2 + 15 + + + Helvetica-Bold + 15 + 16 + + {294, 104} @@ -117,7 +153,6 @@ _NS:9 YES - NO IBCocoaTouchFramework @@ -135,10 +170,7 @@ NO IBCocoaTouchFramework 09/09/2009 at 09:09 - - 3 - MC41AA - + 0 8 @@ -194,15 +226,8 @@ NSImage list_delete_default.png - - 2 - 15 - - - Helvetica-Bold - 15 - 16 - + + {{13, 13}, {294, 114}} @@ -245,6 +270,7 @@ IBCocoaTouchFramework + @@ -328,6 +354,14 @@ 31 + + + downloadButton + + + + 34 + onDeleteClick: @@ -337,12 +371,39 @@ 32 + + + gestureRecognizers + + + NSArray + YES + + 41 + + + + onDownloadClick: + + + 7 + + 39 + + + + onImageClick: + + + + 42 + 0 - + @@ -421,6 +482,7 @@ + messageView @@ -428,6 +490,7 @@ 28 + messageImageView @@ -437,6 +500,17 @@ messageLabel + + 33 + + + downloadButton + + + 40 + + + @@ -455,6 +529,8 @@ com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin com.apple.InterfaceBuilder.IBCocoaTouchPlugin @@ -462,29 +538,38 @@ - 32 + 42 UIChatRoomCell UITableViewCell - - onDeleteClick: - id - - - onDeleteClick: - + + id + id + id + + + onDeleteClick: id - + + onDownloadClick: + id + + + onImageClick: + id + + UIImageView UIView UILabel UIButton + UIButton UIView UIImageView UILabel @@ -507,6 +592,10 @@ deleteButton UIButton + + downloadButton + UIButton + innerView UIView diff --git a/Classes/LinphoneUI/UIContactDetailsHeader.m b/Classes/LinphoneUI/UIContactDetailsHeader.m index 3a2544958..96a1a95e9 100644 --- a/Classes/LinphoneUI/UIContactDetailsHeader.m +++ b/Classes/LinphoneUI/UIContactDetailsHeader.m @@ -264,7 +264,7 @@ #pragma mark - ContactDetailsImagePickerDelegate Functions -- (void)imagePickerDelegateImage:(UIImage*)image { +- (void)imagePickerDelegateImage:(UIImage*)image info:(NSDictionary *)info{ NSError* error = NULL; if(!ABPersonRemoveImageData(contact, (CFErrorRef*)error)) { [LinphoneLogger log:LinphoneLoggerLog format:@"Can't add entry: %@", [error localizedDescription]]; diff --git a/Classes/en.lproj/ChatRoomViewController.xib b/Classes/en.lproj/ChatRoomViewController.xib index dfb378fa5..1972001d5 100644 --- a/Classes/en.lproj/ChatRoomViewController.xib +++ b/Classes/en.lproj/ChatRoomViewController.xib @@ -156,7 +156,7 @@ - -2147483356 + -2147483382 @@ -166,10 +166,7 @@ _NS:9 - - 1 - MSAwLjI4MzE1ODM3MjYgMC4wNTY3ODY4OTE2MQA - + NO IBCocoaTouchFramework @@ -179,7 +176,7 @@ - 292 + 297 {{250, 0}, {70, 59}} @@ -214,7 +211,7 @@ - 292 + 298 {{20, 25}, {213, 9}} @@ -274,6 +271,7 @@ {{250, 0}, {70, 59}} + _NS:9 NO @@ -639,6 +637,14 @@ 86 + + + transferBackgroundImage + + + + 88 + dataSource @@ -930,7 +936,7 @@ 83 - footerBackgroundImage + transfertBackgroundImage @@ -976,7 +982,7 @@ - 87 + 88 @@ -1047,6 +1053,7 @@ UIButton UIButton ChatRoomTableViewController + UIImageView UIView @@ -1114,6 +1121,10 @@ tableController ChatRoomTableViewController + + transferBackgroundImage + UIImageView + transferView UIView diff --git a/Classes/en.lproj/ImageViewController.xib b/Classes/en.lproj/ImageViewController.xib index 0e9bb3507..7e003d3c6 100644 --- a/Classes/en.lproj/ImageViewController.xib +++ b/Classes/en.lproj/ImageViewController.xib @@ -42,7 +42,6 @@ {{0, 44}, {320, 416}} - _NS:9 3 @@ -58,7 +57,7 @@ - 292 + 290 {320, 44} diff --git a/Classes/fr.lproj/ChatRoomViewController.xib b/Classes/fr.lproj/ChatRoomViewController.xib index effff9788..a07b5d6da 100644 --- a/Classes/fr.lproj/ChatRoomViewController.xib +++ b/Classes/fr.lproj/ChatRoomViewController.xib @@ -151,7 +151,7 @@ - -2147483356 + -2147483382 @@ -160,10 +160,7 @@ _NS:9 - - 1 - MSAwLjI4MzE1ODM3MjYgMC4wNTY3ODY4OTE2MQA - + NO IBCocoaTouchFramework @@ -173,7 +170,7 @@ - 292 + 297 {{250, 0}, {70, 59}} @@ -207,7 +204,7 @@ - 292 + 298 {{20, 25}, {213, 9}} @@ -615,6 +612,14 @@ 86 + + + transferBackgroundImage + + + + 88 + dataSource @@ -906,7 +911,7 @@ 83 - footerBackgroundImage + transfertBackgroundImage @@ -952,7 +957,7 @@ - 87 + 88 @@ -1023,6 +1028,7 @@ UIButton UIButton ChatRoomTableViewController + UIImageView UIView @@ -1090,6 +1096,10 @@ tableController ChatRoomTableViewController + + transferBackgroundImage + UIImageView + transferView UIView diff --git a/Classes/fr.lproj/ImageViewController.xib b/Classes/fr.lproj/ImageViewController.xib index 2cd6fe824..071b3cda5 100644 --- a/Classes/fr.lproj/ImageViewController.xib +++ b/Classes/fr.lproj/ImageViewController.xib @@ -56,7 +56,7 @@ - 292 + 290 {320, 44} diff --git a/Resources/en.lproj/Localizable.strings b/Resources/en.lproj/Localizable.strings index 77e98c0aebfadcfe5429948995256911fe4b7664..d1ab447f8b2aa74569ea31a4e87ceb1964227673 100644 GIT binary patch delta 112 zcmX?5)mORUDgWfv+&t3d3`Go?3?&Sy3<^Lxmm!fM9n3Ca$e(;hPF>R(D3S++`9L)a h4ER;cPxh1#n_Mfvv-v&$D!$E&o9v delta 124 zcmeCHJW#daDgWeLL9Wer1y=D*j+Wz_{9lekR)L|Ip@boyp@<=sA%#H!%vNBi1d5b0 s6iwz4P@lY7Mu-TNF#@HVo8?2;CkqNFF$PRd5Gb51ugSOhvD#`;0OrRc$p8QV diff --git a/Resources/fr.lproj/Localizable.strings b/Resources/fr.lproj/Localizable.strings index 4afcd5a69212f0f77d901698fe28ad2f97fa0ba2..065a3917454298f960d0283f0304588d3bee423a 100644 GIT binary patch delta 126 zcmZ2eccFH}CV|NwJUsH{3`Go?3?&Sy3<^Lxmm!fM9n3Ca$OnoPOt#lhp8SSaf>C|) zMuJp+iSRAQOr=3u^2a0|1s-9D@J= diff --git a/linphone.ldb/Contents.plist b/linphone.ldb/Contents.plist index fe6bc31b8..6a88a91eb 100644 --- a/linphone.ldb/Contents.plist +++ b/linphone.ldb/Contents.plist @@ -212,17 +212,17 @@ backup - 15 + 16 class BLWrapperHandle name - Classes/ChatRoomViewController/15/ChatRoomViewController.xib + Classes/ChatRoomViewController/16/ChatRoomViewController.xib change date - 2012-09-14T14:45:49Z + 2012-09-18T13:03:28Z changed values class @@ -232,7 +232,7 @@ flags 0 hash - 1f8683826a86cb2ad7e55cc5c9dcafaf + fb9acb898a88e8d219dfd663e9b8eb19 name ChatRoomViewController.xib @@ -568,9 +568,9 @@ versions en - 15 + 16 fr - 15 + 16 @@ -4281,17 +4281,17 @@ backup - 3 + 4 class BLWrapperHandle name - Classes/ImageViewController/3/ImageViewController.xib + Classes/ImageViewController/4/ImageViewController.xib change date - 2012-09-14T13:00:17Z + 2012-09-18T12:55:28Z changed values class @@ -4301,7 +4301,7 @@ flags 0 hash - 724a8c5069c0fbde9b1e56ef37a42cba + cf8736251f03474edd9a8c46b942ae63 name ImageViewController.xib @@ -4340,9 +4340,9 @@ versions en - 3 + 4 fr - 3 + 4 @@ -7354,7 +7354,7 @@ change date - 2012-09-14T11:57:58Z + 2012-09-18T14:20:34Z changed values class @@ -7364,7 +7364,7 @@ flags 0 hash - dcecc0eedf9132813ef8a938d07d7917 + 9801969515d6fafb3f05d8b036f9e80d name Localizable.strings @@ -7898,6 +7898,31 @@ La cause était: %2$@ snapshots + + change date + 2001-01-01T00:00:00Z + changed values + + class + BLStringKeyObject + comment + No comment provided by engineer. + errors + + flags + 0 + key + Cannot write image to photo library + localizations + + en + Cannot write image to photo library + fr + Impossible d'enregistrer l'image dans l'album photo + + snapshots + + change date 2001-01-01T00:00:00Z @@ -8373,31 +8398,6 @@ La cause était: %2$@ snapshots - - change date - 2001-01-01T00:00:00Z - changed values - - class - BLStringKeyObject - comment - No comment provided by engineer. - errors - - flags - 0 - key - Incoming file stored to your photo library - localizations - - en - Incoming file stored to your photo library - fr - Fichier entrant stocké - - snapshots - - change date 2001-01-01T00:00:00Z @@ -8721,31 +8721,6 @@ La cause était: %2$@ snapshots - - change date - 2001-01-01T00:00:00Z - changed values - - class - BLStringKeyObject - comment - No comment provided by engineer. - errors - - flags - 0 - key - Photo library - localizations - - en - Photo library - fr - Photos - - snapshots - - change date 2001-01-01T00:00:00Z @@ -9661,6 +9636,56 @@ Raison: %2$s snapshots + + change date + 2001-01-01T00:00:00Z + changed values + + class + BLStringKeyObject + comment + No comment provided by engineer. + errors + + flags + 0 + key + Incoming file stored to your photo library + localizations + + en + Incoming file stored to your photo library + fr + Fichier entrant stocké + + snapshots + + + + change date + 2001-01-01T00:00:00Z + changed values + + class + BLStringKeyObject + comment + No comment provided by engineer. + errors + + flags + 0 + key + Photo library + localizations + + en + Photo library + fr + Photos + + snapshots + + plist file diff --git a/linphone.ldb/Resources/Classes/ChatRoomViewController/15/ChatRoomViewController.xib b/linphone.ldb/Resources/Classes/ChatRoomViewController/16/ChatRoomViewController.xib similarity index 98% rename from linphone.ldb/Resources/Classes/ChatRoomViewController/15/ChatRoomViewController.xib rename to linphone.ldb/Resources/Classes/ChatRoomViewController/16/ChatRoomViewController.xib index dfb378fa5..1972001d5 100644 --- a/linphone.ldb/Resources/Classes/ChatRoomViewController/15/ChatRoomViewController.xib +++ b/linphone.ldb/Resources/Classes/ChatRoomViewController/16/ChatRoomViewController.xib @@ -156,7 +156,7 @@ - -2147483356 + -2147483382 @@ -166,10 +166,7 @@ _NS:9 - - 1 - MSAwLjI4MzE1ODM3MjYgMC4wNTY3ODY4OTE2MQA - + NO IBCocoaTouchFramework @@ -179,7 +176,7 @@ - 292 + 297 {{250, 0}, {70, 59}} @@ -214,7 +211,7 @@ - 292 + 298 {{20, 25}, {213, 9}} @@ -274,6 +271,7 @@ {{250, 0}, {70, 59}} + _NS:9 NO @@ -639,6 +637,14 @@ 86 + + + transferBackgroundImage + + + + 88 + dataSource @@ -930,7 +936,7 @@ 83 - footerBackgroundImage + transfertBackgroundImage @@ -976,7 +982,7 @@ - 87 + 88 @@ -1047,6 +1053,7 @@ UIButton UIButton ChatRoomTableViewController + UIImageView UIView @@ -1114,6 +1121,10 @@ tableController ChatRoomTableViewController + + transferBackgroundImage + UIImageView + transferView UIView diff --git a/linphone.ldb/Resources/Classes/ImageViewController/3/ImageViewController.xib b/linphone.ldb/Resources/Classes/ImageViewController/4/ImageViewController.xib similarity index 99% rename from linphone.ldb/Resources/Classes/ImageViewController/3/ImageViewController.xib rename to linphone.ldb/Resources/Classes/ImageViewController/4/ImageViewController.xib index 0e9bb3507..7e003d3c6 100644 --- a/linphone.ldb/Resources/Classes/ImageViewController/3/ImageViewController.xib +++ b/linphone.ldb/Resources/Classes/ImageViewController/4/ImageViewController.xib @@ -42,7 +42,6 @@ {{0, 44}, {320, 416}} - _NS:9 3 @@ -58,7 +57,7 @@ - 292 + 290 {320, 44} diff --git a/linphone.ldb/Resources/Resources/Localizable/1/Localizable.strings b/linphone.ldb/Resources/Resources/Localizable/1/Localizable.strings index 77e98c0aebfadcfe5429948995256911fe4b7664..d1ab447f8b2aa74569ea31a4e87ceb1964227673 100644 GIT binary patch delta 112 zcmX?5)mORUDgWfv+&t3d3`Go?3?&Sy3<^Lxmm!fM9n3Ca$e(;hPF>R(D3S++`9L)a h4ER;cPxh1#n_Mfvv-v&$D!$E&o9v delta 124 zcmeCHJW#daDgWeLL9Wer1y=D*j+Wz_{9lekR)L|Ip@boyp@<=sA%#H!%vNBi1d5b0 s6iwz4P@lY7Mu-TNF#@HVo8?2;CkqNFF$PRd5Gb51ugSOhvD#`;0OrRc$p8QV