diff --git a/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift b/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift index a0416c31b..61b3c3918 100644 --- a/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift +++ b/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift @@ -1861,47 +1861,49 @@ class MediaMessageTextToolbar: UIView, UITextViewDelegate { public func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { - let existingText: String = textView.text ?? "" - let proposedText: String = (existingText as NSString).replacingCharacters(in: range, with: text) - - // Don't complicate things by mixing media attachments with oversize text attachments - guard proposedText.utf8.count <= kOversizeTextMessageSizeThreshold else { - Logger.debug("long text was truncated") - self.lengthLimitLabel.isHidden = false - - // `range` represents the section of the existing text we will replace. We can re-use that space. - // Range is in units of NSStrings's standard UTF-16 characters. Since some of those chars could be - // represented as single bytes in utf-8, while others may be 8 or more, the only way to be sure is - // to just measure the utf8 encoded bytes of the replaced substring. - let bytesAfterDelete: Int = (existingText as NSString).replacingCharacters(in: range, with: "").utf8.count + if !FeatureFlags.sendingMediaWithOversizeText { + let existingText: String = textView.text ?? "" + let proposedText: String = (existingText as NSString).replacingCharacters(in: range, with: text) + + // Don't complicate things by mixing media attachments with oversize text attachments + guard proposedText.utf8.count <= kOversizeTextMessageSizeThreshold else { + Logger.debug("long text was truncated") + self.lengthLimitLabel.isHidden = false + + // `range` represents the section of the existing text we will replace. We can re-use that space. + // Range is in units of NSStrings's standard UTF-16 characters. Since some of those chars could be + // represented as single bytes in utf-8, while others may be 8 or more, the only way to be sure is + // to just measure the utf8 encoded bytes of the replaced substring. + let bytesAfterDelete: Int = (existingText as NSString).replacingCharacters(in: range, with: "").utf8.count + + // Accept as much of the input as we can + let byteBudget: Int = Int(kOversizeTextMessageSizeThreshold) - bytesAfterDelete + if byteBudget >= 0, let acceptableNewText = text.truncated(toByteCount: UInt(byteBudget)) { + textView.text = (existingText as NSString).replacingCharacters(in: range, with: acceptableNewText) + } - // Accept as much of the input as we can - let byteBudget: Int = Int(kOversizeTextMessageSizeThreshold) - bytesAfterDelete - if byteBudget >= 0, let acceptableNewText = text.truncated(toByteCount: UInt(byteBudget)) { - textView.text = (existingText as NSString).replacingCharacters(in: range, with: acceptableNewText) + return false } + self.lengthLimitLabel.isHidden = true - return false - } - self.lengthLimitLabel.isHidden = true + // After verifying the byte-length is sufficiently small, verify the character count is within bounds. + guard proposedText.count <= kMaxMessageBodyCharacterCount else { + Logger.debug("hit attachment message body character count limit") - // After verifying the byte-length is sufficiently small, verify the character count is within bounds. - guard proposedText.count <= kMaxMessageBodyCharacterCount else { - Logger.debug("hit attachment message body character count limit") + self.lengthLimitLabel.isHidden = false - self.lengthLimitLabel.isHidden = false + // `range` represents the section of the existing text we will replace. We can re-use that space. + let charsAfterDelete: Int = (existingText as NSString).replacingCharacters(in: range, with: "").count - // `range` represents the section of the existing text we will replace. We can re-use that space. - let charsAfterDelete: Int = (existingText as NSString).replacingCharacters(in: range, with: "").count + // Accept as much of the input as we can + let charBudget: Int = Int(kMaxMessageBodyCharacterCount) - charsAfterDelete + if charBudget >= 0 { + let acceptableNewText = String(text.prefix(charBudget)) + textView.text = (existingText as NSString).replacingCharacters(in: range, with: acceptableNewText) + } - // Accept as much of the input as we can - let charBudget: Int = Int(kMaxMessageBodyCharacterCount) - charsAfterDelete - if charBudget >= 0 { - let acceptableNewText = String(text.prefix(charBudget)) - textView.text = (existingText as NSString).replacingCharacters(in: range, with: acceptableNewText) + return false } - - return false } // Though we can wrap the text, we don't want to encourage multline captions, plus a "done" button diff --git a/SignalMessaging/utils/ThreadUtil.m b/SignalMessaging/utils/ThreadUtil.m index c4ec5a10c..99a0b714f 100644 --- a/SignalMessaging/utils/ThreadUtil.m +++ b/SignalMessaging/utils/ThreadUtil.m @@ -113,7 +113,15 @@ NS_ASSUME_NONNULL_BEGIN if ([fullMessageText lengthOfBytesUsingEncoding:NSUTF8StringEncoding] < kOversizeTextMessageSizeThreshold) { truncatedText = fullMessageText; } else { - truncatedText = [fullMessageText ows_truncatedToByteCount:kOversizeTextMessageSizeThreshold]; + if (SSKFeatureFlags.sendingMediaWithOversizeText) { + truncatedText = [fullMessageText ows_truncatedToByteCount:kOversizeTextMessageSizeThreshold]; + } else { + // Legacy iOS clients already support receiving long text, but they assume _any_ body + // text is the _full_ body text. So until we consider "rollout" complete, we maintain + // the legacy sending behavior, which does not include the truncated text in the + // websocket proto. + truncatedText = nil; + } DataSource *_Nullable dataSource = [DataSourceValue dataSourceWithOversizeText:fullMessageText]; if (dataSource) { SignalAttachment *oversizeTextAttachment = diff --git a/SignalServiceKit/src/Util/FeatureFlags.swift b/SignalServiceKit/src/Util/FeatureFlags.swift new file mode 100644 index 000000000..539085932 --- /dev/null +++ b/SignalServiceKit/src/Util/FeatureFlags.swift @@ -0,0 +1,22 @@ +// +// Copyright (c) 2019 Open Whisper Systems. All rights reserved. +// + +import Foundation + +/// By centralizing feature flags here and documenting their rollout plan, it's easier to review +/// which feature flags are in play. +@objc(SSKFeatureFlags) +public class FeatureFlags: NSObject { + + /// iOS has long supported sending oversized text as a sidecar attachment. The other clients + /// simply displayed it as a text attachment. As part of the new cross-client long-text feature, + /// we want to be able to display long text with attachments as well. Existing iOS clients + /// won't properly display this, so we'll need to wait a while for rollout. + /// The stakes aren't __too__ high, because legacy clients won't lose data - they just won't + /// see the media attached to a long text message until they update their client. + @objc + public static var sendingMediaWithOversizeText: Bool { + return false + } +}