Room details: Handle local echo for uploaded images

This commit is contained in:
giomfo
2014-11-07 22:58:26 +01:00
parent 317ac3088c
commit 9102b1ff6a
5 changed files with 159 additions and 62 deletions

View File

@@ -125,6 +125,9 @@
<constraint firstAttribute="width" constant="190" id="0gc-hU-Jtc"/>
</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="15" width="20" height="20"/>
</activityIndicatorView>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="default-profile.png" translatesAutoresizingMaskIntoConstraints="NO" id="mks-jh-AiZ">
<rect key="frame" x="552" y="5" width="40" height="40"/>
<constraints>
@@ -150,15 +153,18 @@
<constraint firstItem="alD-cg-uMl" firstAttribute="top" secondItem="5tf-BC-9Ed" secondAttribute="top" constant="14" id="M1S-HJ-o3b"/>
<constraint firstItem="mks-jh-AiZ" firstAttribute="top" secondItem="5tf-BC-9Ed" secondAttribute="top" constant="5" id="SSl-4u-03L"/>
<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="mks-jh-AiZ" firstAttribute="leading" secondItem="QZT-V8-yqJ" secondAttribute="trailing" constant="8" id="a1U-Ar-6ln"/>
<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="top" secondItem="5tf-BC-9Ed" secondAttribute="top" constant="10" id="eSd-gt-RQQ"/>
<constraint firstItem="QZT-V8-yqJ" firstAttribute="centerY" secondItem="Pq8-lB-cZM" secondAttribute="centerY" id="ej0-OC-3hJ"/>
<constraint firstAttribute="bottom" secondItem="QZT-V8-yqJ" secondAttribute="bottom" constant="10" id="lEk-gQ-G3T"/>
<constraint firstItem="7qn-gi-w7s" firstAttribute="top" secondItem="5tf-BC-9Ed" secondAttribute="top" constant="5" id="owD-KZ-snG"/>
</constraints>
</tableViewCellContentView>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<connections>
<outlet property="activityIndicator" destination="Pq8-lB-cZM" id="YBm-xk-m7t"/>
<outlet property="attachmentView" destination="QZT-V8-yqJ" id="Xrj-e5-Yd9"/>
<outlet property="attachmentViewWidthConstraint" destination="0gc-hU-Jtc" id="rpj-98-GpI"/>
<outlet property="messageTextView" destination="7qn-gi-w7s" id="0b2-5P-dqR"/>

View File

@@ -17,6 +17,8 @@
#import <AVFoundation/AVFoundation.h>
#import <UIKit/UIKit.h>
extern NSString *const kMediaManagerPrefixForDummyURL;
// The callback blocks
typedef void (^blockMediaManager_onImageReady)(UIImage *image);
typedef void (^blockMediaManager_onError)(NSError *error);
@@ -34,6 +36,7 @@ typedef void (^blockMediaManager_onError)(NSError *error);
+ (NSString*)cachePictureWithData:(NSData*)imageData forURL:(NSString *)pictureURL;
+ (void)clearCacheForURL:(NSString *)mediaURL;
+ (void)clearCache;
@end

View File

@@ -16,6 +16,8 @@
#import "MediaManager.h"
NSString *const kMediaManagerPrefixForDummyURL = @"dummyUrl-";
static NSString* pictureCachePath = nil;
static NSString *pictureDir = @"picturecache";
@@ -48,8 +50,7 @@ static MediaManager *sharedMediaManager = nil;
downloadConnection = [[NSURLConnection alloc] initWithRequest:[NSURLRequest requestWithURL:url] delegate:self];
}
- (void)cancel
{
- (void)cancel {
// Reset blocks
onImageReady = nil;
onError = nil;
@@ -61,28 +62,25 @@ static MediaManager *sharedMediaManager = nil;
}
}
- (void)dealloc
{
- (void)dealloc {
[self cancel];
}
#pragma mark - NSURLConnectionDelegate
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
NSLog(@"ERROR: picture download failed: %@, %@", error, mediaURL);
if (onError) {
onError (error);
}
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
// Append data
[downloadData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
// CAUTION: Presently only picture are supported
// Set downloaded image
UIImage *image = [UIImage imageWithData:downloadData];
@@ -130,11 +128,16 @@ static MediaManager *sharedMediaManager = nil;
success (image);
}
}
else {
else if ([pictureURL hasPrefix:kMediaManagerPrefixForDummyURL] == NO) {
// Create a media loader to download picture
MediaLoader *mediaLoader = [[MediaLoader alloc] init];
[mediaLoader downloadPicture:pictureURL success:success failure:failure];
ret = mediaLoader;
} else {
NSLog(@"Load tmp picture from cache failed: %@", pictureURL);
if (failure){
failure(nil);
}
}
return ret;
}
@@ -143,8 +146,6 @@ static MediaManager *sharedMediaManager = nil;
[((MediaLoader*)mediaLoader) cancel];
}
#pragma mark - Cache handling
+ (NSString*)cachePictureWithData:(NSData*)imageData forURL:(NSString *)pictureURL {
NSString* filename = [MediaManager getCacheFileNameFor:pictureURL];
@@ -155,6 +156,39 @@ static MediaManager *sharedMediaManager = nil;
}
}
+ (void)clearCacheForURL:(NSString *)mediaURL {
NSString* filename = [MediaManager getCacheFileNameFor:mediaURL];
NSError *error = nil;
if (filename && [[NSFileManager defaultManager] fileExistsAtPath:filename]) {
if (![[NSFileManager defaultManager] removeItemAtPath:filename error:&error]) {
NSLog(@"Fails to delete cached picture: %@", error);
}
}
}
+ (void)clearCache {
NSError *error = nil;
if (!pictureCachePath) {
// compute the path
pictureCachePath = [MediaManager getCachePath];
}
if (pictureCachePath) {
if (![[NSFileManager defaultManager] removeItemAtPath:pictureCachePath error:&error]) {
NSLog(@"Fails to delete picture cache dir : %@", error);
} else {
NSLog(@"Picture cache : deleted !");
}
} else {
NSLog(@"Picture cache does not exist");
}
pictureCachePath = nil;
}
#pragma mark - Cache handling
+ (UIImage*)loadCachePicture:(NSString*)pictureURL {
UIImage* res = nil;
NSString* filename = [MediaManager getCacheFileNameFor:pictureURL];
@@ -192,25 +226,4 @@ static MediaManager *sharedMediaManager = nil;
return [NSString stringWithFormat:@"%@%lu.jpg", baseFileName, (unsigned long)pictureURL.hash];
}
+ (void)clearCache {
NSError *error = nil;
if (!pictureCachePath) {
// compute the path
pictureCachePath = [MediaManager getCachePath];
}
if (pictureCachePath) {
if (![[NSFileManager defaultManager] removeItemAtPath:pictureCachePath error:&error]) {
NSLog(@"Fails to delete picture cache dir : %@", error);
} else {
NSLog(@"Picture cache : deleted !");
}
} else {
NSLog(@"Picture cache does not exist");
}
pictureCachePath = nil;
}
@end

View File

@@ -33,5 +33,6 @@
@interface OutgoingMessageTableCell : RoomMessageTableCell
@property (weak, nonatomic) IBOutlet UILabel *unsentLabel;
@property (weak, nonatomic) IBOutlet UIActivityIndicatorView *activityIndicator;
@end

View File

@@ -23,6 +23,8 @@
#import "AppDelegate.h"
#import "AppSettings.h"
#import "MediaManager.h"
#define ROOM_MESSAGE_CELL_MAX_TEXTVIEW_WIDTH 200
#define ROOM_MESSAGE_CELL_TOP_MARGIN 5
#define ROOM_MESSAGE_CELL_BOTTOM_MARGIN 5
@@ -55,6 +57,9 @@ NSString *const kFailedEventId = @"failedEventId";
// Members list
NSArray *members;
id membersListener;
// Cache
NSMutableArray *tmpCachedAttachments;
}
@property (weak, nonatomic) IBOutlet UINavigationItem *roomNavItem;
@@ -93,6 +98,12 @@ NSString *const kFailedEventId = @"failedEventId";
[[MatrixHandler sharedHandler] removeObserver:self forKeyPath:@"isInitialSyncDone"];
}
#endif
// Clear temporary cached attachments (used for local echo)
NSUInteger index = tmpCachedAttachments.count;
while (index--) {
[MediaManager clearCacheForURL:[tmpCachedAttachments objectAtIndex:index]];
}
tmpCachedAttachments = nil;
messages = nil;
if (messagesListener) {
@@ -120,9 +131,6 @@ NSString *const kFailedEventId = @"failedEventId";
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// Reload room data
[self configureView];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onKeyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onKeyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
@@ -144,11 +152,6 @@ NSString *const kFailedEventId = @"failedEventId";
// Hide members by default
[self hideRoomMembers];
if (messagesListener) {
[mxRoom unregisterListener:messagesListener];
messagesListener = nil;
}
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UITextFieldTextDidChangeNotification object:nil];
@@ -564,6 +567,7 @@ NSString *const kFailedEventId = @"failedEventId";
if ([mxEvent.userId isEqualToString:mxHandler.userId]) {
cell = [tableView dequeueReusableCellWithIdentifier:@"OutgoingMessageCell" forIndexPath:indexPath];
cell.messageTextView.backgroundColor = [UIColor groupTableViewBackgroundColor];
[((OutgoingMessageTableCell*)cell).activityIndicator stopAnimating];
} else {
cell = [tableView dequeueReusableCellWithIdentifier:@"IncomingMessageCell" forIndexPath:indexPath];
cell.messageTextView.backgroundColor = [UIColor lightGrayColor];
@@ -605,6 +609,9 @@ NSString *const kFailedEventId = @"failedEventId";
incomingMsgCell.userNameLabel.text = [NSString stringWithFormat:@"- %@", userName];
}
incomingMsgCell.userNameLabel.frame = frame;
// Reset text color
cell.messageTextView.textColor = [UIColor blackColor];
} else {
// Hide unsent label by default
UILabel *unsentLabel = ((OutgoingMessageTableCell*)cell).unsentLabel;
@@ -628,6 +635,14 @@ NSString *const kFailedEventId = @"failedEventId";
cell.msgTextViewWidthConstraint.constant = contentSize.width;
cell.attachmentViewWidthConstraint.constant = contentSize.width - 2 * ROOM_MESSAGE_CELL_IMAGE_MARGIN;
// Fade attachments during upload
if (isIncomingMsg == NO && [mxEvent.eventId hasPrefix:kLocalEchoEventIdPrefix]) {
cell.attachmentView.alpha = 0.5;
[((OutgoingMessageTableCell*)cell).activityIndicator startAnimating];
} else {
cell.attachmentView.alpha = 1;
}
NSString *msgtype = mxEvent.content[@"msgtype"];
if ([msgtype isEqualToString:kMXMessageTypeImage]) {
NSString *url = mxEvent.content[@"thumbnail_url"];
@@ -646,12 +661,11 @@ NSString *const kFailedEventId = @"failedEventId";
cell.attachmentView.hidden = YES;
NSString *displayText = [mxHandler displayTextFor:mxEvent inSubtitleMode:NO];
// Update text color according to text content
if ([displayText hasPrefix:kMatrixHandlerUnsupportedMessagePrefix]) {
cell.messageTextView.textColor = [UIColor redColor];
} else if (isIncomingMsg && ([displayText rangeOfString:mxHandler.userDisplayName options:NSCaseInsensitiveSearch].location != NSNotFound || [displayText rangeOfString:mxHandler.userId options:NSCaseInsensitiveSearch].location != NSNotFound)) {
cell.messageTextView.textColor = [UIColor blueColor];
} else {
cell.messageTextView.textColor = [UIColor blackColor];
}
cell.messageTextView.text = displayText;
}
@@ -945,24 +959,41 @@ NSString *const kFailedEventId = @"failedEventId";
#pragma mark - Post messages
- (void)postMessage:(NSDictionary*)msgContent {
- (void)postMessage:(NSDictionary*)msgContent withLocalEventId:(NSString*)localEventId {
MXMessageType msgType = msgContent[@"msgtype"];
if (msgType) {
// 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;
mxEvent.content = msgContent;
mxEvent.userId = [MatrixHandler sharedHandler].userId;
// Update table sources
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:messages.count inSection:0];
[messages addObject:mxEvent];
// Refresh table display
[self.messagesTableView insertRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationBottom];
[self scrollToBottomAnimated:YES];
// Check whether a temporary event has already been added for local echo (this happens on attachments)
MXEvent *mxEvent = nil;
if (localEventId) {
// Update the temporary event with the actual msg content
NSUInteger index = messages.count;
while (index--) {
mxEvent = [messages objectAtIndex:index];
if ([mxEvent.eventId isEqualToString:localEventId]) {
mxEvent.content = msgContent;
// Refresh table display
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:0];
[self.messagesTableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
break;
}
}
} else {
// Create a temporary event to displayed outgoing message (local echo)
localEventId = [NSString stringWithFormat:@"%@%@", kLocalEchoEventIdPrefix, [[NSProcessInfo processInfo] globallyUniqueString]];
mxEvent = [[MXEvent alloc] init];
mxEvent.roomId = self.roomId;
mxEvent.eventId = localEventId;
mxEvent.eventType = MXEventTypeRoomMessage;
mxEvent.type = kMXEventTypeStringRoomMessage;
mxEvent.content = msgContent;
mxEvent.userId = [MatrixHandler sharedHandler].userId;
// Update table sources
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:messages.count inSection:0];
[messages addObject:mxEvent];
// Refresh table display
[self.messagesTableView insertRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationBottom];
[self scrollToBottomAnimated:YES];
}
// Send message to the room
[[[MatrixHandler sharedHandler] mxRestClient] postMessage:self.roomId msgType:msgType content:mxEvent.content success:^(NSString *event_id) {
@@ -1006,7 +1037,7 @@ NSString *const kFailedEventId = @"failedEventId";
msgTxt = [msgTxt substringFromIndex:4];
}
[self postMessage:@{@"msgtype":msgType, @"body":msgTxt}];
[self postMessage:@{@"msgtype":msgType, @"body":msgTxt} withLocalEventId:nil];
}
- (BOOL)isIRCStyleCommand:(NSString*)text{
@@ -1178,16 +1209,59 @@ 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
[self.messagesTableView insertRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationBottom];
[self scrollToBottomAnimated:YES];
// Upload image and its thumbnail
MatrixHandler *mxHandler = [MatrixHandler sharedHandler];
NSUInteger thumbnailSize = ROOM_MESSAGE_CELL_MAX_TEXTVIEW_WIDTH - 5 * ROOM_MESSAGE_CELL_IMAGE_MARGIN;
[mxHandler.mxRestClient uploadImage:selectedImage thumbnailSize:thumbnailSize timeout:30 success:^(NSDictionary *imageMessage) {
// Send image
[self postMessage:imageMessage];
[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;
}
}
}];
}
} else if ([mediaType isEqualToString:(NSString *)kUTTypeMovie]) {