// _____ _ // |_ _| |_ _ _ ___ ___ _ __ __ _ // | | | ' \| '_/ -_) -_) ' \/ _` |_ // |_| |_||_|_| \___\___|_|_|_\__,_(_) // // Threema iOS Client // Copyright (c) 2015-2020 Threema GmbH // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License, version 3, // as published by the Free Software Foundation. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . #import "ChatBlobMessageCell.h" #import "BaseMessage.h" #import "BlobData.h" #import "AppDelegate.h" @interface ChatBlobMessageCell () @end @implementation ChatBlobMessageCell - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier transparent:(BOOL)transparent { self = [super initWithStyle:style reuseIdentifier:reuseIdentifier transparent:transparent]; if (self) { [self setupActivityIndicator]; [self setupResendButton]; [self setupProgressBar]; [self setupTapAction]; } return self; } - (void)dealloc { @try { [self.message removeObserver:self forKeyPath:@"progress"]; [self.message removeObserver:self forKeyPath:@"sendFailed"]; } @catch(NSException *e) {} } - (void)setEditing:(BOOL)editing animated:(BOOL)animated { [super setEditing:editing animated:animated]; if (editing) { _resendButton.hidden = YES; } else { [self updateResendButton]; } } - (void)setupProgressBar { _progressBar = [[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleBar]; if (self.message.isOwn.boolValue) { _progressBar.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin; } else { _progressBar.autoresizingMask = UIViewAutoresizingFlexibleRightMargin; } _progressBar.progressTintColor = [Colors main]; _progressBar.hidden = YES; [self.contentView addSubview:_progressBar]; [self setNeedsLayout]; } - (void)setupActivityIndicator { UIActivityIndicatorViewStyle style = UIActivityIndicatorViewStyleGray; switch ([Colors getTheme]) { case ColorThemeDark: case ColorThemeDarkWork: style = UIActivityIndicatorViewStyleWhite; break; case ColorThemeLight: case ColorThemeLightWork: case ColorThemeUndefined: break; } _activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:style]; _activityIndicator.hidesWhenStopped = YES; [self.contentView addSubview:_activityIndicator]; } - (void)setupResendButton { _resendButton = [UIButton buttonWithType:UIButtonTypeRoundedRect]; _resendButton.clearsContextBeforeDrawing = NO; [_resendButton setTitle:NSLocalizedString(@"try_again", nil) forState:UIControlStateNormal]; _resendButton.titleLabel.font = [UIFont boldSystemFontOfSize:12.0]; _resendButton.titleLabel.lineBreakMode = NSLineBreakByTruncatingTail; [_resendButton addTarget:self action:@selector(resendButtonTapped:) forControlEvents:UIControlEventTouchUpInside]; _resendButton.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin; [self.contentView addSubview:_resendButton]; } - (void)setupTapAction { UITapGestureRecognizer *gestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(messageTapped:)]; gestureRecognizer.delegate = self; self.msgBackground.userInteractionEnabled = YES; [self.msgBackground addGestureRecognizer:gestureRecognizer]; } - (void)setMessage:(BaseMessage *)newMessage { @try { [self.message removeObserver:self forKeyPath:@"progress"]; [self.message removeObserver:self forKeyPath:@"sendFailed"]; } @catch(NSException *e) {} BaseMessage *blobMessage = (BaseMessage *)self.message; /* Check if this blob has not been sent yet and is also not in progress. This can happen if the app is terminated while an upload is in progress */ if (blobMessage.isOwn.boolValue && !blobMessage.sent.boolValue && !blobMessage.sendFailed.boolValue && [blobMessage blobGetProgress] == nil && [blobMessage.date compare:[AppDelegate sharedAppDelegate].appLaunchDate] == NSOrderedAscending) { newMessage.sendFailed = [NSNumber numberWithBool:YES]; } [super setMessage:newMessage]; if (!self.chatVc.isOpenWithForceTouch) { [self.message addObserver:self forKeyPath:@"progress" options:0 context:nil]; [self.message addObserver:self forKeyPath:@"sendFailed" options:0 context:nil]; } [self updateResendButton]; [self updateProgress]; [self setNeedsLayout]; } - (void)deleteMessage:(UIMenuController*)menuController { if (self.message.isOwn.boolValue && !self.message.sent.boolValue && !self.message.sendFailed.boolValue) return; else [super deleteMessage:menuController]; } - (BOOL)showActivityIndicator { return NO; } - (BOOL)showProgressBar { return YES; } - (void)updateActivityIndicator { if ([self showActivityIndicator] == NO) { return; } BaseMessage *blobMessage = (BaseMessage *)self.message; if ([blobMessage blobGetProgress] == nil ) { [_activityIndicator stopAnimating]; return; } if (blobMessage.isOwn.boolValue) { [_activityIndicator stopAnimating]; } else { if ([blobMessage blobGetThumbnail] != nil) { [_activityIndicator stopAnimating]; } else { if ([blobMessage blobGetProgress] != nil) { [_activityIndicator startAnimating]; [self.contentView bringSubviewToFront:_activityIndicator]; } else { [_activityIndicator stopAnimating]; } } } } - (void)updateProgress { [self updateActivityIndicator]; if ([self showProgressBar] == NO) { return; } BaseMessage *blobMessage = (BaseMessage *)self.message; if ([blobMessage blobGetProgress] == nil) { _progressBar.hidden = YES; } else { _progressBar.progress = [blobMessage blobGetProgress].floatValue; _progressBar.hidden = NO; [self.contentView bringSubviewToFront:_progressBar]; } } - (void)messageTapped:(id)sender { } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; if (object != self.message || [self.message wasDeleted]) { return; } dispatch_async(dispatch_get_main_queue(), ^{ if ([keyPath isEqualToString:@"progress"] || [keyPath isEqualToString:@"sent"]) { [self updateProgress]; [self updateResendButton]; } else if ([keyPath isEqualToString:@"sendFailed"]) { [self updateProgress]; [self updateStatusImage]; [self updateResendButton]; } }); } - (void)updateStatusImage { BaseMessage *blobMessage = (BaseMessage *)self.message; if (self.message.sendFailed.boolValue && [blobMessage blobGetProgress] == nil) { self.statusImage.image = [UIImage imageNamed:@"MessageStatus_sendfailed"]; self.statusImage.alpha = 0.8; self.statusImage.hidden = NO; [self setNeedsLayout]; } else { [super updateStatusImage]; } } - (void)resendButtonTapped:(id)sender { [self resendMessage:nil]; } - (void)resendMessage:(UIMenuController*)menuController { } - (void)updateResendButton { BaseMessage *blobMessage = (BaseMessage *)self.message; if (blobMessage.isOwn.boolValue && (blobMessage.sendFailed.boolValue || blobMessage.sent.boolValue == NO) && [blobMessage blobGetProgress] == nil) { _resendButton.hidden = NO; } else { _resendButton.hidden = YES; } } - (CALayer*)bubbleMaskForImageSize:(CGSize)imageSize { CALayer *mask = [CALayer layer]; UIImage *maskImage; if (self.message.isOwn.boolValue) maskImage = [UIImage imageNamed:@"ChatBubbleSentMask"]; else maskImage = [UIImage imageNamed:@"ChatBubbleReceivedMask"]; mask.contents = (id)maskImage.CGImage; if (self.message.isOwn.boolValue) mask.contentsCenter = CGRectMake(15.0/maskImage.size.width, 13.0/maskImage.size.height, 1.0/maskImage.size.width, 1.0/maskImage.size.height); else mask.contentsCenter = CGRectMake(23.0/maskImage.size.width, 15.0/maskImage.size.height, 1.0/maskImage.size.width, 1.0/maskImage.size.height); mask.contentsScale = maskImage.scale; mask.frame = CGRectMake(0, 0, imageSize.width, imageSize.height); return mask; } - (CALayer*)bubbleMaskWithoutArrowForImageSize:(CGSize)imageSize { CALayer *mask = [CALayer layer]; UIImage *maskImage; if (self.message.isOwn.boolValue) maskImage = [UIImage imageNamed:@"ChatBubbleSentMaskWithoutArrow"]; else maskImage = [UIImage imageNamed:@"ChatBubbleReceivedMaskWithoutArrow"]; mask.contents = (id)maskImage.CGImage; if (self.message.isOwn.boolValue) mask.contentsCenter = CGRectMake(15.0/maskImage.size.width, 13.0/maskImage.size.height, 1.0/maskImage.size.width, 1.0/maskImage.size.height); else mask.contentsCenter = CGRectMake(23.0/maskImage.size.width, 15.0/maskImage.size.height, 1.0/maskImage.size.width, 1.0/maskImage.size.height); mask.contentsScale = maskImage.scale; mask.frame = CGRectMake(0, 0, imageSize.width, imageSize.height); return mask; } + (CGSize)scaleImageSizeToCell:(CGSize)size forTableWidth:(CGFloat)tableWidth { CGFloat maxWidth = [ChatMessageCell maxContentWidthForTableWidth:tableWidth]; CGFloat maxHeight; CGFloat minWidth = 40.0; CGFloat minHeight = 40.0; /* maximum height is 50% of screen height in current rotation */ if ((int)tableWidth > (int)[UIScreen mainScreen].bounds.size.width) maxHeight = [UIScreen mainScreen].bounds.size.width / 2; else maxHeight = [UIScreen mainScreen].bounds.size.height / 2; /* upper bound on size (for large phones and iPads) */ if (maxHeight > 256.0) { maxHeight = 256.0; } CGSize scaledSize = size; if (scaledSize.width < minWidth) { scaledSize.height *= minWidth / scaledSize.width; scaledSize.width = minWidth; } if (scaledSize.height < minHeight) { scaledSize.width *= minHeight / scaledSize.height; scaledSize.height = minHeight; } if (scaledSize.width > maxWidth) { scaledSize.height *= maxWidth / scaledSize.width; scaledSize.width = maxWidth; } if (scaledSize.height > maxHeight) { scaledSize.width *= maxHeight / scaledSize.height; scaledSize.height = maxHeight; } scaledSize.height = roundf(scaledSize.height); scaledSize.width = roundf(scaledSize.width); return scaledSize; } @end