Merge branch 'dev_alasset_to_photokit' into dev_group_chat

This commit is contained in:
Benjamin Verdier 2018-06-26 10:52:34 +02:00
commit 03a4c7174a
19 changed files with 471 additions and 389 deletions

View file

@ -10,6 +10,18 @@ Group changes to describe their impact on the project, as follows:
Fixed for any bug fixes.
Security to invite users to upgrade in case of vulnerabilities.
## [Unreleased]
### Added
- Auto-layout of images in chat messages
### Changed
- Use of Photokit instead of Asset Library for image handling
### Fixed
### Removed
## [4.0] - 2018-06-11
### Added

View file

@ -27,7 +27,7 @@
@protocol ChatConversationDelegate <NSObject>
- (BOOL)startImageUpload:(UIImage *)image url:(NSURL *)url withQuality:(float)quality;
- (BOOL)startImageUpload:(UIImage *)image assetId:(NSString *)phAssetId withQuality:(float)quality;
- (BOOL)startFileUpload:(NSData *)data withUrl:(NSURL *)url;
- (void)resendChat:(NSString *)message withExternalUrl:(NSString *)url;
- (void)tableViewIsScrolling;
@ -41,6 +41,7 @@
@property(nonatomic) LinphoneChatRoom *chatRoom;
@property(nonatomic, strong) id<ChatConversationDelegate> chatRoomDelegate;
@property NSMutableDictionary<NSString *, UIImage *> *imagesInChatroom;
- (void)addEventEntry:(LinphoneEventLog *)event;
- (void)scrollToBottom:(BOOL)animated;

View file

@ -39,29 +39,36 @@
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.tableView.accessibilityIdentifier = @"ChatRoom list";
_imagesInChatroom = [NSMutableDictionary dictionary];
}
#pragma mark -
- (void)clearEventList {
[eventList removeAllObjects];
for (NSValue *value in eventList) {
LinphoneEventLog *event = value.pointerValue;
linphone_event_log_unref(event);
}
[eventList removeAllObjects];
}
- (void)updateData {
[self clearEventList];
if (!_chatRoom)
return;
[self clearEventList];
LinphoneChatRoomCapabilitiesMask capabilities = linphone_chat_room_get_capabilities(_chatRoom);
bctbx_list_t *chatRoomEvents = (capabilities & LinphoneChatRoomCapabilitiesOneToOne)
? linphone_chat_room_get_history_message_events(_chatRoom, 0)
: linphone_chat_room_get_history_events(_chatRoom, 0);
bctbx_list_t *head = chatRoomEvents;
eventList = [[NSMutableArray alloc] initWithCapacity:bctbx_list_size(chatRoomEvents)];
while (chatRoomEvents) {
LinphoneEventLog *event = (LinphoneEventLog *)chatRoomEvents->data;
[eventList addObject:[NSValue valueWithPointer:linphone_event_log_ref(event)]];
chatRoomEvents = chatRoomEvents->next;
}
bctbx_list_free_with_data(head, (bctbx_list_free_func)linphone_event_log_unref);
for (FileTransferDelegate *ftd in [LinphoneManager.instance fileTransferDelegates]) {
const LinphoneAddress *ftd_peer =
@ -83,7 +90,6 @@
- (void)addEventEntry:(LinphoneEventLog *)event {
[eventList addObject:[NSValue valueWithPointer:linphone_event_log_ref(event)]];
int pos = (int)eventList.count - 1;
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:pos inSection:0];
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:@[ indexPath ] withRowAnimation:UITableViewRowAnimationFade];
@ -98,16 +104,16 @@
}
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:index inSection:0]]
withRowAnimation:FALSE]; // just reload
return;
return;
}
- (void)scrollToBottom:(BOOL)animated {
[self.tableView reloadData];
//[self.tableView reloadData];
size_t count = eventList.count;
if (!count)
return;
[self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:(count - 1) inSection:0]];
//[self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:(count - 1) inSection:0]];
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:(count - 1) inSection:0]
atScrollPosition:UITableViewScrollPositionBottom
animated:YES];
@ -231,7 +237,6 @@ static const CGFloat MESSAGE_SPACING_PERCENTAGE = 5.f;
if (nextEvent) {
LinphoneChatMessage *nextChat = linphone_event_log_get_chat_message(nextEvent);
if (!linphone_address_equal(linphone_chat_message_get_from_address(nextChat), linphone_chat_message_get_from_address(chat))) {
LOGD(@"BITE");
height += tableView.frame.size.height * MESSAGE_SPACING_PERCENTAGE / 100;
}
}

View file

@ -17,6 +17,8 @@
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#import <Photos/PHAssetChangeRequest.h>
#import "ChatConversationView.h"
#import "PhoneMainView.h"
#import "Utils.h"
@ -220,7 +222,7 @@ static UICompositeViewDescription *compositeDescription = nil;
//share photo
NSData *data = dict[@"nsData"];
UIImage *image = [[UIImage alloc] initWithData:data];
[self chooseImageQuality:image url:nil];
[self chooseImageQuality:image assetId:nil];
[defaults removeObjectForKey:@"img"];
} else if (dictWeb) {
//share url, if local file, then upload file
@ -308,38 +310,11 @@ static UICompositeViewDescription *compositeDescription = nil;
return TRUE;
}
- (void)saveAndSend:(UIImage *)image url:(NSURL *)url withQuality:(float)quality{
// photo from Camera, must be saved first
if (url == nil) {
[LinphoneManager.instance.photoLibrary
writeImageToSavedPhotosAlbum:image.CGImage
orientation:(ALAssetOrientation)[image imageOrientation]
completionBlock:^(NSURL *assetURL, NSError *error) {
if (error) {
LOGE(@"Cannot save image data downloaded [%@]", [error localizedDescription]);
UIAlertController *errView = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Transfer error", nil)
message:NSLocalizedString(@"Cannot write image to photo library",
nil)
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:@"OK"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {}];
[errView addAction:defaultAction];
[self presentViewController:errView animated:YES completion:nil];
} else {
LOGI(@"Image saved to [%@]", [assetURL absoluteString]);
[self startImageUpload:image url:assetURL withQuality:quality];
}
}];
} else {
[self startImageUpload:image url:url withQuality:quality];
}
- (void)saveAndSend:(UIImage *)image assetId:(NSString *)phAssetId withQuality:(float)quality{
[self startImageUpload:image assetId:phAssetId withQuality:quality];
}
- (void)chooseImageQuality:(UIImage *)image url:(NSURL *)url {
- (void)chooseImageQuality:(UIImage *)image assetId:(NSString *)phAssetId {
DTActionSheet *sheet = [[DTActionSheet alloc] initWithTitle:NSLocalizedString(@"Choose the image size", nil)];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (NSString *key in [imageQualities allKeys]) {
@ -349,7 +324,7 @@ static UICompositeViewDescription *compositeDescription = nil;
NSString *text = [NSString stringWithFormat:@"%@ (%@)", key, [size toHumanReadableSize]];
[sheet addButtonWithTitle:text
block:^() {
[self saveAndSend:image url:url withQuality:[quality floatValue]];
[self saveAndSend:image assetId:phAssetId withQuality:[quality floatValue]];
}];
}
[sheet addCancelButtonWithTitle:NSLocalizedString(@"Cancel", nil) block:nil];
@ -601,9 +576,9 @@ static UICompositeViewDescription *compositeDescription = nil;
#pragma mark ChatRoomDelegate
- (BOOL)startImageUpload:(UIImage *)image url:(NSURL *)url withQuality:(float)quality {
- (BOOL)startImageUpload:(UIImage *)image assetId:(NSString *)phAssetId withQuality:(float)quality {
FileTransferDelegate *fileTransfer = [[FileTransferDelegate alloc] init];
[fileTransfer upload:image withURL:url forChatRoom:_chatRoom withQuality:quality];
[fileTransfer upload:image withassetId:phAssetId forChatRoom:_chatRoom withQuality:quality];
[_tableController scrollToBottom:true];
return TRUE;
}
@ -621,7 +596,7 @@ static UICompositeViewDescription *compositeDescription = nil;
#pragma mark ImagePickerDelegate
- (void)imagePickerDelegateImage:(UIImage *)image info:(NSDictionary *)info {
- (void)imagePickerDelegateImage:(UIImage *)image info:(NSString *)phAssetId {
// When getting image from the camera, it may be 90° rotated due to orientation
// (image.imageOrientation = UIImageOrientationRight). Just rotate it to be face up.
if (image.imageOrientation != UIImageOrientationUp) {
@ -635,9 +610,7 @@ static UICompositeViewDescription *compositeDescription = nil;
if (IPAD) {
[VIEW(ImagePickerView).popoverController dismissPopoverAnimated:TRUE];
}
NSURL *url = [info valueForKey:UIImagePickerControllerReferenceURL];
[self chooseImageQuality:image url:url];
[self chooseImageQuality:image assetId:phAssetId];
}
- (void)tableViewIsScrolling {

View file

@ -21,7 +21,7 @@
@protocol ImagePickerDelegate <NSObject>
- (void)imagePickerDelegateImage:(UIImage *)image info:(NSDictionary *)info;
- (void)imagePickerDelegateImage:(UIImage *)image info:(NSString *)phAssetId;
@end

View file

@ -20,7 +20,6 @@
#import <MobileCoreServices/UTCoreTypes.h>
#import <AVFoundation/AVCaptureDevice.h>
#import <AVFoundation/AVFoundation.h>
#import <Photos/Photos.h>
#import "ImagePickerView.h"
#import "PhoneMainView.h"
@ -162,15 +161,38 @@ static UICompositeViewDescription *compositeDescription = nil;
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
[self dismiss];
UIImage *image = [info objectForKey:UIImagePickerControllerEditedImage];
if (image == nil) {
image = [info objectForKey:UIImagePickerControllerOriginalImage];
}
if (image != nil && imagePickerDelegate != nil) {
[imagePickerDelegate imagePickerDelegateImage:image info:info];
}
PHAsset *phasset = [info objectForKey:UIImagePickerControllerPHAsset];
UIImage *image = [info objectForKey:UIImagePickerControllerEditedImage] ? [info objectForKey:UIImagePickerControllerEditedImage] : [info objectForKey:UIImagePickerControllerOriginalImage];
if (!phasset) {
__block PHObjectPlaceholder *placeHolder;
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
PHAssetCreationRequest *request = [PHAssetCreationRequest creationRequestForAssetFromImage:image];
placeHolder = [request placeholderForCreatedAsset];
} completionHandler:^(BOOL success, NSError *error) {
if (success) {
LOGI(@"Image saved to [%@]", [placeHolder localIdentifier]);
[self passImageToDelegate:image PHAssetId:[placeHolder localIdentifier]];
} else {
LOGE(@"Cannot save image data downloaded [%@]", [error localizedDescription]);
}
}
];
return;
}
[self passImageToDelegate:image PHAssetId:[phasset localIdentifier]];
}
- (void) passImageToDelegate:(UIImage *)image PHAssetId:(NSString *)assetId {
if (imagePickerDelegate != nil) {
[imagePickerDelegate imagePickerDelegateImage:image info:(NSString *)assetId];
}
}
/*
if (imagePickerDelegate != nil) {
[imagePickerDelegate imagePickerDelegateImage:image info:(__bridge NSDictionary *)contextInfo];
}
}
*/
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker {
[self dismiss];
}
@ -223,35 +245,57 @@ static UICompositeViewDescription *compositeDescription = nil;
[PhoneMainView.instance changeCurrentView:view.compositeViewDescription];
}
};
DTActionSheet *sheet = [[DTActionSheet alloc] initWithTitle:NSLocalizedString(@"Select the source", nil)];
if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
[sheet addButtonWithTitle:NSLocalizedString(@"Camera", nil)
block:^() {
if([AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo] == AVAuthorizationStatusAuthorized ){
if([PHPhotoLibrary authorizationStatus] != PHAuthorizationStatusDenied ){
block(UIImagePickerControllerSourceTypeCamera);
}else{
[[[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Photo's permission", nil) message:NSLocalizedString(@"Photo not authorized", nil) delegate:nil cancelButtonTitle:nil otherButtonTitles:@"Continue", nil] show];
}
}else {
[[[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Camera's permission", nil) message:NSLocalizedString(@"Camera not authorized", nil) delegate:nil cancelButtonTitle:nil otherButtonTitles:@"Continue", nil] show];
}
}];
}
if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary]) {
[sheet addButtonWithTitle:NSLocalizedString(@"Photo library", nil)
block:^() {
if([PHPhotoLibrary authorizationStatus] != PHAuthorizationStatusDenied ){
block(UIImagePickerControllerSourceTypePhotoLibrary);
}else{
[[[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Photo's permission", nil) message:NSLocalizedString(@"Photo not authorized", nil) delegate:nil cancelButtonTitle:nil otherButtonTitles:@"Continue", nil] show];
}
}];
}
[sheet addCancelButtonWithTitle:NSLocalizedString(@"Cancel", nil) block:nil];
[sheet showInView:PhoneMainView.instance.view];
if ([PHPhotoLibrary authorizationStatus] == PHAuthorizationStatusAuthorized) {
DTActionSheet *sheet = [[DTActionSheet alloc] initWithTitle:NSLocalizedString(@"Select the source", nil)];
if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
[sheet addButtonWithTitle:NSLocalizedString(@"Camera", nil)
block:^() {
if([AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo] != AVAuthorizationStatusAuthorized ) {
[[[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Camera's permission", nil) message:NSLocalizedString(@"Camera not authorized", nil) delegate:nil cancelButtonTitle:nil otherButtonTitles:@"Continue", nil] show];
return;
}
block(UIImagePickerControllerSourceTypeCamera);
}];
}
if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary]) {
[sheet addButtonWithTitle:NSLocalizedString(@"Photo library", nil)
block:^() {
block(UIImagePickerControllerSourceTypePhotoLibrary);
}];
}
[sheet addCancelButtonWithTitle:NSLocalizedString(@"Cancel", nil) block:nil];
[sheet showInView:PhoneMainView.instance.view];
} else {
[PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
dispatch_async(dispatch_get_main_queue(), ^{
if ([PHPhotoLibrary authorizationStatus] == PHAuthorizationStatusAuthorized) {
DTActionSheet *sheet = [[DTActionSheet alloc] initWithTitle:NSLocalizedString(@"Select the source", nil)];
if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
[sheet addButtonWithTitle:NSLocalizedString(@"Camera", nil)
block:^() {
if([AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo] != AVAuthorizationStatusAuthorized ) {
[[[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Camera's permission", nil) message:NSLocalizedString(@"Camera not authorized", nil) delegate:nil cancelButtonTitle:nil otherButtonTitles:@"Continue", nil] show];
return;
}
block(UIImagePickerControllerSourceTypeCamera);
}];
}
if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary]) {
[sheet addButtonWithTitle:NSLocalizedString(@"Photo library", nil)
block:^() {
block(UIImagePickerControllerSourceTypePhotoLibrary);
}];
}
[sheet addCancelButtonWithTitle:NSLocalizedString(@"Cancel", nil) block:nil];
[sheet showInView:PhoneMainView.instance.view];
} else {
[[[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Photo's permission", nil) message:NSLocalizedString(@"Photo not authorized", nil) delegate:nil cancelButtonTitle:nil otherButtonTitles:@"Continue", nil] show];
}
});
}];
}
}
@end

View file

@ -20,7 +20,7 @@
#import <Foundation/Foundation.h>
#import <SystemConfiguration/SCNetworkReachability.h>
#import <AudioToolbox/AudioToolbox.h>
#import <AssetsLibrary/ALAssetsLibrary.h>
#import <Photos/Photos.h>
#import <CoreTelephony/CTCallCenter.h>
#import <sqlite3.h>
@ -208,6 +208,8 @@ typedef struct _LinphoneManagerSounds {
- (void)checkNewVersion;
- (void)loadAvatar;
@property ProviderDelegate *providerDelegate;
@property (readonly) BOOL isTesting;
@ -225,7 +227,6 @@ typedef struct _LinphoneManagerSounds {
@property (nonatomic, assign) BOOL speakerEnabled;
@property (nonatomic, assign) BOOL bluetoothAvailable;
@property (nonatomic, assign) BOOL bluetoothEnabled;
@property (readonly) ALAssetsLibrary *photoLibrary;
@property (readonly) NSString* contactSipField;
@property (readonly,copy) NSString* contactFilter;
@property (copy) void (^silentPushCompletion)(UIBackgroundFetchResult);
@ -238,5 +239,6 @@ typedef struct _LinphoneManagerSounds {
@property NSDictionary *pushDict;
@property(strong, nonatomic) OrderedDictionary *linphoneManagerAddressBookMap;
@property (nonatomic, assign) BOOL contactsUpdated;
@property UIImage *avatar;
@end

View file

@ -263,7 +263,6 @@ struct codec_name_pref_table codec_pref_table[] = {{"speex", 8000, "speex_8k_pre
_fileTransferDelegates = [[NSMutableArray alloc] init];
_linphoneManagerAddressBookMap = [[OrderedDictionary alloc] init];
pushCallIDs = [[NSMutableArray alloc] init];
_photoLibrary = [[ALAssetsLibrary alloc] init];
_isTesting = [LinphoneManager isRunningTests];
[self renameDefaultSettings];
[self copyDefaultSettings];
@ -288,6 +287,7 @@ struct codec_name_pref_table codec_pref_table[] = {{"speex", 8000, "speex_8k_pre
}
[self migrateFromUserPrefs];
[self loadAvatar];
}
return self;
}
@ -2887,20 +2887,19 @@ static int comp_call_state_paused(const LinphoneCall *call, const void *param) {
}
+ (void)setValueInMessageAppData:(id)value forKey:(NSString *)key inMessage:(LinphoneChatMessage *)msg {
NSMutableDictionary *appDataDict = [NSMutableDictionary dictionary];
const char *appData = linphone_chat_message_get_appdata(msg);
if (appData) {
appDataDict = [NSJSONSerialization JSONObjectWithData:[NSData dataWithBytes:appData length:strlen(appData)]
options:NSJSONReadingMutableContainers
error:nil];
}
NSMutableDictionary *appDataDict = [NSMutableDictionary dictionary];
const char *appData = linphone_chat_message_get_appdata(msg);
if (appData) {
appDataDict = [NSJSONSerialization JSONObjectWithData:[NSData dataWithBytes:appData length:strlen(appData)]
options:NSJSONReadingMutableContainers
error:nil];
}
[appDataDict setValue:value forKey:key];
[appDataDict setValue:value forKey:key];
NSData *data = [NSJSONSerialization dataWithJSONObject:appDataDict options:0 error:nil];
NSString *appdataJSON = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
linphone_chat_message_set_appdata(msg, [appdataJSON UTF8String]);
NSData *data = [NSJSONSerialization dataWithJSONObject:appDataDict options:0 error:nil];
NSString *appdataJSON = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
linphone_chat_message_set_appdata(msg, [appdataJSON UTF8String]);
}
#pragma mark - LPConfig Functions
@ -3077,4 +3076,33 @@ static int comp_call_state_paused(const LinphoneCall *call, const void *param) {
const char *curVersionCString = [curVersion cStringUsingEncoding:NSUTF8StringEncoding];
linphone_core_check_for_update(theLinphoneCore, curVersionCString);
}
- (void)loadAvatar {
NSString *assetId = [self lpConfigStringForKey:@"avatar"];
__block UIImage *ret = nil;
if (assetId) {
PHFetchResult<PHAsset *> *assets = [PHAsset fetchAssetsWithLocalIdentifiers:[NSArray arrayWithObject:assetId] options:nil];
if (![assets firstObject]) {
LOGE(@"Can't fetch avatar image.");
}
PHAsset *asset = [assets firstObject];
// load avatar synchronously so that we can return UIIMage* directly - since we are
// only using thumbnail, it must be pretty fast to fetch even without cache.
PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init];
options.synchronous = TRUE;
[[PHImageManager defaultManager] requestImageForAsset:asset targetSize:PHImageManagerMaximumSize contentMode:PHImageContentModeDefault options:options
resultHandler:^(UIImage *image, NSDictionary * info) {
if (image)
ret = [UIImage UIImageThumbnail:image thumbSize:150];
else
LOGE(@"Can't read avatar");
}];
}
if (!ret) {
ret = [UIImage imageNamed:@"avatar.png"];
}
_avatar = ret;
}
@end

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14113" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14113" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
@ -21,6 +21,7 @@
<outlet property="downloadButton" destination="N75-gL-R6t" id="EgN-Ab-Ded"/>
<outlet property="fileName" destination="Dho-UV-6Ev" id="Iro-II-w8a"/>
<outlet property="fileTransferProgress" destination="USm-wC-GvG" id="POt-YD-NCG"/>
<outlet property="finalAssetView" destination="VYJ-RC-Jmg" id="fnb-mY-nPR"/>
<outlet property="finalImage" destination="gzv-K4-5OL" id="YIw-kM-Ld6"/>
<outlet property="imageGestureRecognizer" destination="aDF-hC-ddO" id="2jh-Rr-eKk"/>
<outlet property="imageSubView" destination="GmN-7v-uuO" id="k9r-Xc-csv"/>
@ -35,19 +36,19 @@
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" misplaced="YES" id="UGz-WT-BUv">
<view contentMode="scaleToFill" id="UGz-WT-BUv">
<rect key="frame" x="0.0" y="0.0" width="377" height="301"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
<subviews>
<view clipsSubviews="YES" contentMode="scaleToFill" id="Y7i-Gm-AdY" userLabel="innerView">
<view clipsSubviews="YES" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Y7i-Gm-AdY" userLabel="innerView">
<rect key="frame" x="6" y="5" width="365" height="291"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView userInteractionEnabled="NO" alpha="0.20000000298023224" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="color_A.png" id="U2P-5n-gg8" userLabel="backgroundColorImage">
<imageView userInteractionEnabled="NO" alpha="0.20000000298023224" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="color_A.png" translatesAutoresizingMaskIntoConstraints="NO" id="U2P-5n-gg8" userLabel="backgroundColorImage">
<rect key="frame" x="0.0" y="0.0" width="365" height="291"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES" flexibleMaxY="YES"/>
</imageView>
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" image="avatar.png" id="hD2-19-7IH" userLabel="avatarImage" customClass="UIRoundedImageView">
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" fixedFrame="YES" image="avatar.png" translatesAutoresizingMaskIntoConstraints="NO" id="hD2-19-7IH" userLabel="avatarImage" customClass="UIRoundedImageView">
<rect key="frame" x="7" y="7" width="40" height="40"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="Contact avatar">
@ -55,7 +56,7 @@
<bool key="isElement" value="YES"/>
</accessibility>
</imageView>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="11:35 John Doe" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="JyR-RQ-uwF" userLabel="contactDateLabel">
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" fixedFrame="YES" text="11:35 John Doe" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="JyR-RQ-uwF" userLabel="contactDateLabel">
<rect key="frame" x="55" y="8" width="286" height="14"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="Contact name"/>
@ -63,16 +64,16 @@
<color key="textColor" red="0.98766469955444336" green="0.27512490749359131" blue="0.029739789664745331" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<view contentMode="scaleToFill" misplaced="YES" id="8I3-n2-0kS" userLabel="view">
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="8I3-n2-0kS" userLabel="view">
<rect key="frame" x="39" y="55" width="298" height="190"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES" flexibleMaxY="YES"/>
<subviews>
<imageView contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="linphone_logo.png" id="yMW-cT-bpU" userLabel="image" customClass="UILoadingImageView">
<imageView contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="linphone_logo.png" translatesAutoresizingMaskIntoConstraints="NO" id="yMW-cT-bpU" userLabel="image" customClass="UILoadingImageView">
<rect key="frame" x="0.0" y="0.0" width="298" height="122"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES" flexibleMaxY="YES"/>
<gestureRecognizers/>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" misplaced="YES" text="Label" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Dho-UV-6Ev" userLabel="fileName">
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Label" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Dho-UV-6Ev" userLabel="fileName">
<rect key="frame" x="0.0" y="0.0" width="200" height="50"/>
<autoresizingMask key="autoresizingMask"/>
<color key="backgroundColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
@ -80,15 +81,15 @@
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
<view contentMode="scaleToFill" id="GmN-7v-uuO" userLabel="imageSubView">
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="GmN-7v-uuO" userLabel="imageSubView">
<rect key="frame" x="0.0" y="128" width="297" height="62"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES"/>
<subviews>
<progressView opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="750" progress="0.5" id="USm-wC-GvG" userLabel="transferProgress">
<progressView opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="750" fixedFrame="YES" progress="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="USm-wC-GvG" userLabel="transferProgress">
<rect key="frame" x="10" y="29" width="277" height="2"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
</progressView>
<button opaque="NO" contentMode="scaleToFill" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" id="N75-gL-R6t" userLabel="downloadButton" customClass="UIRoundBorderedButton">
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="N75-gL-R6t" userLabel="downloadButton" customClass="UIRoundBorderedButton">
<rect key="frame" x="84" y="33" width="115" height="27"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/>
<accessibility key="accessibilityConfiguration" label="Download"/>
@ -100,7 +101,7 @@
<action selector="onDownloadClick:" destination="-1" eventType="touchUpInside" id="8BO-9E-iOX"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" id="6dl-Nz-rdv" userLabel="cancelButton" customClass="UIRoundBorderedButton">
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="6dl-Nz-rdv" userLabel="cancelButton" customClass="UIRoundBorderedButton">
<rect key="frame" x="84" y="33" width="115" height="27"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/>
<accessibility key="accessibilityConfiguration" label="Cancel"/>
@ -117,16 +118,16 @@
</view>
</subviews>
</view>
<view contentMode="scaleToFill" misplaced="YES" id="VYJ-RC-Jmg">
<view clipsSubviews="YES" contentMode="scaleToFill" fixedFrame="YES" preservesSuperviewLayoutMargins="YES" translatesAutoresizingMaskIntoConstraints="NO" id="VYJ-RC-Jmg" userLabel="finalAssetView">
<rect key="frame" x="8" y="55" width="349" height="213"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES" flexibleMaxY="YES"/>
<subviews>
<imageView hidden="YES" userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" misplaced="YES" id="gzv-K4-5OL" userLabel="finalImage">
<imageView hidden="YES" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="gzv-K4-5OL" userLabel="finalImage">
<rect key="frame" x="0.0" y="0.0" width="349" height="213"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<gestureRecognizers/>
</imageView>
<button opaque="NO" contentMode="scaleToFill" misplaced="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" id="cvc-tl-Pcf" userLabel="playButton" customClass="UIRoundBorderedButton">
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="cvc-tl-Pcf" userLabel="playButton" customClass="UIRoundBorderedButton">
<rect key="frame" x="125" y="93" width="50" height="25"/>
<autoresizingMask key="autoresizingMask"/>
<accessibility key="accessibilityConfiguration" label="Cancel"/>
@ -145,27 +146,27 @@
<outletCollection property="gestureRecognizers" destination="NYA-II-xYn" appends="YES" id="fK6-ld-zOX"/>
</connections>
</view>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="color_A.png" id="6dA-3U-OPW" userLabel="bottomBarColor">
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="color_A.png" translatesAutoresizingMaskIntoConstraints="NO" id="6dA-3U-OPW" userLabel="bottomBarColor">
<rect key="frame" x="0.0" y="290" width="365" height="1"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
</imageView>
<activityIndicatorView opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" hidesWhenStopped="YES" animating="YES" style="gray" id="Eab-ND-ix3" userLabel="statusInprogressSpinner">
<activityIndicatorView opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" fixedFrame="YES" hidesWhenStopped="YES" animating="YES" style="gray" translatesAutoresizingMaskIntoConstraints="NO" id="Eab-ND-ix3" userLabel="statusInprogressSpinner">
<rect key="frame" x="345" y="0.0" width="20" height="20"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
</activityIndicatorView>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" misplaced="YES" image="chat_unsecure.png" id="IST-5o-DCu" userLabel="LIMEKO">
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="chat_unsecure.png" translatesAutoresizingMaskIntoConstraints="NO" id="IST-5o-DCu" userLabel="LIMEKO">
<rect key="frame" x="351" y="6" width="8" height="12"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="Delivery failed"/>
</imageView>
<label hidden="YES" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" misplaced="YES" text="Delivered" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="44j-me-Iqi" userLabel="imdmLabel">
<label hidden="YES" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Delivered" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="44j-me-Iqi" userLabel="imdmLabel">
<rect key="frame" x="283" y="274" width="64" height="20"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="11"/>
<color key="textColor" red="1" green="0.36862745099999999" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<imageView hidden="YES" userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" misplaced="YES" image="valid_default.png" id="LPj-VT-0fH" userLabel="imdmIcon">
<imageView hidden="YES" userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="valid_default.png" translatesAutoresizingMaskIntoConstraints="NO" id="LPj-VT-0fH" userLabel="imdmIcon">
<rect key="frame" x="349" y="276" width="13" height="13"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
<accessibility key="accessibilityConfiguration" label="Delivery failed"/>

View file

@ -67,7 +67,7 @@
</subviews>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
</view>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" misplaced="YES" image="chat_read.png" id="aa2-Kl-c1H" userLabel="imdmIcon">
<imageView hidden="YES" userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" misplaced="YES" image="chat_read.png" id="aa2-Kl-c1H" userLabel="imdmIcon">
<rect key="frame" x="59" y="39" width="13" height="13"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="Delivery failed">

View file

@ -36,6 +36,7 @@
@property(weak, nonatomic) IBOutlet UIView *imageSubView;
@property (strong, nonatomic) IBOutlet UITapGestureRecognizer *openRecognizer;
@property(weak, nonatomic) IBOutlet UIView *totalView;
@property (weak, nonatomic) IBOutlet UIView *finalAssetView;
@property (weak, nonatomic) IBOutlet UIImageView *finalImage;
@property(strong, nonatomic) IBOutlet UITapGestureRecognizer *imageGestureRecognizer;

View file

@ -30,9 +30,8 @@
@implementation UIChatBubblePhotoCell {
FileTransferDelegate *_ftd;
CGSize imageSize, bubbleSize, videoDefaultSize;
int actualAvailableWidth;
ChatConversationTableView *chatTableView;
//CGImageRef displayedImage;
BOOL assetIsLoaded;
}
#pragma mark - Lifecycle Functions
@ -51,15 +50,18 @@
break;
}
}
[self setFrame:CGRectMake(0, 0, 5, 100)];
[self addSubview:sub];
chatTableView = VIEW(ChatConversationView).tableController;
actualAvailableWidth = chatTableView.tableView.frame.size.width;
videoDefaultSize = CGSizeMake(320, 240);
assetIsLoaded = FALSE;
}
return self;
}
- (void)onDelete {
[super onDelete];
}
#pragma mark -
- (void)setEvent:(LinphoneEventLog *)event {
if (!event || !(linphone_event_log_get_type(event) == LinphoneEventLogTypeConferenceChatMessage))
@ -76,6 +78,7 @@
_finalImage.image = nil;
_finalImage.hidden = TRUE;
_fileTransferProgress.progress = 0;
assetIsLoaded = FALSE;
[self disconnectFromFileDelegate];
if (amessage) {
@ -98,25 +101,33 @@
[super setChatMessage:amessage];
}
- (void) loadImageAsset:(ALAsset*) asset thumb:(UIImage *)thumb image:(UIImage *)image {
- (void) loadImageAsset:(PHAsset*) asset image:(UIImage *)image {
dispatch_async(dispatch_get_main_queue(), ^{
[_finalImage setImage:image];
[_messageImageView setImage:thumb];
[_messageImageView setFullImageUrl:asset];
[_messageImageView setAsset:asset];
[_messageImageView stopLoading];
_messageImageView.hidden = YES;
_imageGestureRecognizer.enabled = YES;
_finalImage.hidden = NO;
[self layoutSubviews];
});
}
- (void) loadAsset:(ALAsset*) asset {
UIImage *thumb = [[UIImage alloc] initWithCGImage:[asset thumbnail]];
ALAssetRepresentation *representation = [asset defaultRepresentation];
imageSize = [UIChatBubbleTextCell getMediaMessageSizefromOriginalSize:[representation dimensions] withWidth:chatTableView.tableView.frame.size.width];
CGImageRef tmpImg = [self cropImageFromRepresentation:representation];
UIImage *image = [[UIImage alloc] initWithCGImage:tmpImg];
[self loadImageAsset:asset thumb:thumb image:image];
- (void) loadAsset:(PHAsset *) asset {
PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init];
options.synchronous = TRUE;
[[PHImageManager defaultManager] requestImageForAsset:asset targetSize:PHImageManagerMaximumSize contentMode:PHImageContentModeDefault options:options
resultHandler:^(UIImage *image, NSDictionary * info) {
if (image) {
imageSize = [UIChatBubbleTextCell getMediaMessageSizefromOriginalSize:[image size] withWidth:chatTableView.tableView.frame.size.width];
UIImage *newImage = [UIImage UIImageResize:image toSize:imageSize];
[chatTableView.imagesInChatroom setObject:newImage forKey:[asset localIdentifier]];
[self loadImageAsset:asset image:newImage];
}
else {
LOGE(@"Can't read image");
}
}];
}
- (void) loadVideoAsset: (AVAsset *) asset {
@ -139,7 +150,7 @@
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[self loadImageAsset:nil thumb:thumb image:image];
[self loadImageAsset:nil image:image];
// put the play button in the top
CGRect newFrame = _playButton.frame;
@ -183,40 +194,21 @@
if ([localImage isEqualToString:@"saving..."] || [localVideo isEqualToString:@"saving..."] || [localFile isEqualToString:@"saving..."]) {
_cancelButton.hidden = _fileTransferProgress.hidden = _downloadButton.hidden = _playButton.hidden = _fileName.hidden = YES;
fullScreenImage = YES;
} else {
} else if(!assetIsLoaded) {
assetIsLoaded = TRUE;
if (localImage) {
// we did not load the image yet, so start doing so
if (_messageImageView.image == nil) {
NSURL *imageUrl = [NSURL URLWithString:localImage];
[_messageImageView startLoading];
__block LinphoneChatMessage *achat = self.message;
[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.message) // Avoid glitch and scrolling
return;
if (asset) {
[self loadAsset:asset];
}
else {
[LinphoneManager.instance.photoLibrary
enumerateGroupsWithTypes:ALAssetsGroupAll
usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
[group enumerateAssetsWithOptions:NSEnumerationReverse
usingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop) {
if([result.defaultRepresentation.url isEqual:imageUrl]) {
[self loadAsset:result];
*stop = YES;
}
}];
}
failureBlock:^(NSError *error) {
LOGE(@"Error: Cannot load asset from photo stream - %@", [error localizedDescription]);
}];
}
});
} failureBlock:^(NSError *error) {
LOGE(@"Can't read image");
}];
PHFetchResult<PHAsset *> *assets = [PHAsset fetchAssetsWithLocalIdentifiers:[NSArray arrayWithObject:localImage] options:nil];
UIImage *img = [chatTableView.imagesInChatroom objectForKey:localImage];
if (![assets firstObject])
return;
PHAsset *asset = [assets firstObject];
if (img)
[self loadImageAsset:asset image:img];
else
[self loadAsset:asset];
}
} else if (localVideo) {
if (_messageImageView.image == nil) {
@ -246,16 +238,11 @@
fullScreenImage = YES;
_playButton.hidden = localVideo ? NO : YES;
_fileName.hidden = localFile ? NO : YES;
// Should fix cell not resizing after doanloading image.
[self layoutSubviews];
}
}
}
// resize image so that it take the full bubble space available
CGRect newFrame = _totalView.frame;
newFrame.origin.x = newFrame.origin.y = 0;
if (!fullScreenImage) {
newFrame.size.height -= _imageSubView.frame.size.height;
}
_messageImageView.frame = newFrame;
}
- (void)fileErrorBlock {
@ -336,9 +323,18 @@
if (![_messageImageView isLoading]) {
ImageView *view = VIEW(ImageView);
[PhoneMainView.instance changeCurrentView:view.compositeViewDescription];
CGImageRef fullScreenRef = [[_messageImageView.fullImageUrl defaultRepresentation] fullScreenImage];
UIImage *fullScreen = [UIImage imageWithCGImage:fullScreenRef];
[view setImage:fullScreen];
PHAsset *asset = [_messageImageView asset];
PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init];
options.synchronous = TRUE;
[[PHImageManager defaultManager] requestImageForAsset:asset targetSize:PHImageManagerMaximumSize contentMode:PHImageContentModeDefault options:options
resultHandler:^(UIImage *image, NSDictionary * info) {
if (image) {
[view setImage:image];
}
else {
LOGE(@"Can't read image");
}
}];
}
}
}
@ -366,7 +362,8 @@
}
- (void)disconnectFromFileDelegate {
[NSNotificationCenter.defaultCenter removeObserver:self];
[NSNotificationCenter.defaultCenter removeObserver:self name:kLinphoneFileTransferSendUpdate object:_ftd];
[NSNotificationCenter.defaultCenter removeObserver:self name:kLinphoneFileTransferRecvUpdate object:_ftd];
_ftd = nil;
}
@ -399,32 +396,8 @@
}
}
+ (CGImageRef)resizeCGImage:(CGImageRef)image toWidth:(int)width andHeight:(int)height {
// create context, keeping original image properties
CGColorSpaceRef colorspace = CGImageGetColorSpace(image);
CGContextRef context = CGBitmapContextCreate(NULL, width, height,
CGImageGetBitsPerComponent(image),
CGImageGetBytesPerRow(image),
colorspace,
CGImageGetAlphaInfo(image));
CGColorSpaceRelease(colorspace);
if(context == NULL)
return nil;
// draw image to context (resizing it)
CGContextDrawImage(context, CGRectMake(0, 0, width, height), image);
// extract resulting image from context
CGImageRef imgRef = CGBitmapContextCreateImage(context);
CGContextRelease(context);
return imgRef;
}
- (void)layoutSubviews {
[super layoutSubviews];
//if (!isAssetLoaded) return;
UITableView *tableView = VIEW(ChatConversationView).tableController.tableView;
//[super layoutSubviews];
BOOL is_outgoing = linphone_chat_message_is_outgoing(super.message);
CGRect bubbleFrame = super.bubbleView.frame;
int origin_x;
@ -433,7 +406,7 @@
bubbleFrame.size = bubbleSize;
if (tableView.isEditing) {
if (chatTableView.tableView.isEditing) {
origin_x = 0;
} else {
origin_x = (is_outgoing ? self.frame.size.width - bubbleFrame.size.width : 0);
@ -444,25 +417,6 @@
super.bubbleView.frame = bubbleFrame;
}
- (CGImageRef)cropImageFromRepresentation:(ALAssetRepresentation*)rep {
CGImageRef newImage = [rep fullResolutionImage];
CGSize originalSize = [rep dimensions];
float originalAspectRatio = originalSize.width / originalSize.height;
// We resize in width and crop in height
if (originalSize.width > imageSize.width) {
int height = imageSize.width / originalAspectRatio;
newImage = [self.class resizeCGImage:newImage toWidth:imageSize.width andHeight:height];
originalSize.height = height;
}
CGRect cropRect = CGRectMake(0, 0, imageSize.width, imageSize.height);
if (imageSize.height < originalSize.height) cropRect.origin.y = (originalSize.height - imageSize.height)/2;
newImage = CGImageCreateWithImageInRect(newImage, cropRect);
LOGD([NSString stringWithFormat:@"Image size : width = %g, height = %g", imageSize.width, imageSize.height]);
LOGD([NSString stringWithFormat:@"Bubble size : width = %g, height = %g", super.bubbleView.frame.size.width, super.bubbleView.frame.size.height]);
return newImage;
}
@end

View file

@ -235,26 +235,40 @@
NSString *localVideo = [LinphoneManager getMessageAppDataForKey:@"localvideo" inMessage:_message];
NSString *localFile = [LinphoneManager getMessageAppDataForKey:@"localfile" inMessage:_message];
NSString *fileName = localVideo ? localVideo : localFile;
NSURL *imageUrl = [NSURL URLWithString:localImage];
[self onDelete];
if(localImage){
[LinphoneManager.instance.photoLibrary assetForURL:imageUrl
resultBlock:^(ALAsset *asset) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, (unsigned long)NULL),
^(void) {
UIImage *image = [[UIImage alloc] initWithCGImage:[[asset defaultRepresentation] fullResolutionImage]];
[_chatRoomDelegate startImageUpload:image url:imageUrl withQuality:(uploadQuality ? [uploadQuality floatValue] : 0.9)];
});
}
failureBlock:^(NSError *error) {
LOGE(@"Can't read image");
}];
ChatConversationTableView *tableView = VIEW(ChatConversationView).tableController;
UIImage *img = [tableView.imagesInChatroom objectForKey:localImage];
if (img) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, (unsigned long)NULL),
^(void) {
[_chatRoomDelegate startImageUpload:img assetId:localImage withQuality:(uploadQuality ? [uploadQuality floatValue] : 0.9)];
});
} else {
PHFetchResult<PHAsset *> *assets = [PHAsset fetchAssetsWithLocalIdentifiers:[NSArray arrayWithObject:localImage] options:nil];
if (![assets firstObject])
return;
PHAsset *asset = [assets firstObject];
PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init];
options.synchronous = TRUE;
[[PHImageManager defaultManager] requestImageForAsset:asset targetSize:PHImageManagerMaximumSize contentMode:PHImageContentModeDefault options:options
resultHandler:^(UIImage *image, NSDictionary * info) {
if (image) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, (unsigned long)NULL),
^(void) {
[_chatRoomDelegate startImageUpload:img assetId:localImage withQuality:(uploadQuality ? [uploadQuality floatValue] : 0.9)];
});
} else {
LOGE(@"Can't read image");
}
}];
}
} else if(fileName) {
NSString *filePath = [LinphoneManager documentFile:fileName];
[_chatRoomDelegate startFileUpload:[NSData dataWithContentsOfFile:filePath] withUrl:[NSURL URLWithString:filePath]];
}
} else {
[self onDelete];
[self onDelete];
double delayInSeconds = 0.4;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void) {
@ -346,25 +360,19 @@ static const CGFloat CELL_MESSAGE_Y_MARGIN = 52; // 44;
size = [self getMediaMessageSizefromOriginalSize:videoSize withWidth:width];
size.height += CELL_MESSAGE_X_MARGIN;
} else {
NSURL *imageUrl = [NSURL URLWithString:localImage];
__block CGSize originalImageSize = CGSizeMake(0, 0);
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
[LinphoneManager.instance.photoLibrary assetForURL:imageUrl
resultBlock:^(ALAsset *asset) {
originalImageSize = [[asset defaultRepresentation] dimensions];
dispatch_semaphore_signal(sema);
}
failureBlock:^(NSError *error) {
LOGE(@"Can't read image");
dispatch_semaphore_signal(sema);
}];
});
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
size = [self getMediaMessageSizefromOriginalSize:originalImageSize withWidth:width];
//This fixes the image being too small. I think the issue comes form the fact that the display is retina. This should probably be changed in the future.
size.height += CELL_MESSAGE_X_MARGIN;
if (!localImage) {
//We are loading the image
return CGSizeMake(CELL_MIN_WIDTH + CELL_MESSAGE_X_MARGIN, CELL_MIN_HEIGHT + CELL_MESSAGE_Y_MARGIN);
}
PHFetchResult<PHAsset *> *assets = [PHAsset fetchAssetsWithLocalIdentifiers:[NSArray arrayWithObject:localImage] options:nil];
if (![assets firstObject]) {
return CGSizeMake(CELL_MIN_WIDTH, CELL_MIN_HEIGHT);
}
PHAsset *asset = [assets firstObject];
CGSize originalImageSize = CGSizeMake([asset pixelWidth], [asset pixelHeight]);
size = [self getMediaMessageSizefromOriginalSize:originalImageSize withWidth:width];
//This fixes the image being too small. I think the issue comes form the fact that the display is retina. This should probably be changed in the future.
size.height += CELL_MESSAGE_X_MARGIN;
}
}

View file

@ -28,7 +28,7 @@
- (BOOL)isLoading;
- (void)stopLoading;
@property(nonatomic, strong) ALAsset *fullImageUrl;
@property(nonatomic, strong) PHAsset *asset;
@property (nonatomic, readonly) IBOutlet UIActivityIndicatorView *waitIndicatorView;
@end

View file

@ -104,7 +104,7 @@
#pragma mark - Image picker delegate
- (void)imagePickerDelegateImage:(UIImage *)image info:(NSDictionary *)info {
- (void)imagePickerDelegateImage:(UIImage *)image info:(NSString *)phAssetId {
// When getting image from the camera, it may be 90° rotated due to orientation
// (image.imageOrientation = UIImageOrientationRight). Just rotate it to be face up.
if (image.imageOrientation != UIImageOrientationUp) {
@ -113,6 +113,10 @@
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
[LinphoneManager.instance lpConfigSetString:phAssetId forKey:@"avatar"];
_avatarImage.image = [LinphoneUtils selfAvatar];
[LinphoneManager.instance loadAvatar];
// Dismiss popover on iPad
if (IPAD) {
@ -120,27 +124,6 @@
} else {
[PhoneMainView.instance.mainViewController hideSideMenu:NO];
}
NSURL *url = [info valueForKey:UIImagePickerControllerReferenceURL];
// taken from camera, must be saved to device first
if (!url) {
[LinphoneManager.instance.photoLibrary
writeImageToSavedPhotosAlbum:image.CGImage
orientation:(ALAssetOrientation)[image imageOrientation]
completionBlock:^(NSURL *assetURL, NSError *error) {
if (error) {
LOGE(@"Cannot save image data downloaded [%@]", [error localizedDescription]);
} else {
LOGI(@"Image saved to [%@]", [assetURL absoluteString]);
}
[LinphoneManager.instance lpConfigSetString:assetURL.absoluteString forKey:@"avatar"];
_avatarImage.image = [LinphoneUtils selfAvatar];
}];
} else {
[LinphoneManager.instance lpConfigSetString:url.absoluteString forKey:@"avatar"];
_avatarImage.image = [LinphoneUtils selfAvatar];
}
}
@end

View file

@ -12,7 +12,7 @@
@interface FileTransferDelegate : NSObject
- (void)upload:(UIImage *)image withURL:(NSURL *)url forChatRoom:(LinphoneChatRoom *)chatRoom withQuality:(float)quality;
- (void)upload:(UIImage *)image withassetId:(NSString *)phAssetId forChatRoom:(LinphoneChatRoom *)chatRoom withQuality:(float)quality;
- (void)uploadFile:(NSData *)data forChatRoom:(LinphoneChatRoom *)chatRoom withUrl:(NSURL *)url;
- (void)cancel;
- (BOOL)download:(LinphoneChatMessage *)message;

View file

@ -75,44 +75,47 @@ static void linphone_iphone_file_transfer_recv(LinphoneChatMessage *message, con
// chat bubble is aware of the fact that image is being saved to device
[LinphoneManager setValueInMessageAppData:@"saving..." forKey:@"localimage" inMessage:message];
[LinphoneManager.instance.photoLibrary
writeImageToSavedPhotosAlbum:image.CGImage
orientation:(ALAssetOrientation)[image imageOrientation]
completionBlock:^(NSURL *assetURL, NSError *error) {
if (error) {
LOGE(@"Cannot save image data downloaded [%@]", [error localizedDescription]);
[LinphoneManager setValueInMessageAppData:nil forKey:@"localimage" inMessage:message];
UIAlertController *errView = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Transfer error", nil)
message:NSLocalizedString(@"Cannot write image to photo library",
nil)
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:@"OK"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {}];
[errView addAction:defaultAction];
[PhoneMainView.instance presentViewController:errView animated:YES completion:nil];
} else {
LOGI(@"Image saved to [%@]", [assetURL absoluteString]);
[LinphoneManager setValueInMessageAppData:[assetURL absoluteString]
forKey:@"localimage"
inMessage:message];
}
[NSNotificationCenter.defaultCenter
postNotificationName:kLinphoneFileTransferRecvUpdate
object:thiz
userInfo:@{
@"state" : @(LinphoneChatMessageStateDelivered), // we dont want to
// trigger
// FileTransferDone here
@"image" : image,
@"progress" : @(1.f),
}];
[thiz stopAndDestroy];
CFRelease((__bridge CFTypeRef)thiz);
__block PHObjectPlaceholder *placeHolder;
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
PHAssetCreationRequest *request = [PHAssetCreationRequest creationRequestForAssetFromImage:image];
placeHolder = [request placeholderForCreatedAsset];
} completionHandler:^(BOOL success, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (error) {
LOGE(@"Cannot save image data downloaded [%@]", [error localizedDescription]);
[LinphoneManager setValueInMessageAppData:nil forKey:@"localimage" inMessage:message];
UIAlertController *errView = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Transfer error", nil)
message:NSLocalizedString(@"Cannot write image to photo library",
nil)
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:@"OK"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {}];
[errView addAction:defaultAction];
[PhoneMainView.instance presentViewController:errView animated:YES completion:nil];
} else {
LOGI(@"Image saved to [%@]", [placeHolder localIdentifier]);
[LinphoneManager setValueInMessageAppData:[placeHolder localIdentifier]
forKey:@"localimage"
inMessage:message];
}
[NSNotificationCenter.defaultCenter
postNotificationName:kLinphoneFileTransferRecvUpdate
object:thiz
userInfo:@{
@"state" : @(LinphoneChatMessageStateDelivered), // we dont want to
// trigger
// FileTransferDone here
@"image" : image,
@"progress" : @(1.f),
}];
[thiz stopAndDestroy];
CFRelease((__bridge CFTypeRef)thiz);
});
}];
} else {
[[LinphoneManager.instance fileTransferDelegates] removeObject:thiz];
@ -224,10 +227,10 @@ static LinphoneBuffer *linphone_iphone_file_transfer_send(LinphoneChatMessage *m
}
}
- (void)upload:(UIImage *)image withURL:(NSURL *)url forChatRoom:(LinphoneChatRoom *)chatRoom withQuality:(float)quality {
- (void)upload:(UIImage *)image withassetId:(NSString *)phAssetId forChatRoom:(LinphoneChatRoom *)chatRoom withQuality:(float)quality {
NSString *name = [NSString stringWithFormat:@"%li-%f.jpg", (long)image.hash, [NSDate timeIntervalSinceReferenceDate]];
if (url)
[self uploadData:UIImageJPEGRepresentation(image, quality) forChatRoom:chatRoom type:@"image" subtype:@"jpeg" name:name key:@"localimage" keyData:[url absoluteString] qualityData:[NSNumber numberWithFloat:quality]];
if (phAssetId)
[self uploadData:UIImageJPEGRepresentation(image, quality) forChatRoom:chatRoom type:@"image" subtype:@"jpeg" name:name key:@"localimage" keyData:phAssetId qualityData:[NSNumber numberWithFloat:quality]];
else
[self uploadData:UIImageJPEGRepresentation(image, quality) forChatRoom:chatRoom type:@"image" subtype:@"jpeg" name:name key:@"localimage" keyData:nil qualityData:nil];
}

View file

@ -102,6 +102,16 @@ typedef enum {
@end
@interface UIImage (ResizeAndThumbnail)
+ (UIImage *)UIImageThumbnail:(UIImage *)image thumbSize:(CGFloat) tbSize;
+ (UIImage *)UIImageResize:(UIImage *)image toSize:(CGSize) newSize;
+ (CGImageRef)resizeCGImage:(CGImageRef)image toWidth:(int)width andHeight:(int)height;
@end
/* Use that macro when you want to invoke a custom initialisation method on your class,
whatever is using it (xib, source code, etc., tableview cell) */
#define INIT_WITH_COMMON_C \

View file

@ -32,36 +32,10 @@
@implementation LinphoneUtils
+ (BOOL)hasSelfAvatar {
return [NSURL URLWithString:[LinphoneManager.instance lpConfigStringForKey:@"avatar"]] != nil;
return [LinphoneManager.instance lpConfigStringForKey:@"avatar"] != nil;
}
+ (UIImage *)selfAvatar {
NSURL *url = [NSURL URLWithString:[LinphoneManager.instance lpConfigStringForKey:@"avatar"]];
__block UIImage *ret = nil;
if (url) {
__block NSConditionLock *photoLock = [[NSConditionLock alloc] initWithCondition:1];
// load avatar synchronously so that we can return UIIMage* directly - since we are
// only using thumbnail, it must be pretty fast to fetch even without cache.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[LinphoneManager.instance.photoLibrary assetForURL:url
resultBlock:^(ALAsset *asset) {
ret = [[UIImage alloc] initWithCGImage:[asset thumbnail]];
[photoLock lock];
[photoLock unlockWithCondition:0];
}
failureBlock:^(NSError *error) {
LOGE(@"Can't read avatar");
[photoLock lock];
[photoLock unlockWithCondition:0];
}];
});
[photoLock lockWhenCondition:0];
[photoLock unlock];
}
if (!ret) {
ret = [UIImage imageNamed:@"avatar.png"];
}
return ret;
return [LinphoneManager.instance avatar];
}
+ (NSString *)durationToString:(int)duration {
@ -463,47 +437,42 @@
}
+ (LinphoneAddress *)normalizeSipOrPhoneAddress:(NSString *)value {
if (!value || [value isEqualToString:@""]) {
return NULL;
}
LinphoneProxyConfig *cfg = linphone_core_get_default_proxy_config(LC);
const char *normvalue;
if (linphone_proxy_config_is_phone_number(cfg, value.UTF8String)) {
normvalue = linphone_proxy_config_normalize_phone_number(cfg, value.UTF8String);
} else {
normvalue = value.UTF8String;
}
LinphoneAddress *addr = linphone_proxy_config_normalize_sip_uri(cfg, normvalue);
// first try to find a friend with the given address
Contact *c = [FastAddressBook getContactWithAddress:addr];
if (c && c.friend) {
LinphoneFriend *f = c.friend;
const LinphonePresenceModel *m = f ? linphone_friend_get_presence_model_for_uri_or_tel(f, value.UTF8String) : NULL;
const char *contact = m ? linphone_presence_model_get_contact(m) : NULL;
if (contact) {
LinphoneAddress *contact_addr = linphone_address_new(contact);
if (contact_addr) {
linphone_address_destroy(addr);
return contact_addr;
}
}
}
// since user wants to escape plus, we assume it expects to have phone
// numbers by default
if (addr) {
if (cfg || (linphone_proxy_config_get_dial_escape_plus(cfg))) {
if (linphone_proxy_config_is_phone_number(cfg, normvalue)) {
linphone_address_set_username(addr, normvalue);
}
} else {
if (linphone_proxy_config_is_phone_number(cfg, value.UTF8String)) {
linphone_address_set_username(addr, value.UTF8String);
}
}
}
return addr;
if (!value || [value isEqualToString:@""])
return NULL;
LinphoneProxyConfig *cfg = linphone_core_get_default_proxy_config(LC);
const char *normvalue;
normvalue = linphone_proxy_config_is_phone_number(cfg, value.UTF8String)
? linphone_proxy_config_normalize_phone_number(cfg, value.UTF8String)
: value.UTF8String;
LinphoneAddress *addr = linphone_proxy_config_normalize_sip_uri(cfg, normvalue);
// first try to find a friend with the given address
Contact *c = [FastAddressBook getContactWithAddress:addr];
if (c && c.friend) {
LinphoneFriend *f = c.friend;
const LinphonePresenceModel *m = f
? linphone_friend_get_presence_model_for_uri_or_tel(f, value.UTF8String)
: NULL;
const char *contact = m ? linphone_presence_model_get_contact(m) : NULL;
if (contact) {
LinphoneAddress *contact_addr = linphone_address_new(contact);
if (contact_addr) {
linphone_address_destroy(addr);
return contact_addr;
}
}
}
// since user wants to escape plus, we assume it expects to have phone
// numbers by default
if (addr && cfg) {
const char *username = linphone_proxy_config_get_dial_escape_plus(cfg) ? normvalue : value.UTF8String;
if (linphone_proxy_config_is_phone_number(cfg, username))
linphone_address_set_username(addr, username);
}
return addr;
}
@end
@ -782,3 +751,91 @@
}
@end
@implementation UIImage (ResizeAndThumbnail)
+ (UIImage *)UIImageThumbnail:(UIImage *)image thumbSize:(CGFloat) tbSize {
// Create a thumbnail version of the image for the event object.
CGSize size = image.size;
CGSize croppedSize;
CGFloat offsetX = 0.0;
CGFloat offsetY = 0.0;
CGFloat actualTbSize = MAX(tbSize, MAX(size.height, size.width));
// check the size of the image, we want to make it
// a square with sides the size of the smallest end
if (size.width > size.height) {
offsetX = (size.height - size.width) / 2;
croppedSize = CGSizeMake(size.height, size.height);
} else {
offsetY = (size.width - size.height) / 2;
croppedSize = CGSizeMake(size.width, size.width);
}
// Crop the image before resize
CGRect clippedRect = CGRectMake(offsetX * -1,
offsetY * -1,
croppedSize.width,
croppedSize.height);
CGImageRef imageRef = CGImageCreateWithImageInRect([image CGImage],
clippedRect);
UIImage *cropped = [UIImage imageWithCGImage:imageRef];
CGImageRelease(imageRef);
// Done cropping
// Resize the image
CGRect rect = CGRectMake(0, 0, actualTbSize, actualTbSize);
UIGraphicsBeginImageContext(rect.size);
[cropped drawInRect:rect];
UIImage *thumbnail = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// Done Resizing
return thumbnail;
}
+ (UIImage *)UIImageResize:(UIImage *)image toSize:(CGSize) newSize {
CGImageRef newImage = [image CGImage];
CGSize originalSize = [image size];
float originalAspectRatio = originalSize.width / originalSize.height;
// We resize in width and crop in height
if (originalSize.width > newSize.width) {
int height = newSize.width / originalAspectRatio;
newImage = [UIImage resizeCGImage:newImage toWidth:newSize.width andHeight:height];
originalSize.height = height;
}
CGRect cropRect = CGRectMake(0, 0, newSize.width, newSize.height);
if (newSize.height < originalSize.height) cropRect.origin.y = (originalSize.height - newSize.height)/2;
newImage = CGImageCreateWithImageInRect(newImage, cropRect);
UIImage *cropped = [UIImage imageWithCGImage:newImage];
CGImageRelease(newImage);
return cropped;
}
+ (CGImageRef)resizeCGImage:(CGImageRef)image toWidth:(int)width andHeight:(int)height {
// create context, keeping original image properties
CGColorSpaceRef colorspace = CGImageGetColorSpace(image);
CGContextRef context = CGBitmapContextCreate(NULL, width, height,
CGImageGetBitsPerComponent(image),
CGImageGetBytesPerRow(image),
colorspace,
CGImageGetAlphaInfo(image));
CGColorSpaceRelease(colorspace);
if(context == NULL)
return nil;
// draw image to context (resizing it)
CGContextDrawImage(context, CGRectMake(0, 0, width, height), image);
// extract resulting image from context
CGImageRef imgRef = CGBitmapContextCreateImage(context);
CGContextRelease(context);
return imgRef;
}
@end