Merge branch 'charlesmchen/tapVsLinkVsKeyboard'

This commit is contained in:
Matthew Chen 2017-11-14 09:10:26 -05:00
commit 1fa0dda582

View file

@ -24,6 +24,8 @@ NS_ASSUME_NONNULL_BEGIN
@end
#pragma mark -
@implementation BubbleMaskingView
- (void)setFrame:(CGRect)frame
@ -65,6 +67,72 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark -
@interface OWSMessageTextView : UITextView
@property (nonatomic) BOOL shouldIgnoreEvents;
@end
#pragma mark -
@implementation OWSMessageTextView
// Our message text views are never used for editing;
// suppress their ability to become first responder
// so that tapping on them doesn't hide keyboard.
- (BOOL)canBecomeFirstResponder
{
return NO;
}
// Ignore interactions with the text view _except_ taps on links.
//
// We want to disable "partial" selection of text in the message
// and we want to enable "tap to resend" by tapping on a message.
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *_Nullable)event
{
if (self.shouldIgnoreEvents) {
// We ignore all events for failed messages so that users
// can tap-to-resend even "all link" messages.
return NO;
}
// Find the nearest text position to the event.
UITextPosition *_Nullable position = [self closestPositionToPoint:point];
if (!position) {
return NO;
}
// Find the range of the character in the text which contains the event.
//
// Try every layout direction (this might not be necessary).
UITextRange *_Nullable range = nil;
for (NSNumber *textLayoutDirection in @[
@(UITextLayoutDirectionLeft),
@(UITextLayoutDirectionRight),
@(UITextLayoutDirectionUp),
@(UITextLayoutDirectionDown),
]) {
range = [self.tokenizer rangeEnclosingPosition:position
withGranularity:UITextGranularityCharacter
inDirection:(UITextDirection)textLayoutDirection.intValue];
if (range) {
break;
}
}
if (!range) {
return NO;
}
// Ignore the event unless it occurred inside a link.
NSInteger startIndex = [self offsetFromPosition:self.beginningOfDocument toPosition:range.start];
BOOL result =
[self.attributedText attribute:NSLinkAttributeName atIndex:(NSUInteger)startIndex effectiveRange:nil] != nil;
return result;
}
@end
#pragma mark -
@interface OWSMessageCell ()
// The nullable properties are created as needed.
@ -72,7 +140,7 @@ NS_ASSUME_NONNULL_BEGIN
// to always keep one around.
@property (nonatomic) BubbleMaskingView *payloadView;
@property (nonatomic) UILabel *dateHeaderLabel;
@property (nonatomic) UITextView *textView;
@property (nonatomic) OWSMessageTextView *textView;
@property (nonatomic, nullable) UIImageView *failedSendBadgeView;
@property (nonatomic, nullable) UILabel *tapForMoreLabel;
@property (nonatomic, nullable) UIImageView *bubbleImageView;
@ -90,6 +158,7 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, nullable) NSArray<NSLayoutConstraint *> *dateHeaderConstraints;
@property (nonatomic, nullable) NSArray<NSLayoutConstraint *> *contentConstraints;
@property (nonatomic, nullable) NSArray<NSLayoutConstraint *> *footerConstraints;
@property (nonatomic) BOOL isPresentingMenuController;
@end
@ -132,7 +201,7 @@ NS_ASSUME_NONNULL_BEGIN
[self.payloadView addSubview:self.bubbleImageView];
[self.bubbleImageView autoPinToSuperviewEdges];
self.textView = [UITextView new];
self.textView = [OWSMessageTextView new];
self.textView.backgroundColor = [UIColor clearColor];
self.textView.opaque = NO;
self.textView.editable = NO;
@ -646,21 +715,20 @@ NS_ASSUME_NONNULL_BEGIN
self.textView.textColor = textColor;
// Honor dynamic type in the message bodies.
self.textView.font = [self textMessageFont];
self.textView.linkTextAttributes = @{
NSForegroundColorAttributeName : textColor,
NSUnderlineStyleAttributeName : @(NSUnderlineStyleSingle | NSUnderlinePatternSolid)
};
self.textView.dataDetectorTypes
= (UIDataDetectorTypeLink | UIDataDetectorTypeAddress | UIDataDetectorTypeCalendarEvent);
// Don't link outgoing messages that haven't been sent yet, as
// this interferes with "tap to retry".
BOOL canLinkify = YES;
if (self.viewItem.interaction.interactionType == OWSInteractionType_OutgoingMessage) {
// Ignore taps on links in outgoing messages that haven't been sent yet, as
// this interferes with "tap to retry".
TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)self.viewItem.interaction;
canLinkify = outgoingMessage.messageState == TSOutgoingMessageStateSentToService;
}
if (canLinkify) {
self.textView.linkTextAttributes = @{
NSForegroundColorAttributeName : textColor,
NSUnderlineStyleAttributeName : @(NSUnderlineStyleSingle | NSUnderlinePatternSolid)
};
self.textView.dataDetectorTypes
= (UIDataDetectorTypeLink | UIDataDetectorTypeAddress | UIDataDetectorTypeCalendarEvent);
self.textView.shouldIgnoreEvents = outgoingMessage.messageState != TSOutgoingMessageStateSentToService;
} else {
self.textView.shouldIgnoreEvents = NO;
}
if (self.displayableText.isTextTruncated) {
@ -1038,6 +1106,8 @@ NS_ASSUME_NONNULL_BEGIN
[self.expirationTimerView clearAnimations];
[self.expirationTimerView removeFromSuperview];
self.expirationTimerView = nil;
self.isPresentingMenuController = NO;
}
#pragma mark - Notifications
@ -1159,6 +1229,11 @@ NS_ASSUME_NONNULL_BEGIN
- (void)showMenuController:(CGPoint)fromLocation
{
// We don't want taps on messages to hide the keyboard,
// so we only let messages become first responder
// while they are trying to present the menu controller.
self.isPresentingMenuController = YES;
[self becomeFirstResponder];
if ([UIMenuController sharedMenuController].isMenuVisible) {
@ -1208,7 +1283,37 @@ NS_ASSUME_NONNULL_BEGIN
- (BOOL)canBecomeFirstResponder
{
return YES;
return self.isPresentingMenuController;
}
- (void)didHideMenuController:(NSNotification *)notification
{
self.isPresentingMenuController = NO;
}
- (void)setIsPresentingMenuController:(BOOL)isPresentingMenuController
{
if (_isPresentingMenuController == isPresentingMenuController) {
return;
}
_isPresentingMenuController = isPresentingMenuController;
if (isPresentingMenuController) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(didHideMenuController:)
name:UIMenuControllerDidHideMenuNotification
object:nil];
} else {
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIMenuControllerDidHideMenuNotification
object:nil];
}
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#pragma mark - Logging