ChatAudioMessageCell.m 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. // _____ _
  2. // |_ _| |_ _ _ ___ ___ _ __ __ _
  3. // | | | ' \| '_/ -_) -_) ' \/ _` |_
  4. // |_| |_||_|_| \___\___|_|_|_\__,_(_)
  5. //
  6. // Threema iOS Client
  7. // Copyright (c) 2014-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 "ChatAudioMessageCell.h"
  21. #import "AudioMessage.h"
  22. #import "AudioData.h"
  23. #import "ChatDefines.h"
  24. #import "AudioMessageSender.h"
  25. #import "UserSettings.h"
  26. #import "Utils.h"
  27. #import "UIImage+ColoredImage.h"
  28. #import "MDMSetup.h"
  29. #ifdef DEBUG
  30. static const DDLogLevel ddLogLevel = DDLogLevelVerbose;
  31. #else
  32. static const DDLogLevel ddLogLevel = DDLogLevelWarning;
  33. #endif
  34. @implementation ChatAudioMessageCell {
  35. UILabel *durationLabel;
  36. UIImageView *audioIcon;
  37. }
  38. + (CGFloat)heightForMessage:(BaseMessage*)message forTableWidth:(CGFloat)tableWidth {
  39. AudioMessage *audioMessage = (AudioMessage*)message;
  40. NSString *text = [ChatAudioMessageCell displayTextForAudioMessage:audioMessage];
  41. CGSize size = [text boundingRectWithSize:CGSizeMake([ChatMessageCell maxContentWidthForTableWidth:tableWidth] - 25, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName : [ChatMessageCell textFont]} context:nil].size;
  42. size.height = ceilf(size.height);
  43. return MAX(size.height, 34.0f);
  44. }
  45. - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier transparent:(BOOL)transparent
  46. {
  47. self = [super initWithStyle:style reuseIdentifier:reuseIdentifier transparent:transparent];
  48. if (self) {
  49. audioIcon = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"Microphone"]];
  50. [self.contentView addSubview:audioIcon];
  51. durationLabel = [[UILabel alloc] init];
  52. durationLabel.clearsContextBeforeDrawing = NO;
  53. durationLabel.backgroundColor = [UIColor clearColor];
  54. durationLabel.numberOfLines = 0;
  55. durationLabel.lineBreakMode = NSLineBreakByWordWrapping;
  56. durationLabel.font = [ChatMessageCell textFont];
  57. [self.contentView addSubview:durationLabel];
  58. }
  59. return self;
  60. }
  61. - (void)setupColors {
  62. [super setupColors];
  63. audioIcon.image = [UIImage imageNamed:@"Microphone" inColor:[Colors fontNormal]];
  64. durationLabel.textColor = [Colors fontNormal];
  65. }
  66. - (void)layoutSubviews {
  67. CGFloat messageTextWidth;
  68. if (@available(iOS 11.0, *)) {
  69. messageTextWidth = [ChatMessageCell maxContentWidthForTableWidth:self.safeAreaLayoutGuide.layoutFrame.size.width];
  70. } else {
  71. messageTextWidth = [ChatMessageCell maxContentWidthForTableWidth:self.frame.size.width];
  72. }
  73. CGSize textSize = [durationLabel.text boundingRectWithSize:CGSizeMake(messageTextWidth - 25, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName : [ChatMessageCell textFont]} context:nil].size;
  74. textSize.width = ceilf(textSize.width);
  75. textSize.height = ceilf(textSize.height);
  76. CGSize size = CGSizeMake(textSize.width + 25, MAX(34.0f, textSize.height));
  77. [self setBubbleContentSize:size];
  78. [super layoutSubviews];
  79. CGFloat textY = 7;
  80. if (textSize.height < 34.0)
  81. textY += (34.0 - textSize.height) / 2;
  82. if (self.message.isOwn.boolValue) {
  83. durationLabel.frame = CGRectMake(self.contentView.frame.size.width - textSize.width - 20, textY, floor(textSize.width+1), floor(textSize.height+1));
  84. audioIcon.frame = CGRectMake(self.contentView.frame.size.width - textSize.width - 42, (durationLabel.frame.origin.y + durationLabel.frame.size.height/2) - audioIcon.frame.size.height/2, audioIcon.frame.size.width, audioIcon.frame.size.height);
  85. self.resendButton.frame = CGRectMake(self.contentView.frame.size.width - size.width - 160.0f - self.statusImage.frame.size.width, 7 + (size.height - 32) / 2, 114, 32);
  86. } else {
  87. durationLabel.frame = CGRectMake(46 + self.contentLeftOffset, textY, floor(textSize.width+1), floor(textSize.height+1));
  88. audioIcon.frame = CGRectMake(23 + self.contentLeftOffset, (durationLabel.frame.origin.y + durationLabel.frame.size.height/2) - audioIcon.frame.size.height/2, audioIcon.frame.size.width, audioIcon.frame.size.height);
  89. }
  90. self.activityIndicator.frame = audioIcon.frame;
  91. }
  92. - (NSString *)accessibilityLabelForContent {
  93. NSString *duration = [Utils accessabilityTimeStringForSeconds:((AudioMessage*)self.message).duration.intValue];
  94. return [NSString stringWithFormat:@"%@, %@", NSLocalizedString(@"audio", nil), duration];
  95. }
  96. - (void)setMessage:(BaseMessage *)newMessage {
  97. [super setMessage:newMessage];
  98. [self updateView];
  99. }
  100. - (BOOL)showActivityIndicator {
  101. return [self showProgressBar] == NO;
  102. }
  103. - (BOOL)showProgressBar {
  104. return NO;
  105. }
  106. - (void)updateProgress {
  107. [self updateActivityIndicator];
  108. }
  109. - (void)updateActivityIndicator {
  110. AudioMessage *audioMessage = (AudioMessage*)self.message;
  111. if (audioMessage.isOwn.boolValue) {
  112. if (audioMessage.sent.boolValue || audioMessage.sendFailed.boolValue) {
  113. [self.activityIndicator stopAnimating];
  114. audioIcon.hidden = NO;
  115. } else {
  116. [self.activityIndicator startAnimating];
  117. audioIcon.hidden = YES;
  118. }
  119. } else {
  120. if (audioMessage.audio != nil) {
  121. [self.activityIndicator stopAnimating];
  122. audioIcon.hidden = NO;
  123. } else {
  124. if (audioMessage.progress != nil) {
  125. [self.activityIndicator startAnimating];
  126. audioIcon.hidden = YES;
  127. } else {
  128. [self.activityIndicator stopAnimating];
  129. audioIcon.hidden = NO;
  130. }
  131. }
  132. }
  133. }
  134. - (void)updateView {
  135. AudioMessage *audioMessage = (AudioMessage*)self.message;
  136. NSString *displayText = [ChatAudioMessageCell displayTextForAudioMessage:audioMessage];
  137. if (audioMessage.isOwn.boolValue) {
  138. durationLabel.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin;
  139. audioIcon.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin;
  140. } else {
  141. durationLabel.autoresizingMask = UIViewAutoresizingFlexibleRightMargin;
  142. audioIcon.autoresizingMask = UIViewAutoresizingFlexibleRightMargin;
  143. }
  144. [self setNeedsLayout];
  145. durationLabel.text = displayText;
  146. }
  147. - (void)messageTapped:(id)sender {
  148. [self.chatVc audioMessageTapped:(AudioMessage*)self.message];
  149. }
  150. - (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
  151. if (action == @selector(resendMessage:) && self.message.isOwn.boolValue && self.message.sendFailed.boolValue) {
  152. return YES;
  153. } else if (action == @selector(deleteMessage:) && self.message.isOwn.boolValue && !self.message.sent.boolValue && !self.message.sendFailed.boolValue) {
  154. return NO; /* don't allow messages in progress to be deleted */
  155. } else if (action == @selector(copyMessage:)) {
  156. return NO; /* cannot copy audios */
  157. } else if (action == @selector(shareMessage:)) {
  158. if (@available(iOS 13.0, *)) {
  159. MDMSetup *mdmSetup = [[MDMSetup alloc] initWithSetup:false];
  160. if ([mdmSetup disableShareMedia] == true) {
  161. return NO;
  162. }
  163. }
  164. return (((AudioMessage*)self.message).audio != nil); /* can only save downloaded audios */
  165. } else if (action == @selector(forwardMessage:)) {
  166. if (@available(iOS 13.0, *)) {
  167. return (((AudioMessage*)self.message).audio != nil); /* can only save downloaded audios */
  168. } else {
  169. return NO;
  170. }
  171. } else {
  172. return [super canPerformAction:action withSender:sender];
  173. }
  174. }
  175. - (void)copyMessage:(UIMenuController *)menuController {
  176. }
  177. - (void)resendMessage:(UIMenuController*)menuController {
  178. AudioMessage *audioMessage = (AudioMessage*)self.message;
  179. AudioMessageSender *sender = [[AudioMessageSender alloc] init];
  180. [sender retryWithAudioMessage:audioMessage];
  181. }
  182. + (NSString*)displayTextForAudioMessage:(AudioMessage*)audioMessage {
  183. int seconds = audioMessage.duration.intValue;
  184. return [Utils timeStringForSeconds:seconds];
  185. }
  186. - (BOOL)performPlayActionForAccessibility {
  187. [self messageTapped:self];
  188. return YES;
  189. }
  190. @end