Try to avoid generating link previews while user is actively editing the URL

This commit is contained in:
Michael Kirk 2019-02-15 20:35:53 -07:00
parent f0e4c9f9b4
commit 467dde2bc9
4 changed files with 56 additions and 14 deletions

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import <SignalMessaging/OWSTextView.h>
@ -23,6 +23,7 @@ NS_ASSUME_NONNULL_BEGIN
@protocol ConversationTextViewToolbarDelegate <NSObject>
- (void)textViewDidChange:(UITextView *)textView;
- (void)textViewDidChangeSelection:(UITextView *)textView;
@end

View File

@ -189,6 +189,11 @@ NS_ASSUME_NONNULL_BEGIN
[self.textViewToolbarDelegate textViewDidChange:self];
}
- (void)textViewDidChangeSelection:(UITextView *)textView
{
[self.textViewToolbarDelegate textViewDidChangeSelection:self];
}
#pragma mark - Key Commands
- (nullable NSArray<UIKeyCommand *> *)keyCommands

View File

@ -869,6 +869,11 @@ const CGFloat kMaxTextViewHeight = 98;
[self updateInputLinkPreview];
}
- (void)textViewDidChangeSelection:(UITextView *)textView
{
[self updateInputLinkPreview];
}
- (void)updateHeightWithTextView:(UITextView *)textView
{
// compute new height assuming width is unchanged
@ -922,7 +927,10 @@ const CGFloat kMaxTextViewHeight = 98;
return;
}
NSString *_Nullable previewUrl = [OWSLinkPreview previewUrlForMessageBodyText:body];
// It's key that we use the *raw/unstripped* text, so we can reconcile cursor position with the
// selectedRange.
NSString *_Nullable previewUrl = [OWSLinkPreview previewUrlForRawBodyText:self.inputTextView.text
selectedRange:self.inputTextView.selectedRange];
if (previewUrl.length < 1) {
[self clearLinkPreviewStateAndView];
return;

View File

@ -432,7 +432,11 @@ public class OWSLinkPreview: MTLModel {
private static var previewUrlCache: NSCache<AnyObject, AnyObject> = NSCache()
@objc
public class func previewUrl(forMessageBodyText body: String?) -> String? {
public class func previewUrl(forRawBodyText body: String?, selectedRange: NSRange) -> String? {
return previewUrl(forMessageBodyText: body, selectedRange: selectedRange)
}
public class func previewUrl(forMessageBodyText body: String?, selectedRange: NSRange?) -> String? {
AssertIsOnMainThread()
// Exit early if link previews are not enabled in order to avoid
@ -440,10 +444,15 @@ public class OWSLinkPreview: MTLModel {
guard OWSLinkPreview.featureEnabled else {
return nil
}
guard SSKPreferences.areLinkPreviewsEnabled() else {
return nil
}
guard let body = body else {
return nil
}
if let cachedUrl = previewUrlCache.object(forKey: body as AnyObject) as? String {
Logger.verbose("URL parsing cache hit.")
guard cachedUrl.count > 0 else {
@ -451,27 +460,45 @@ public class OWSLinkPreview: MTLModel {
}
return cachedUrl
}
let previewUrls = allPreviewUrls(forMessageBodyText: body)
guard let previewUrl = previewUrls.first else {
let previewUrlMatches = allPreviewUrlMatches(forMessageBodyText: body)
guard let urlMatch = previewUrlMatches.first else {
// Use empty string to indicate "no preview URL" in the cache.
previewUrlCache.setObject("" as AnyObject, forKey: body as AnyObject)
return nil
}
previewUrlCache.setObject(previewUrl as AnyObject, forKey: body as AnyObject)
return previewUrl
if let selectedRange = selectedRange {
Logger.verbose("match: urlString: \(urlMatch.urlString) range: \(urlMatch.matchRange) selectedRange: \(selectedRange)")
if selectedRange.location != body.count,
urlMatch.matchRange.intersection(selectedRange) != nil {
Logger.debug("ignoring URL, since the user is currently editing it.")
// we don't want to cache the result here, as we want to fetch the link preview
// if the user moves the cursor.
return nil
}
Logger.debug("considering URL, since the user is not currently editing it.")
}
previewUrlCache.setObject(urlMatch.urlString as AnyObject, forKey: body as AnyObject)
return urlMatch.urlString
}
class func allPreviewUrls(forMessageBodyText body: String?) -> [String] {
struct URLMatchResult {
let urlString: String
let matchRange: NSRange
}
class func allPreviewUrls(forMessageBodyText body: String) -> [String] {
return allPreviewUrlMatches(forMessageBodyText: body).map { $0.urlString }
}
class func allPreviewUrlMatches(forMessageBodyText body: String) -> [URLMatchResult] {
guard OWSLinkPreview.featureEnabled else {
return []
}
guard SSKPreferences.areLinkPreviewsEnabled() else {
return []
}
guard let body = body else {
return []
}
let detector: NSDataDetector
do {
@ -481,7 +508,7 @@ public class OWSLinkPreview: MTLModel {
return []
}
var previewUrls = [String]()
var urlMatches: [URLMatchResult] = []
let matches = detector.matches(in: body, options: [], range: NSRange(location: 0, length: body.count))
for match in matches {
guard let matchURL = match.url else {
@ -490,10 +517,11 @@ public class OWSLinkPreview: MTLModel {
}
let urlString = matchURL.absoluteString
if isValidLinkUrl(urlString) {
previewUrls.append(urlString)
let matchResult = URLMatchResult(urlString: urlString, matchRange: match.range)
urlMatches.append(matchResult)
}
}
return previewUrls
return urlMatches
}
// MARK: - Preview Construction