ChatBlobMessageCell.m 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. // _____ _
  2. // |_ _| |_ _ _ ___ ___ _ __ __ _
  3. // | | | ' \| '_/ -_) -_) ' \/ _` |_
  4. // |_| |_||_|_| \___\___|_|_|_\__,_(_)
  5. //
  6. // Threema iOS Client
  7. // Copyright (c) 2015-2020 Threema GmbH
  8. //
  9. // This program is free software: you can redistribute it and/or modify
  10. // it under the terms of the GNU Affero General Public License, version 3,
  11. // as published by the Free Software Foundation.
  12. //
  13. // This program is distributed in the hope that it will be useful,
  14. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. // GNU Affero General Public License for more details.
  17. //
  18. // You should have received a copy of the GNU Affero General Public License
  19. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  20. #import "ChatBlobMessageCell.h"
  21. #import "BaseMessage.h"
  22. #import "BlobData.h"
  23. #import "AppDelegate.h"
  24. @interface ChatBlobMessageCell ()
  25. @end
  26. @implementation ChatBlobMessageCell
  27. - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier transparent:(BOOL)transparent {
  28. self = [super initWithStyle:style reuseIdentifier:reuseIdentifier transparent:transparent];
  29. if (self) {
  30. [self setupActivityIndicator];
  31. [self setupResendButton];
  32. [self setupProgressBar];
  33. [self setupTapAction];
  34. }
  35. return self;
  36. }
  37. - (void)dealloc {
  38. @try {
  39. [self.message removeObserver:self forKeyPath:@"progress"];
  40. [self.message removeObserver:self forKeyPath:@"sendFailed"];
  41. }
  42. @catch(NSException *e) {}
  43. }
  44. - (void)setEditing:(BOOL)editing animated:(BOOL)animated {
  45. [super setEditing:editing animated:animated];
  46. if (editing) {
  47. _resendButton.hidden = YES;
  48. } else {
  49. [self updateResendButton];
  50. }
  51. }
  52. - (void)setupProgressBar {
  53. _progressBar = [[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleBar];
  54. if (self.message.isOwn.boolValue) {
  55. _progressBar.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin;
  56. } else {
  57. _progressBar.autoresizingMask = UIViewAutoresizingFlexibleRightMargin;
  58. }
  59. _progressBar.progressTintColor = [Colors main];
  60. _progressBar.hidden = YES;
  61. [self.contentView addSubview:_progressBar];
  62. [self setNeedsLayout];
  63. }
  64. - (void)setupActivityIndicator {
  65. UIActivityIndicatorViewStyle style = UIActivityIndicatorViewStyleGray;
  66. switch ([Colors getTheme]) {
  67. case ColorThemeDark:
  68. case ColorThemeDarkWork:
  69. style = UIActivityIndicatorViewStyleWhite;
  70. break;
  71. case ColorThemeLight:
  72. case ColorThemeLightWork:
  73. case ColorThemeUndefined:
  74. break;
  75. }
  76. _activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:style];
  77. _activityIndicator.hidesWhenStopped = YES;
  78. [self.contentView addSubview:_activityIndicator];
  79. }
  80. - (void)setupResendButton {
  81. _resendButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
  82. _resendButton.clearsContextBeforeDrawing = NO;
  83. [_resendButton setTitle:NSLocalizedString(@"try_again", nil) forState:UIControlStateNormal];
  84. _resendButton.titleLabel.font = [UIFont boldSystemFontOfSize:12.0];
  85. _resendButton.titleLabel.lineBreakMode = NSLineBreakByTruncatingTail;
  86. [_resendButton addTarget:self action:@selector(resendButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
  87. _resendButton.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin;
  88. [self.contentView addSubview:_resendButton];
  89. }
  90. - (void)setupTapAction {
  91. UITapGestureRecognizer *gestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(messageTapped:)];
  92. gestureRecognizer.delegate = self;
  93. self.msgBackground.userInteractionEnabled = YES;
  94. [self.msgBackground addGestureRecognizer:gestureRecognizer];
  95. }
  96. - (void)setMessage:(BaseMessage *)newMessage {
  97. @try {
  98. [self.message removeObserver:self forKeyPath:@"progress"];
  99. [self.message removeObserver:self forKeyPath:@"sendFailed"];
  100. }
  101. @catch(NSException *e) {}
  102. BaseMessage<BlobData> *blobMessage = (BaseMessage<BlobData> *)self.message;
  103. /* 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 */
  104. if (blobMessage.isOwn.boolValue && !blobMessage.sent.boolValue && !blobMessage.sendFailed.boolValue && [blobMessage blobGetProgress] == nil && [blobMessage.date compare:[AppDelegate sharedAppDelegate].appLaunchDate] == NSOrderedAscending) {
  105. newMessage.sendFailed = [NSNumber numberWithBool:YES];
  106. }
  107. [super setMessage:newMessage];
  108. if (!self.chatVc.isOpenWithForceTouch) {
  109. [self.message addObserver:self forKeyPath:@"progress" options:0 context:nil];
  110. [self.message addObserver:self forKeyPath:@"sendFailed" options:0 context:nil];
  111. }
  112. [self updateResendButton];
  113. [self updateProgress];
  114. [self setNeedsLayout];
  115. }
  116. - (void)deleteMessage:(UIMenuController*)menuController {
  117. if (self.message.isOwn.boolValue && !self.message.sent.boolValue && !self.message.sendFailed.boolValue)
  118. return;
  119. else
  120. [super deleteMessage:menuController];
  121. }
  122. - (BOOL)showActivityIndicator {
  123. return NO;
  124. }
  125. - (BOOL)showProgressBar {
  126. return YES;
  127. }
  128. - (void)updateActivityIndicator {
  129. if ([self showActivityIndicator] == NO) {
  130. return;
  131. }
  132. BaseMessage<BlobData> *blobMessage = (BaseMessage<BlobData> *)self.message;
  133. if ([blobMessage blobGetProgress] == nil ) {
  134. [_activityIndicator stopAnimating];
  135. return;
  136. }
  137. if (blobMessage.isOwn.boolValue) {
  138. [_activityIndicator stopAnimating];
  139. } else {
  140. if ([blobMessage blobGetThumbnail] != nil) {
  141. [_activityIndicator stopAnimating];
  142. } else {
  143. if ([blobMessage blobGetProgress] != nil) {
  144. [_activityIndicator startAnimating];
  145. [self.contentView bringSubviewToFront:_activityIndicator];
  146. } else {
  147. [_activityIndicator stopAnimating];
  148. }
  149. }
  150. }
  151. }
  152. - (void)updateProgress {
  153. [self updateActivityIndicator];
  154. if ([self showProgressBar] == NO) {
  155. return;
  156. }
  157. BaseMessage<BlobData> *blobMessage = (BaseMessage<BlobData> *)self.message;
  158. if ([blobMessage blobGetProgress] == nil) {
  159. _progressBar.hidden = YES;
  160. } else {
  161. _progressBar.progress = [blobMessage blobGetProgress].floatValue;
  162. _progressBar.hidden = NO;
  163. [self.contentView bringSubviewToFront:_progressBar];
  164. }
  165. }
  166. - (void)messageTapped:(id)sender {
  167. }
  168. - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
  169. [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
  170. if (object != self.message || [self.message wasDeleted]) {
  171. return;
  172. }
  173. dispatch_async(dispatch_get_main_queue(), ^{
  174. if ([keyPath isEqualToString:@"progress"] || [keyPath isEqualToString:@"sent"]) {
  175. [self updateProgress];
  176. [self updateResendButton];
  177. } else if ([keyPath isEqualToString:@"sendFailed"]) {
  178. [self updateProgress];
  179. [self updateStatusImage];
  180. [self updateResendButton];
  181. }
  182. });
  183. }
  184. - (void)updateStatusImage {
  185. BaseMessage<BlobData> *blobMessage = (BaseMessage<BlobData> *)self.message;
  186. if (self.message.sendFailed.boolValue && [blobMessage blobGetProgress] == nil) {
  187. self.statusImage.image = [UIImage imageNamed:@"MessageStatus_sendfailed"];
  188. self.statusImage.alpha = 0.8;
  189. self.statusImage.hidden = NO;
  190. [self setNeedsLayout];
  191. } else {
  192. [super updateStatusImage];
  193. }
  194. }
  195. - (void)resendButtonTapped:(id)sender {
  196. [self resendMessage:nil];
  197. }
  198. - (void)resendMessage:(UIMenuController*)menuController {
  199. }
  200. - (void)updateResendButton {
  201. BaseMessage<BlobData> *blobMessage = (BaseMessage<BlobData> *)self.message;
  202. if (blobMessage.isOwn.boolValue && (blobMessage.sendFailed.boolValue || blobMessage.sent.boolValue == NO) && [blobMessage blobGetProgress] == nil) {
  203. _resendButton.hidden = NO;
  204. } else {
  205. _resendButton.hidden = YES;
  206. }
  207. }
  208. - (CALayer*)bubbleMaskForImageSize:(CGSize)imageSize {
  209. CALayer *mask = [CALayer layer];
  210. UIImage *maskImage;
  211. if (self.message.isOwn.boolValue)
  212. maskImage = [UIImage imageNamed:@"ChatBubbleSentMask"];
  213. else
  214. maskImage = [UIImage imageNamed:@"ChatBubbleReceivedMask"];
  215. mask.contents = (id)maskImage.CGImage;
  216. if (self.message.isOwn.boolValue)
  217. mask.contentsCenter = CGRectMake(15.0/maskImage.size.width, 13.0/maskImage.size.height, 1.0/maskImage.size.width, 1.0/maskImage.size.height);
  218. else
  219. mask.contentsCenter = CGRectMake(23.0/maskImage.size.width, 15.0/maskImage.size.height, 1.0/maskImage.size.width, 1.0/maskImage.size.height);
  220. mask.contentsScale = maskImage.scale;
  221. mask.frame = CGRectMake(0, 0, imageSize.width, imageSize.height);
  222. return mask;
  223. }
  224. - (CALayer*)bubbleMaskWithoutArrowForImageSize:(CGSize)imageSize {
  225. CALayer *mask = [CALayer layer];
  226. UIImage *maskImage;
  227. if (self.message.isOwn.boolValue)
  228. maskImage = [UIImage imageNamed:@"ChatBubbleSentMaskWithoutArrow"];
  229. else
  230. maskImage = [UIImage imageNamed:@"ChatBubbleReceivedMaskWithoutArrow"];
  231. mask.contents = (id)maskImage.CGImage;
  232. if (self.message.isOwn.boolValue)
  233. mask.contentsCenter = CGRectMake(15.0/maskImage.size.width, 13.0/maskImage.size.height, 1.0/maskImage.size.width, 1.0/maskImage.size.height);
  234. else
  235. mask.contentsCenter = CGRectMake(23.0/maskImage.size.width, 15.0/maskImage.size.height, 1.0/maskImage.size.width, 1.0/maskImage.size.height);
  236. mask.contentsScale = maskImage.scale;
  237. mask.frame = CGRectMake(0, 0, imageSize.width, imageSize.height);
  238. return mask;
  239. }
  240. + (CGSize)scaleImageSizeToCell:(CGSize)size forTableWidth:(CGFloat)tableWidth {
  241. CGFloat maxWidth = [ChatMessageCell maxContentWidthForTableWidth:tableWidth];
  242. CGFloat maxHeight;
  243. CGFloat minWidth = 40.0;
  244. CGFloat minHeight = 40.0;
  245. /* maximum height is 50% of screen height in current rotation */
  246. if ((int)tableWidth > (int)[UIScreen mainScreen].bounds.size.width)
  247. maxHeight = [UIScreen mainScreen].bounds.size.width / 2;
  248. else
  249. maxHeight = [UIScreen mainScreen].bounds.size.height / 2;
  250. /* upper bound on size (for large phones and iPads) */
  251. if (maxHeight > 256.0) {
  252. maxHeight = 256.0;
  253. }
  254. CGSize scaledSize = size;
  255. if (scaledSize.width < minWidth) {
  256. scaledSize.height *= minWidth / scaledSize.width;
  257. scaledSize.width = minWidth;
  258. }
  259. if (scaledSize.height < minHeight) {
  260. scaledSize.width *= minHeight / scaledSize.height;
  261. scaledSize.height = minHeight;
  262. }
  263. if (scaledSize.width > maxWidth) {
  264. scaledSize.height *= maxWidth / scaledSize.width;
  265. scaledSize.width = maxWidth;
  266. }
  267. if (scaledSize.height > maxHeight) {
  268. scaledSize.width *= maxHeight / scaledSize.height;
  269. scaledSize.height = maxHeight;
  270. }
  271. scaledSize.height = roundf(scaledSize.height);
  272. scaledSize.width = roundf(scaledSize.width);
  273. return scaledSize;
  274. }
  275. @end