From 6b6f4f933684d5cd43548f17876e56f253ecc331 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Tue, 10 Apr 2018 12:59:26 -0400 Subject: [PATCH] Limit caption length // FREEBIE --- .../ConversationViewController.m | 3 ++ .../AttachmentApprovalViewController.swift | 40 ++++++++++++++----- .../Messages/Interactions/TSOutgoingMessage.m | 15 ++++++- 3 files changed, 47 insertions(+), 11 deletions(-) diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m index 10e4e19b0..e8cdac495 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m @@ -4165,6 +4165,9 @@ typedef enum : NSUInteger { DataSource *_Nullable dataSource = [DataSourceValue dataSourceWithOversizeText:text]; SignalAttachment *attachment = [SignalAttachment attachmentWithDataSource:dataSource dataUTI:kOversizeTextAttachmentUTI]; + // TODO we should redundantly send the first n chars in the body field so it can be viewed + // on clients that don't support oversized text messgaes, (and potentially generate a preview + // before the attachment is downloaded) message = [ThreadUtil sendMessageWithAttachment:attachment inThread:self.thread quotedReplyModel:self.inputToolbar.quotedReply diff --git a/SignalMessaging/attachments/AttachmentApprovalViewController.swift b/SignalMessaging/attachments/AttachmentApprovalViewController.swift index db7598bf6..d673e0556 100644 --- a/SignalMessaging/attachments/AttachmentApprovalViewController.swift +++ b/SignalMessaging/attachments/AttachmentApprovalViewController.swift @@ -609,19 +609,26 @@ class CaptioningToolbar: UIView, UITextViewDelegate { // MARK: - UITextViewDelegate public func textViewDidChange(_ textView: UITextView) { - // compute new height assuming width is unchanged - let currentSize = textView.frame.size - let newHeight = clampedTextViewHeight(fixedWidth: currentSize.width) - - if newHeight != self.textViewHeight { - Logger.debug("\(self.logTag) TextView height changed: \(self.textViewHeight) -> \(newHeight)") - self.textViewHeight = newHeight - self.textViewHeightConstraint?.constant = textViewHeight - self.invalidateIntrinsicContentSize() - } + updateHeight(textView: textView) } public func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { + + // Limit caption character count. We do this in characters, not bytes. + // This character limit will be safely below our byte limit (16k) for almost all uses. + // Because the captioning interface doesn't allow newlines, in practice design pressures users to leave relatively short captions. + let maxCharacterCount = 2000 + guard textView.text.count + text.count - range.length <= maxCharacterCount else { + // Accept as much of the input as we can + let remainingSpace = maxCharacterCount - textView.text.count + if (remainingSpace) > 0 { + let acceptableAddition = text.substring(to: text.startIndex.advanced(by: remainingSpace)) + textView.text = "\(textView.text ?? "")\(acceptableAddition)" + updateHeight(textView: textView) + } + return false + } + // Though we can wrap the text, we don't want to encourage multline captions, plus a "done" button // allows the user to get the keyboard out of the way while in the attachment approval view. if text == "\n" { @@ -642,6 +649,19 @@ class CaptioningToolbar: UIView, UITextViewDelegate { // MARK: - Helpers + private func updateHeight(textView: UITextView) { + // compute new height assuming width is unchanged + let currentSize = textView.frame.size + let newHeight = clampedTextViewHeight(fixedWidth: currentSize.width) + + if newHeight != self.textViewHeight { + Logger.debug("\(self.logTag) TextView height changed: \(self.textViewHeight) -> \(newHeight)") + self.textViewHeight = newHeight + self.textViewHeightConstraint?.constant = textViewHeight + self.invalidateIntrinsicContentSize() + } + } + private func clampedTextViewHeight(fixedWidth: CGFloat) -> CGFloat { let contentSize = textView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude)) return Clamp(contentSize.height, kMinTextViewHeight, maxTextViewHeight) diff --git a/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m b/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m index 9189ecd33..129c7ae2c 100644 --- a/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m +++ b/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m @@ -4,6 +4,7 @@ #import "TSOutgoingMessage.h" #import "NSDate+OWS.h" +#import "OWSMessageSender.h" #import "OWSOutgoingSyncMessage.h" #import "OWSSignalServiceProtos.pb.h" #import "ProtoBuf+OWS.h" @@ -415,7 +416,19 @@ NSString *const kTSOutgoingMessageSentRecipientAll = @"kTSOutgoingMessageSentRec OWSSignalServiceProtosDataMessageBuilder *builder = [OWSSignalServiceProtosDataMessageBuilder new]; [builder setTimestamp:self.timestamp]; - [builder setBody:self.body]; + + + if ([self.body lengthOfBytesUsingEncoding:NSUTF8StringEncoding] <= kOversizeTextMessageSizeThreshold) { + [builder setBody:self.body]; + } else { + OWSFail(@"%@ message body length too long.", self.logTag); + NSMutableString *truncatedBody = [self.body mutableCopy]; + while ([truncatedBody lengthOfBytesUsingEncoding:NSUTF8StringEncoding] > kOversizeTextMessageSizeThreshold) { + DDLogError(@"%@ truncating body which is too long: %tu", self.logTag, [truncatedBody lengthOfBytesUsingEncoding:NSUTF8StringEncoding]); + truncatedBody = [truncatedBody substringToIndex:truncatedBody.length / 2]; + } + [builder setBody:truncatedBody]; + } [builder setExpireTimer:self.expiresInSeconds]; // Group Messages