Room details: support video attachment

This commit is contained in:
giomfo 2014-11-17 17:32:46 +01:00
parent f859789d0f
commit 579f0dd503
7 changed files with 902 additions and 1709 deletions

File diff suppressed because it is too large Load diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -61,7 +61,14 @@
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
</textView>
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="mvK-ez-meg" userLabel="Attachment View">
<rect key="frame" x="56" y="25" width="190" height="20"/>
<rect key="frame" x="59" y="28" width="184" height="14"/>
</imageView>
<imageView hidden="YES" userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="play.png" translatesAutoresizingMaskIntoConstraints="NO" id="vF4-rq-4Rn">
<rect key="frame" x="135" y="19" width="32" height="32"/>
<constraints>
<constraint firstAttribute="height" constant="32" id="JvT-k9-GoK"/>
<constraint firstAttribute="width" constant="32" id="sTU-9e-QiN"/>
</constraints>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="User name:" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="8" translatesAutoresizingMaskIntoConstraints="NO" id="egJ-aY-QVW">
<rect key="frame" x="51" y="3" width="200" height="20"/>
@ -97,14 +104,16 @@
<constraints>
<constraint firstAttribute="bottom" secondItem="J5R-Mh-3hV" secondAttribute="bottom" id="662-Ze-6ia"/>
<constraint firstItem="Ttt-0P-dQW" firstAttribute="leading" secondItem="egJ-aY-QVW" secondAttribute="trailing" id="6L3-Pz-zbG"/>
<constraint firstItem="J5R-Mh-3hV" firstAttribute="leading" secondItem="mvK-ez-meg" secondAttribute="leading" constant="-5" id="Cbe-us-CeX"/>
<constraint firstItem="J5R-Mh-3hV" firstAttribute="leading" secondItem="mvK-ez-meg" secondAttribute="leading" constant="-8" id="Cbe-us-CeX"/>
<constraint firstAttribute="bottom" secondItem="Ttt-0P-dQW" secondAttribute="bottom" id="GAU-J5-ciT"/>
<constraint firstItem="yRH-kt-si3" firstAttribute="leading" secondItem="J5R-Mh-3hV" secondAttribute="trailing" id="JMP-AW-CDI"/>
<constraint firstItem="egJ-aY-QVW" firstAttribute="top" secondItem="iJp-sA-hG6" secondAttribute="top" constant="3" id="N8f-0n-ObR"/>
<constraint firstItem="Ttt-0P-dQW" firstAttribute="top" secondItem="iJp-sA-hG6" secondAttribute="top" id="Ptt-qa-Cg4"/>
<constraint firstItem="J5R-Mh-3hV" firstAttribute="trailing" secondItem="mvK-ez-meg" secondAttribute="trailing" constant="5" id="Zdd-F0-OAU"/>
<constraint firstItem="mvK-ez-meg" firstAttribute="top" secondItem="J5R-Mh-3hV" secondAttribute="top" constant="5" id="d5n-ha-SuV"/>
<constraint firstItem="mvK-ez-meg" firstAttribute="bottom" secondItem="J5R-Mh-3hV" secondAttribute="bottom" constant="-5" id="eaa-XD-uyr"/>
<constraint firstItem="mvK-ez-meg" firstAttribute="centerY" secondItem="vF4-rq-4Rn" secondAttribute="centerY" id="ROj-jF-hIQ"/>
<constraint firstItem="mvK-ez-meg" firstAttribute="centerX" secondItem="vF4-rq-4Rn" secondAttribute="centerX" id="VwV-of-MSN"/>
<constraint firstItem="J5R-Mh-3hV" firstAttribute="trailing" secondItem="mvK-ez-meg" secondAttribute="trailing" constant="8" id="Zdd-F0-OAU"/>
<constraint firstItem="mvK-ez-meg" firstAttribute="top" secondItem="J5R-Mh-3hV" secondAttribute="top" constant="8" id="d5n-ha-SuV"/>
<constraint firstItem="mvK-ez-meg" firstAttribute="bottom" secondItem="J5R-Mh-3hV" secondAttribute="bottom" constant="-8" id="eaa-XD-uyr"/>
<constraint firstItem="yRH-kt-si3" firstAttribute="top" secondItem="iJp-sA-hG6" secondAttribute="top" constant="25" id="epA-UD-55M"/>
<constraint firstItem="uhu-R0-9NH" firstAttribute="leading" secondItem="iJp-sA-hG6" secondAttribute="leading" constant="8" id="fNV-Tp-p31"/>
<constraint firstAttribute="trailing" secondItem="Ttt-0P-dQW" secondAttribute="trailing" id="mJg-Df-CWQ"/>
@ -127,6 +136,7 @@
<outlet property="msgTextViewTopConstraint" destination="rJt-w3-D8g" id="6Um-o1-J08"/>
<outlet property="msgTextViewWidthConstraint" destination="rsq-bW-mGt" id="gt4-3A-412"/>
<outlet property="pictureView" destination="uhu-R0-9NH" id="59O-If-m7H"/>
<outlet property="playIconView" destination="vF4-rq-4Rn" id="G3R-52-GmA"/>
<outlet property="userNameLabel" destination="egJ-aY-QVW" id="IWg-7t-5Vp"/>
</connections>
</tableViewCell>
@ -146,7 +156,14 @@
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
</textView>
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="QZT-V8-yqJ" userLabel="Attachment View">
<rect key="frame" x="354" y="25" width="190" height="20"/>
<rect key="frame" x="357" y="28" width="184" height="14"/>
</imageView>
<imageView hidden="YES" userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="play.png" translatesAutoresizingMaskIntoConstraints="NO" id="0Bl-Sv-Q2H">
<rect key="frame" x="433" y="19" width="32" height="32"/>
<constraints>
<constraint firstAttribute="height" constant="32" id="VbS-GC-vtz"/>
<constraint firstAttribute="width" constant="32" id="faJ-bg-xPV"/>
</constraints>
</imageView>
<activityIndicatorView hidden="YES" opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" hidesWhenStopped="YES" style="gray" translatesAutoresizingMaskIntoConstraints="NO" id="Pq8-lB-cZM">
<rect key="frame" x="439" y="25" width="20" height="20"/>
@ -193,21 +210,23 @@
<constraint firstItem="2lD-F0-Haf" firstAttribute="top" secondItem="5tf-BC-9Ed" secondAttribute="top" constant="25" id="7Ee-H6-Wsd"/>
<constraint firstItem="7qn-gi-w7s" firstAttribute="leading" secondItem="fNQ-DX-U8F" secondAttribute="trailing" id="AH0-j0-oAW"/>
<constraint firstItem="2lD-F0-Haf" firstAttribute="leading" secondItem="5tf-BC-9Ed" secondAttribute="leading" constant="8" id="CX9-TJ-kJ5"/>
<constraint firstItem="QZT-V8-yqJ" firstAttribute="leading" secondItem="7qn-gi-w7s" secondAttribute="leading" constant="5" id="IVl-I8-Dzq"/>
<constraint firstItem="QZT-V8-yqJ" firstAttribute="leading" secondItem="7qn-gi-w7s" secondAttribute="leading" constant="8" id="IVl-I8-Dzq"/>
<constraint firstAttribute="bottom" secondItem="7qn-gi-w7s" secondAttribute="bottom" id="KPt-Vo-ntg"/>
<constraint firstItem="alD-cg-uMl" firstAttribute="top" secondItem="5tf-BC-9Ed" secondAttribute="top" constant="14" id="M1S-HJ-o3b"/>
<constraint firstItem="fNQ-DX-U8F" firstAttribute="leading" secondItem="5tf-BC-9Ed" secondAttribute="leading" id="MqK-3Z-lp5"/>
<constraint firstAttribute="bottom" secondItem="fNQ-DX-U8F" secondAttribute="bottom" id="NUK-Kq-ITl"/>
<constraint firstItem="mks-jh-AiZ" firstAttribute="top" secondItem="5tf-BC-9Ed" secondAttribute="top" constant="5" id="SSl-4u-03L"/>
<constraint firstItem="QZT-V8-yqJ" firstAttribute="top" secondItem="7qn-gi-w7s" secondAttribute="top" constant="5" id="U4f-kJ-Kgb"/>
<constraint firstItem="QZT-V8-yqJ" firstAttribute="top" secondItem="7qn-gi-w7s" secondAttribute="top" constant="8" id="U4f-kJ-Kgb"/>
<constraint firstItem="QZT-V8-yqJ" firstAttribute="centerX" secondItem="0Bl-Sv-Q2H" secondAttribute="centerX" id="Uvh-A6-5G9"/>
<constraint firstItem="7qn-gi-w7s" firstAttribute="leading" secondItem="alD-cg-uMl" secondAttribute="trailing" constant="5" id="Vde-Q3-e8O"/>
<constraint firstItem="QZT-V8-yqJ" firstAttribute="centerX" secondItem="Pq8-lB-cZM" secondAttribute="centerX" id="a0M-9u-hMC"/>
<constraint firstItem="QZT-V8-yqJ" firstAttribute="centerY" secondItem="0Bl-Sv-Q2H" secondAttribute="centerY" id="aUE-Bv-XF2"/>
<constraint firstItem="mks-jh-AiZ" firstAttribute="leading" secondItem="7qn-gi-w7s" secondAttribute="trailing" constant="3" id="cCA-xk-XBe"/>
<constraint firstItem="QZT-V8-yqJ" firstAttribute="centerY" secondItem="Pq8-lB-cZM" secondAttribute="centerY" id="ej0-OC-3hJ"/>
<constraint firstItem="7qn-gi-w7s" firstAttribute="top" secondItem="5tf-BC-9Ed" secondAttribute="top" constant="20" id="owD-KZ-snG"/>
<constraint firstItem="QZT-V8-yqJ" firstAttribute="bottom" secondItem="7qn-gi-w7s" secondAttribute="bottom" constant="-5" id="q4h-EM-X4Z"/>
<constraint firstItem="QZT-V8-yqJ" firstAttribute="bottom" secondItem="7qn-gi-w7s" secondAttribute="bottom" constant="-8" id="q4h-EM-X4Z"/>
<constraint firstItem="7qn-gi-w7s" firstAttribute="leading" secondItem="2lD-F0-Haf" secondAttribute="trailing" id="q5r-bz-qP7"/>
<constraint firstItem="QZT-V8-yqJ" firstAttribute="trailing" secondItem="7qn-gi-w7s" secondAttribute="trailing" constant="-5" id="tgM-eJ-BEk"/>
<constraint firstItem="QZT-V8-yqJ" firstAttribute="trailing" secondItem="7qn-gi-w7s" secondAttribute="trailing" constant="-8" id="tgM-eJ-BEk"/>
<constraint firstItem="fNQ-DX-U8F" firstAttribute="top" secondItem="5tf-BC-9Ed" secondAttribute="top" id="wb3-8X-eQH"/>
</constraints>
</tableViewCellContentView>
@ -224,6 +243,7 @@
<outlet property="msgTextViewTopConstraint" destination="owD-KZ-snG" id="oqc-0f-05O"/>
<outlet property="msgTextViewWidthConstraint" destination="a6g-WS-jjN" id="aM9-xQ-hVM"/>
<outlet property="pictureView" destination="mks-jh-AiZ" id="qL1-Kd-oRC"/>
<outlet property="playIconView" destination="0Bl-Sv-Q2H" id="VNa-J3-NuO"/>
<outlet property="unsentLabel" destination="alD-cg-uMl" id="UXm-mh-sux"/>
<outlet property="unsentLabelTopConstraint" destination="M1S-HJ-o3b" id="Geo-hs-Ils"/>
</connections>
@ -1022,6 +1042,7 @@
<resources>
<image name="default-profile.png" width="160" height="160"/>
<image name="logoHighRes.png" width="480" height="204"/>
<image name="play.png" width="64" height="64"/>
<image name="tab_home.ico" width="16" height="16"/>
</resources>
<inferredMetricsTieBreakers>

View file

@ -27,14 +27,16 @@ typedef void (^blockMediaManager_onError)(NSError *error);
+ (id)sharedInstance;
+ (UIImage *)resize:(UIImage *)image toFitInSize:(CGSize)size;
// Load a picture from the local cache or download it if it is not available yet.
// In this second case a mediaLoader reference is returned in order to let the user cancel this action.
+ (id)loadPicture:(NSString*)pictureURL
+ (id)loadPicture:(NSString *)pictureURL
success:(blockMediaManager_onImageReady)success
failure:(blockMediaManager_onError)failure;
+ (void)cancel:(id)mediaLoader;
+ (NSString*)cachePictureWithData:(NSData*)imageData forURL:(NSString *)pictureURL;
+ (NSString *)cachePictureWithData:(NSData *)imageData forURL:(NSString *)pictureURL;
+ (void)clearCacheForURL:(NSString *)mediaURL;
+ (void)clearCache;

View file

@ -34,6 +34,8 @@ static MediaManager *sharedMediaManager = nil;
}
@end
#pragma mark - MediaLoader
@implementation MediaLoader
- (void)downloadPicture:(NSString*)pictureURL
@ -66,7 +68,7 @@ static MediaManager *sharedMediaManager = nil;
[self cancel];
}
#pragma mark - NSURLConnectionDelegate
#pragma mark -
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
NSLog(@"ERROR: picture download failed: %@, %@", error, mediaURL);
@ -104,6 +106,8 @@ static MediaManager *sharedMediaManager = nil;
@end
#pragma mark - MediaManager
@implementation MediaManager
+ (id)sharedInstance {
@ -114,6 +118,44 @@ static MediaManager *sharedMediaManager = nil;
return sharedMediaManager;
}
+ (UIImage *)resize:(UIImage *)image toFitInSize:(CGSize)size {
UIImage *resizedImage = image;
// Check whether resize is required
if (size.width && size.height) {
CGFloat width = image.size.width;
CGFloat height = image.size.height;
if (width > size.width) {
height = (height * size.width) / width;
height = floorf(height / 2) * 2;
width = size.width;
}
if (height > size.height) {
width = (width * size.height) / height;
width = floorf(width / 2) * 2;
height = size.height;
}
if (width != image.size.width || height != image.size.height) {
// Create the thumbnail
CGSize imageSize = CGSizeMake(width, height);
UIGraphicsBeginImageContext(imageSize);
CGRect thumbnailRect = CGRectMake(0, 0, 0, 0);
thumbnailRect.origin = CGPointMake(0.0,0.0);
thumbnailRect.size.width = imageSize.width;
thumbnailRect.size.height = imageSize.height;
[image drawInRect:thumbnailRect];
resizedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
}
return resizedImage;
}
// Load a picture from the local cache or download it if it is not available yet.
// In this second case a mediaLoader reference is returned in order to let the user cancel this action.
+ (id)loadPicture:(NSString*)pictureURL

View file

@ -21,6 +21,7 @@
@interface RoomMessageTableCell : CustomTableViewCell
@property (weak, nonatomic) IBOutlet UITextView *messageTextView;
@property (strong, nonatomic) IBOutlet UIImageView *attachmentView;
@property (strong, nonatomic) IBOutlet UIImageView *playIconView;
@property (weak, nonatomic) IBOutlet UILabel *dateTimeLabel;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *msgTextViewWidthConstraint;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *msgTextViewTopConstraint;

View file

@ -15,6 +15,8 @@
*/
#import <MobileCoreServices/MobileCoreServices.h>
#import <MediaPlayer/MediaPlayer.h>
#import "RoomViewController.h"
#import "RoomMessageTableCell.h"
#import "RoomMemberTableCell.h"
@ -25,6 +27,8 @@
#import "MediaManager.h"
#define UPLOAD_FILE_SIZE 5000000
#define ROOM_MESSAGE_CELL_MAX_TEXTVIEW_WIDTH 200
#define ROOM_MESSAGE_CELL_TEXTVIEW_TOP_CONST_DEFAULT 10
@ -33,7 +37,7 @@
#define ROOM_MESSAGE_CELL_TEXTVIEW_BOTTOM_CONST_DEFAULT 0
#define ROOM_MESSAGE_CELL_TEXTVIEW_BOTTOM_CONST_GROUPED_CELL (-5)
#define ROOM_MESSAGE_CELL_IMAGE_MARGIN 5
#define ROOM_MESSAGE_CELL_IMAGE_MARGIN 8
NSString *const kCmdChangeDisplayName = @"/nick";
NSString *const kCmdEmote = @"/me";
@ -770,6 +774,7 @@ NSString *const kFailedEventId = @"failedEventId";
if ([mxHandler isAttachment:mxEvent]) {
cell.attachmentView.hidden = NO;
cell.playIconView.hidden = YES;
cell.messageTextView.text = nil; // Note: Text view is used as attachment background view
CGSize contentSize = [self attachmentContentSize:mxEvent];
if (!contentSize.width || !contentSize.height) {
@ -797,12 +802,16 @@ NSString *const kFailedEventId = @"failedEventId";
}
NSString *msgtype = mxEvent.content[@"msgtype"];
if ([msgtype isEqualToString:kMXMessageTypeImage]) {
if ([msgtype isEqualToString:kMXMessageTypeImage] || [msgtype isEqualToString:kMXMessageTypeVideo]) {
NSString *url = mxEvent.content[@"thumbnail_url"];
if (url == nil) {
url = mxEvent.content[@"url"];
}
cell.attachedImageURL = url;
if ([msgtype isEqualToString:kMXMessageTypeVideo]) {
cell.playIconView.hidden = NO;
}
} else {
cell.attachedImageURL = nil;
}
@ -813,6 +822,7 @@ NSString *const kFailedEventId = @"failedEventId";
// Cancel potential attachment loading
cell.attachedImageURL = nil;
cell.attachmentView.hidden = YES;
cell.playIconView.hidden = YES;
NSString *displayText = [mxHandler displayTextFor:mxEvent inSubtitleMode:NO];
// Update text color according to text content
@ -847,7 +857,7 @@ NSString *const kFailedEventId = @"failedEventId";
- (CGSize)attachmentContentSize:(MXEvent*)mxEvent {
CGSize contentSize;
NSString *msgtype = mxEvent.content[@"msgtype"];
if ([msgtype isEqualToString:kMXMessageTypeImage]) {
if ([msgtype isEqualToString:kMXMessageTypeImage] || [msgtype isEqualToString:kMXMessageTypeVideo]) {
CGFloat width, height;
width = height = 0;
NSDictionary *thumbInfo = mxEvent.content[@"thumbnail_info"];
@ -1208,20 +1218,7 @@ NSString *const kFailedEventId = @"failedEventId";
}
}
} failure:^(NSError *error) {
NSLog(@"Failed to send message (%@): %@", mxEvent.description, error);
// Update the temporary event with the failed event id
NSUInteger index = messages.count;
while (index--) {
MXEvent *mxEvent = [messages objectAtIndex:index];
if ([mxEvent.eventId isEqualToString:localEventId]) {
mxEvent.eventId = kFailedEventId;
// Refresh table display
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:0];
[self.messagesTableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
[self scrollToBottomAnimated:YES];
break;
}
}
[self handleError:error forLocalEventId:localEventId];
}];
}
}
@ -1238,6 +1235,72 @@ NSString *const kFailedEventId = @"failedEventId";
[self postMessage:@{@"msgtype":msgType, @"body":msgTxt} withLocalEventId:nil];
}
- (NSString*)addLocalEventForAttachedImage:(UIImage*)image {
// Create a temporary event to displayed outgoing message (local echo)
NSString *localEventId = [NSString stringWithFormat:@"%@%@", kLocalEchoEventIdPrefix, [[NSProcessInfo processInfo] globallyUniqueString]];
MXEvent *mxEvent = [[MXEvent alloc] init];
mxEvent.roomId = self.roomId;
mxEvent.eventId = localEventId;
mxEvent.eventType = MXEventTypeRoomMessage;
mxEvent.type = kMXEventTypeStringRoomMessage;
// We store temporarily the image in cache, use the localId to build temporary url
NSString *dummyURL = [NSString stringWithFormat:@"%@%@", kMediaManagerPrefixForDummyURL, localEventId];
NSData *imageData = UIImageJPEGRepresentation(image, 0.5);
[MediaManager cachePictureWithData:imageData forURL:dummyURL];
if (tmpCachedAttachments == nil) {
tmpCachedAttachments = [NSMutableArray array];
}
[tmpCachedAttachments addObject:dummyURL];
NSMutableDictionary *thumbnailInfo = [[NSMutableDictionary alloc] init];
[thumbnailInfo setValue:@"image/jpeg" forKey:@"mimetype"];
[thumbnailInfo setValue:[NSNumber numberWithUnsignedInteger:(NSUInteger)image.size.width] forKey:@"w"];
[thumbnailInfo setValue:[NSNumber numberWithUnsignedInteger:(NSUInteger)image.size.height] forKey:@"h"];
[thumbnailInfo setValue:[NSNumber numberWithUnsignedInteger:imageData.length] forKey:@"size"];
mxEvent.content = @{@"msgtype":@"m.image", @"thumbnail_info":thumbnailInfo, @"thumbnail_url":dummyURL};
mxEvent.userId = [MatrixHandler sharedHandler].userId;
// Update table sources
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:messages.count inSection:0];
[messages addObject:mxEvent];
// Refresh table display (Disable animation during cells insertion to prevent flickering)
[UIView setAnimationsEnabled:NO];
[self.messagesTableView beginUpdates];
if (indexPath.row > 0) {
NSIndexPath *prevIndexPath = [NSIndexPath indexPathForRow:indexPath.row - 1 inSection:0];
[self.messagesTableView reloadRowsAtIndexPaths:@[prevIndexPath] withRowAnimation:UITableViewRowAnimationNone];
}
[self.messagesTableView insertRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
[self.messagesTableView endUpdates];
[UIView setAnimationsEnabled:YES];
[self scrollToBottomAnimated:NO];
return localEventId;
}
- (void)handleError:(NSError *)error forLocalEventId:(NSString *)localEventId {
NSLog(@"Post message failed: %@", error);
if (error) {
// Alert user
[[AppDelegate theDelegate] showErrorAsAlert:error];
}
// Update the temporary event with this local event id
NSUInteger index = messages.count;
while (index--) {
MXEvent *mxEvent = [messages objectAtIndex:index];
if ([mxEvent.eventId isEqualToString:localEventId]) {
NSLog(@"Posted event: %@", mxEvent.description);
mxEvent.eventId = kFailedEventId;
// Refresh table display
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:0];
[self.messagesTableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
[self scrollToBottomAnimated:YES];
break;
}
}
}
- (BOOL)isIRCStyleCommand:(NSString*)text{
// Check whether the provided text may be an IRC-style command
if ([text hasPrefix:@"/"] == NO || [text hasPrefix:@"//"] == YES) {
@ -1407,75 +1470,129 @@ NSString *const kFailedEventId = @"failedEventId";
if ([mediaType isEqualToString:(NSString *)kUTTypeImage]) {
UIImage *selectedImage = [info objectForKey:UIImagePickerControllerOriginalImage];
if (selectedImage) {
// Create a temporary event to displayed outgoing message (local echo)
NSString *localEventId = [NSString stringWithFormat:@"%@%@", kLocalEchoEventIdPrefix, [[NSProcessInfo processInfo] globallyUniqueString]];
MXEvent *mxEvent = [[MXEvent alloc] init];
mxEvent.roomId = self.roomId;
mxEvent.eventId = localEventId;
mxEvent.eventType = MXEventTypeRoomMessage;
mxEvent.type = kMXEventTypeStringRoomMessage;
// We store temporarily the selected image in cache, use the localId to build temporary url
NSString *dummyURL = [NSString stringWithFormat:@"%@%@", kMediaManagerPrefixForDummyURL, localEventId];
NSData *selectedImageData = UIImageJPEGRepresentation(selectedImage, 0.5);
[MediaManager cachePictureWithData:selectedImageData forURL:dummyURL];
if (tmpCachedAttachments == nil) {
tmpCachedAttachments = [NSMutableArray array];
}
[tmpCachedAttachments addObject:dummyURL];
NSMutableDictionary *thumbnailInfo = [[NSMutableDictionary alloc] init];
[thumbnailInfo setValue:@"image/jpeg" forKey:@"mimetype"];
[thumbnailInfo setValue:[NSNumber numberWithUnsignedInteger:(NSUInteger)selectedImage.size.width] forKey:@"w"];
[thumbnailInfo setValue:[NSNumber numberWithUnsignedInteger:(NSUInteger)selectedImage.size.height] forKey:@"h"];
[thumbnailInfo setValue:[NSNumber numberWithUnsignedInteger:selectedImageData.length] forKey:@"size"];
mxEvent.content = @{@"msgtype":@"m.image", @"thumbnail_info":thumbnailInfo, @"thumbnail_url":dummyURL};
mxEvent.userId = [MatrixHandler sharedHandler].userId;
// Update table sources
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:messages.count inSection:0];
[messages addObject:mxEvent];
// Refresh table display (Disable animation during cells insertion to prevent flickering)
[UIView setAnimationsEnabled:NO];
[self.messagesTableView beginUpdates];
if (indexPath.row > 0) {
NSIndexPath *prevIndexPath = [NSIndexPath indexPathForRow:indexPath.row - 1 inSection:0];
[self.messagesTableView reloadRowsAtIndexPaths:@[prevIndexPath] withRowAnimation:UITableViewRowAnimationNone];
}
[self.messagesTableView insertRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
[self.messagesTableView endUpdates];
[UIView setAnimationsEnabled:YES];
[self scrollToBottomAnimated:NO];
NSString * localEventId = [self addLocalEventForAttachedImage:selectedImage];
// Upload image and its thumbnail
MatrixHandler *mxHandler = [MatrixHandler sharedHandler];
NSUInteger thumbnailSize = ROOM_MESSAGE_CELL_MAX_TEXTVIEW_WIDTH - 5 * ROOM_MESSAGE_CELL_IMAGE_MARGIN;
NSUInteger thumbnailSize = ROOM_MESSAGE_CELL_MAX_TEXTVIEW_WIDTH - 2 * ROOM_MESSAGE_CELL_IMAGE_MARGIN;
[mxHandler.mxRestClient uploadImage:selectedImage thumbnailSize:thumbnailSize timeout:30 success:^(NSDictionary *imageMessage) {
// Send image
[self postMessage:imageMessage withLocalEventId:localEventId];
} failure:^(NSError *error) {
NSLog(@"Failed to upload image: %@", error);
//Alert user
[[AppDelegate theDelegate] showErrorAsAlert:error];
// Update the temporary event with the failed event id
NSUInteger index = messages.count;
while (index--) {
MXEvent *mxEvent = [messages objectAtIndex:index];
if ([mxEvent.eventId isEqualToString:localEventId]) {
mxEvent.eventId = kFailedEventId;
// Refresh table display
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:0];
[self.messagesTableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
[self scrollToBottomAnimated:YES];
break;
}
}
[self handleError:error forLocalEventId:localEventId];
}];
}
} else if ([mediaType isEqualToString:(NSString *)kUTTypeMovie]) {
//TODO
NSURL* selectedVideo = [info objectForKey:UIImagePickerControllerMediaURL];
if (selectedVideo) {
// Create video thumbnail
MPMoviePlayerController* moviePlayerController = [[MPMoviePlayerController alloc] initWithContentURL:selectedVideo];
if (moviePlayerController) {
[moviePlayerController setShouldAutoplay:NO];
UIImage* videoThumbnail = [moviePlayerController thumbnailImageAtTime:(NSTimeInterval)1 timeOption:MPMovieTimeOptionNearestKeyFrame];
[moviePlayerController stop];
moviePlayerController = nil;
if (videoThumbnail) {
// Prepare video thumbnail description
NSUInteger thumbnailSize = ROOM_MESSAGE_CELL_MAX_TEXTVIEW_WIDTH - 2 * ROOM_MESSAGE_CELL_IMAGE_MARGIN;
UIImage *thumbnail = [MediaManager resize:videoThumbnail toFitInSize:CGSizeMake(thumbnailSize, thumbnailSize)];
NSMutableDictionary *thumbnailInfo = [[NSMutableDictionary alloc] init];
[thumbnailInfo setValue:@"image/jpeg" forKey:@"mimetype"];
[thumbnailInfo setValue:[NSNumber numberWithUnsignedInteger:(NSUInteger)thumbnail.size.width] forKey:@"w"];
[thumbnailInfo setValue:[NSNumber numberWithUnsignedInteger:(NSUInteger)thumbnail.size.height] forKey:@"h"];
NSData *thumbnailData = UIImageJPEGRepresentation(thumbnail, 0.9);
[thumbnailInfo setValue:[NSNumber numberWithUnsignedInteger:thumbnailData.length] forKey:@"size"];
// Create the local event displayed during uploading
NSString * localEventId = [self addLocalEventForAttachedImage:thumbnail];
// Upload thumbnail
MatrixHandler *mxHandler = [MatrixHandler sharedHandler];
[mxHandler.mxRestClient uploadContent:thumbnailData mimeType:@"image/jpeg" timeout:30 success:^(NSString *url) {
// Prepare content of attached video
NSMutableDictionary *videoContent = [[NSMutableDictionary alloc] init];
[videoContent setValue:@"m.video" forKey:@"msgtype"];
[videoContent setValue:url forKey:@"thumbnail_url"];
[videoContent setValue:thumbnailInfo forKey:@"thumbnail_info"];
// Convert video container to mp4
AVURLAsset* videoAsset = [AVURLAsset URLAssetWithURL:selectedVideo options:nil];
AVAssetExportSession *exportSession = [AVAssetExportSession exportSessionWithAsset:videoAsset presetName:AVAssetExportPresetMediumQuality];
// Set output URL
NSString * outputFileName = [NSString stringWithFormat:@"%.0f.mp4",[[NSDate date] timeIntervalSince1970]];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *cacheRoot = [paths objectAtIndex:0];
NSURL *tmpVideoLocation = [NSURL fileURLWithPath:[cacheRoot stringByAppendingPathComponent:outputFileName]];
exportSession.outputURL = tmpVideoLocation;
// Check supported output file type
NSArray *supportedFileTypes = exportSession.supportedFileTypes;
if ([supportedFileTypes containsObject:AVFileTypeMPEG4]) {
exportSession.outputFileType = AVFileTypeMPEG4;
[videoContent setValue:@"video/mp4" forKey:@"mimetype"];
} else {
NSLog(@"Unexpected case: MPEG-4 file format is not supported");
// we send QuickTime movie file by default
exportSession.outputFileType = AVFileTypeQuickTimeMovie;
[videoContent setValue:@"video/quicktime" forKey:@"mimetype"];
}
// Export video file and send it
[exportSession exportAsynchronouslyWithCompletionHandler:^{
// Check status
if ([exportSession status] == AVAssetExportSessionStatusCompleted) {
AVURLAsset* asset = [AVURLAsset URLAssetWithURL:tmpVideoLocation
options:[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES],
AVURLAssetPreferPreciseDurationAndTimingKey,
nil]
];
[videoContent setValue:[NSNumber numberWithDouble:(1000 * CMTimeGetSeconds(asset.duration))] forKey:@"duration"];
NSArray *videoTracks = [asset tracksWithMediaType:AVMediaTypeVideo];
if (videoTracks.count > 0) {
AVAssetTrack *videoTrack = [videoTracks objectAtIndex:0];
CGSize videoSize = videoTrack.naturalSize;
[videoContent setValue:[NSNumber numberWithUnsignedInteger:(NSUInteger)videoSize.width] forKey:@"w"];
[videoContent setValue:[NSNumber numberWithUnsignedInteger:(NSUInteger)videoSize.height] forKey:@"h"];
}
// Upload the video
NSData *videoData = [NSData dataWithContentsOfURL:tmpVideoLocation];
[[NSFileManager defaultManager] removeItemAtPath:[tmpVideoLocation path] error:nil];
if (videoData) {
if (videoData.length < UPLOAD_FILE_SIZE) {
[videoContent setValue:[NSNumber numberWithUnsignedInteger:videoData.length] forKey:@"size"];
[mxHandler.mxRestClient uploadContent:videoData mimeType:videoContent[@"mimetype"] timeout:30 success:^(NSString *url) {
[videoContent setValue:url forKey:@"url"];
[videoContent setValue:@"Video" forKey:@"body"];
[self postMessage:videoContent withLocalEventId:localEventId];
} failure:^(NSError *error) {
[self handleError:error forLocalEventId:localEventId];
}];
} else {
NSLog(@"Video is too large");
[self handleError:nil forLocalEventId:localEventId];
}
} else {
NSLog(@"Attach video failed: no data");
[self handleError:nil forLocalEventId:localEventId];
}
}
else {
NSLog(@"Video export failed: %d", [exportSession status]);
// remove tmp file (if any)
[[NSFileManager defaultManager] removeItemAtPath:[tmpVideoLocation path] error:nil];
[self handleError:nil forLocalEventId:localEventId];
}
}];
} failure:^(NSError *error) {
NSLog(@"Video thumbnail upload failed");
[self handleError:error forLocalEventId:localEventId];
}];
}
}
}
}
[self dismissMediaPicker];
}