Bubble collapse.

This commit is contained in:
Matthew Chen 2018-03-28 10:01:01 -04:00
parent 6525ccdb05
commit cb00b22870
9 changed files with 259 additions and 270 deletions

View File

@ -214,6 +214,8 @@
34D8C02B1ED3685800188D7C /* DebugUIContacts.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D8C02A1ED3685800188D7C /* DebugUIContacts.m */; };
34D99C931F2937CC00D284D6 /* OWSAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D99C911F2937CC00D284D6 /* OWSAnalytics.swift */; };
34DB0BED2011548B007B313F /* OWSDatabaseConverterTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 34DB0BEC2011548B007B313F /* OWSDatabaseConverterTest.m */; };
34DBF003206BD5A500025978 /* OWSMessageTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34DBEFFF206BD5A400025978 /* OWSMessageTextView.m */; };
34DBF004206BD5A500025978 /* OWSBubbleView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34DBF001206BD5A500025978 /* OWSBubbleView.m */; };
34E3E5681EC4B19400495BAC /* AudioProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34E3E5671EC4B19400495BAC /* AudioProgressView.swift */; };
34E3EF0D1EFC235B007F6822 /* DebugUIDiskUsage.m in Sources */ = {isa = PBXBuildFile; fileRef = 34E3EF0C1EFC235B007F6822 /* DebugUIDiskUsage.m */; };
34E3EF101EFC2684007F6822 /* DebugUIPage.m in Sources */ = {isa = PBXBuildFile; fileRef = 34E3EF0F1EFC2684007F6822 /* DebugUIPage.m */; };
@ -835,6 +837,10 @@
34D99C911F2937CC00D284D6 /* OWSAnalytics.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWSAnalytics.swift; sourceTree = "<group>"; };
34DB0BEB2011548A007B313F /* OWSDatabaseConverterTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSDatabaseConverterTest.h; sourceTree = "<group>"; };
34DB0BEC2011548B007B313F /* OWSDatabaseConverterTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSDatabaseConverterTest.m; sourceTree = "<group>"; };
34DBEFFF206BD5A400025978 /* OWSMessageTextView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSMessageTextView.m; sourceTree = "<group>"; };
34DBF000206BD5A400025978 /* OWSMessageTextView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSMessageTextView.h; sourceTree = "<group>"; };
34DBF001206BD5A500025978 /* OWSBubbleView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSBubbleView.m; sourceTree = "<group>"; };
34DBF002206BD5A500025978 /* OWSBubbleView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSBubbleView.h; sourceTree = "<group>"; };
34E3E5671EC4B19400495BAC /* AudioProgressView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AudioProgressView.swift; sourceTree = "<group>"; };
34E3EF0B1EFC235B007F6822 /* DebugUIDiskUsage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DebugUIDiskUsage.h; sourceTree = "<group>"; };
34E3EF0C1EFC235B007F6822 /* DebugUIDiskUsage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DebugUIDiskUsage.m; sourceTree = "<group>"; };
@ -1645,6 +1651,8 @@
34D1F0971F867BFC0066283D /* ConversationViewCell.m */,
34D1F0B81F8800D90066283D /* OWSAudioMessageView.h */,
34D1F0B91F8800D90066283D /* OWSAudioMessageView.m */,
34DBF002206BD5A500025978 /* OWSBubbleView.h */,
34DBF001206BD5A500025978 /* OWSBubbleView.m */,
34D1F09A1F867BFC0066283D /* OWSContactOffersCell.h */,
34D1F09B1F867BFC0066283D /* OWSContactOffersCell.m */,
34D1F09C1F867BFC0066283D /* OWSExpirableMessageView.h */,
@ -1654,6 +1662,8 @@
34D1F0B61F87F8850066283D /* OWSGenericAttachmentView.m */,
34D1F0A11F867BFC0066283D /* OWSMessageCell.h */,
34D1F0A21F867BFC0066283D /* OWSMessageCell.m */,
34DBF000206BD5A400025978 /* OWSMessageTextView.h */,
34DBEFFF206BD5A400025978 /* OWSMessageTextView.m */,
34D1F0A51F867BFC0066283D /* OWSSystemMessageCell.h */,
34D1F0A61F867BFC0066283D /* OWSSystemMessageCell.m */,
34D1F0A71F867BFC0066283D /* OWSUnreadIndicatorCell.h */,
@ -3213,6 +3223,7 @@
45F32C222057297A00A300D5 /* MediaDetailViewController.m in Sources */,
34B3F8851E8DF1700035BE1A /* NewGroupViewController.m in Sources */,
34D8C0271ED3673300188D7C /* DebugUIMessages.m in Sources */,
34DBF003206BD5A500025978 /* OWSMessageTextView.m in Sources */,
34D1F0B41F86D31D0066283D /* ConversationCollectionView.m in Sources */,
34B3F8821E8DF1700035BE1A /* NewContactThreadViewController.m in Sources */,
45D308AD2049A439000189E4 /* PinEntryView.m in Sources */,
@ -3244,6 +3255,7 @@
458DE9D61DEE3FD00071BB03 /* PeerConnectionClient.swift in Sources */,
45F32C242057297A00A300D5 /* MessageDetailViewController.swift in Sources */,
34D1F0841F8678AA0066283D /* ConversationInputToolbar.m in Sources */,
34DBF004206BD5A500025978 /* OWSBubbleView.m in Sources */,
FCC81A981A44558300DFEC7D /* UIDevice+TSHardwareVersion.m in Sources */,
76EB054018170B33006006FC /* AppDelegate.m in Sources */,
34D1F0831F8678AA0066283D /* ConversationInputTextView.m in Sources */,

View File

@ -22,6 +22,7 @@
#import "OWSBackup.h"
#import "OWSBackupIO.h"
#import "OWSBezierPathView.h"
#import "OWSBubbleView.h"
#import "OWSCallNotificationsAdaptee.h"
#import "OWSDatabaseMigration.h"
#import "OWSMessageCell.h"

View File

@ -0,0 +1,28 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
extern const CGFloat kOWSMessageCellCornerRadius;
extern const CGFloat kBubbleVRounding;
extern const CGFloat kBubbleHRounding;
extern const CGFloat kBubbleThornSideInset;
extern const CGFloat kBubbleThornVInset;
extern const CGFloat kBubbleTextHInset;
extern const CGFloat kBubbleTextVInset;
@interface OWSBubbleView : UIView
@property (nonatomic) BOOL isOutgoing;
@property (nonatomic) BOOL hideTail;
@property (nonatomic) CAShapeLayer *maskLayer;
@property (nonatomic) CAShapeLayer *shapeLayer;
@property (nonatomic, nullable) UIColor *bubbleColor;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,130 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "OWSBubbleView.h"
#import <SignalMessaging/UIView+OWS.h>
NS_ASSUME_NONNULL_BEGIN
// This approximates the curve of our message bubbles, which makes the animation feel a little smoother.
const CGFloat kOWSMessageCellCornerRadius = 17;
const CGFloat kBubbleVRounding = kOWSMessageCellCornerRadius;
const CGFloat kBubbleHRounding = kOWSMessageCellCornerRadius;
const CGFloat kBubbleThornSideInset = 5.f;
const CGFloat kBubbleThornVInset = 0;
const CGFloat kBubbleTextHInset = 10.f;
const CGFloat kBubbleTextVInset = 10.f;
@implementation OWSBubbleView
- (void)setIsOutgoing:(BOOL)isOutgoing
{
BOOL didChange = _isOutgoing != isOutgoing;
_isOutgoing = isOutgoing;
if (didChange || !self.shapeLayer) {
[self updateMask];
}
}
- (void)setFrame:(CGRect)frame
{
BOOL didChange = !CGSizeEqualToSize(self.frame.size, frame.size);
[super setFrame:frame];
if (didChange || !self.shapeLayer) {
[self updateMask];
}
}
- (void)setBounds:(CGRect)bounds
{
BOOL didChange = !CGSizeEqualToSize(self.bounds.size, bounds.size);
[super setBounds:bounds];
if (didChange || !self.shapeLayer) {
[self updateMask];
}
}
- (void)setBubbleColor:(nullable UIColor *)bubbleColor
{
_bubbleColor = bubbleColor;
if (!self.shapeLayer) {
[self updateMask];
}
self.shapeLayer.fillColor = bubbleColor.CGColor;
}
- (void)updateMask
{
if (!self.shapeLayer) {
self.shapeLayer = [CAShapeLayer new];
[self.layer addSublayer:self.shapeLayer];
}
if (!self.maskLayer) {
self.maskLayer = [CAShapeLayer new];
self.layer.mask = self.maskLayer;
}
UIBezierPath *bezierPath =
[self.class maskPathForSize:self.bounds.size isOutgoing:self.isOutgoing isRTL:self.isRTL];
self.shapeLayer.fillColor = self.bubbleColor.CGColor;
self.shapeLayer.path = bezierPath.CGPath;
self.maskLayer.path = bezierPath.CGPath;
}
+ (UIBezierPath *)maskPathForSize:(CGSize)size isOutgoing:(BOOL)isOutgoing isRTL:(BOOL)isRTL
{
UIBezierPath *bezierPath = [UIBezierPath new];
CGFloat bubbleLeft = 0.f;
CGFloat bubbleRight = size.width - kBubbleThornSideInset;
CGFloat bubbleTop = 0.f;
CGFloat bubbleBottom = size.height - kBubbleThornVInset;
[bezierPath moveToPoint:CGPointMake(bubbleLeft + kBubbleHRounding, bubbleTop)];
[bezierPath addLineToPoint:CGPointMake(bubbleRight - kBubbleHRounding, bubbleTop)];
[bezierPath addQuadCurveToPoint:CGPointMake(bubbleRight, bubbleTop + kBubbleVRounding)
controlPoint:CGPointMake(bubbleRight, bubbleTop)];
[bezierPath addLineToPoint:CGPointMake(bubbleRight, bubbleBottom - kBubbleVRounding)];
[bezierPath addQuadCurveToPoint:CGPointMake(bubbleRight - kBubbleHRounding, bubbleBottom)
controlPoint:CGPointMake(bubbleRight, bubbleBottom)];
[bezierPath addLineToPoint:CGPointMake(bubbleLeft + kBubbleHRounding, bubbleBottom)];
[bezierPath addQuadCurveToPoint:CGPointMake(bubbleLeft, bubbleBottom - kBubbleVRounding)
controlPoint:CGPointMake(bubbleLeft, bubbleBottom)];
[bezierPath addLineToPoint:CGPointMake(bubbleLeft, bubbleTop + kBubbleVRounding)];
[bezierPath addQuadCurveToPoint:CGPointMake(bubbleLeft + kBubbleHRounding, bubbleTop)
controlPoint:CGPointMake(bubbleLeft, bubbleTop)];
// Thorn Tip
CGPoint thornTip = CGPointMake(size.width + 1, size.height);
CGPoint thornA = CGPointMake(bubbleRight - kBubbleHRounding * 0.5f, bubbleBottom - kBubbleVRounding);
CGPoint thornB = CGPointMake(bubbleRight, bubbleBottom - kBubbleVRounding);
[bezierPath moveToPoint:thornTip];
[bezierPath addQuadCurveToPoint:thornA controlPoint:CGPointMake(thornA.x, bubbleBottom)];
[bezierPath addLineToPoint:thornB];
[bezierPath addQuadCurveToPoint:thornTip controlPoint:CGPointMake(thornB.x, bubbleBottom)];
[bezierPath addLineToPoint:thornTip];
// Horizontal Flip If Necessary
BOOL shouldFlip = isOutgoing == isRTL;
if (shouldFlip) {
CGAffineTransform flipTransform = CGAffineTransformMakeTranslation(size.width, 0.0);
flipTransform = CGAffineTransformScale(flipTransform, -1.0, 1.0);
[bezierPath applyTransform:flipTransform];
}
return bezierPath;
}
@end
NS_ASSUME_NONNULL_END

View File

@ -6,8 +6,6 @@
NS_ASSUME_NONNULL_BEGIN
extern const CGFloat OWSMessageCellCornerRadius;
@interface OWSMessageCell : ConversationViewCell
+ (NSString *)cellReuseIdentifier;

View File

@ -8,8 +8,10 @@
#import "ConversationViewItem.h"
#import "NSAttributedString+OWS.h"
#import "OWSAudioMessageView.h"
#import "OWSBubbleView.h"
#import "OWSExpirationTimerView.h"
#import "OWSGenericAttachmentView.h"
#import "OWSMessageTextView.h"
#import "Signal-Swift.h"
#import "UIColor+OWS.h"
#import <JSQMessagesViewController/JSQMessagesTimestampFormatter.h>
@ -18,7 +20,6 @@
NS_ASSUME_NONNULL_BEGIN
// TODO: Choose best thorn.
// TODO: Review all comments.
CG_INLINE CGSize CGSizeCeil(CGSize size)
@ -26,270 +27,6 @@ CG_INLINE CGSize CGSizeCeil(CGSize size)
return CGSizeMake((CGFloat)ceil(size.width), (CGFloat)ceil(size.height));
}
// This approximates the curve of our message bubbles, which makes the animation feel a little smoother.
const CGFloat OWSMessageCellCornerRadius = 17;
// TODO: We could make the bubble shape respond to dynamic text.
static const CGFloat kBubbleVRounding = 8.5f;
static const CGFloat kBubbleHRounding = 10.f;
//static const CGFloat kBubbleThornSideInset = 3.f;
//static const CGFloat kBubbleThornVInset = 3.f;
//static const CGFloat kBubbleThornSideInset = 6.f;
//static const CGFloat kBubbleThornVInset = 0.f;
static const CGFloat kBubbleThornSideInset = kBubbleHRounding * 0.3f;
static const CGFloat kBubbleThornVInset = kBubbleVRounding * 0.3f;
static const CGFloat kBubbleTextHInset = 6.f;
static const CGFloat kBubbleTextVInset = 6.f;
@interface OWSBubbleView : UIView
@property (nonatomic) BOOL isOutgoing;
@property (nonatomic) BOOL hideTail;
//@property (nonatomic, nullable, weak) UIView *maskedSubview;
@property (nonatomic) CAShapeLayer *maskLayer;
//@property (nonatomic) BOOL isOutgoing;
@property (nonatomic) CAShapeLayer *shapeLayer;
@property (nonatomic, nullable) UIColor *bubbleColor;
@end
#pragma mark -
@implementation OWSBubbleView
- (void)setIsOutgoing:(BOOL)isOutgoing {
BOOL didChange = _isOutgoing != isOutgoing;
_isOutgoing = isOutgoing;
if (didChange || !self.shapeLayer) {
[self updateMask];
}
}
- (void)setFrame:(CGRect)frame
{
BOOL didChange = !CGSizeEqualToSize(self.frame.size, frame.size);
[super setFrame:frame];
if (didChange || !self.shapeLayer) {
[self updateMask];
}
}
- (void)setBounds:(CGRect)bounds
{
BOOL didChange = !CGSizeEqualToSize(self.bounds.size, bounds.size);
[super setBounds:bounds];
if (didChange || !self.shapeLayer) {
[self updateMask];
}
}
- (void)setBubbleColor:(nullable UIColor *)bubbleColor
{
_bubbleColor = bubbleColor;
if (!self.shapeLayer) {
[self updateMask];
}
self.shapeLayer.fillColor = bubbleColor.CGColor;
}
- (void)updateMask
{
if (!self.shapeLayer) {
self.shapeLayer = [CAShapeLayer new];
[self.layer addSublayer:self.shapeLayer];
}
if (!self.maskLayer) {
self.maskLayer = [CAShapeLayer new];
self.layer.mask = self.maskLayer;
}
UIBezierPath *bezierPath = [self.class maskPathForSize:self.bounds.size
isOutgoing:self.isOutgoing
isRTL:self.isRTL];
self.shapeLayer.fillColor = self.bubbleColor.CGColor;
self.shapeLayer.path = bezierPath.CGPath;
self.maskLayer.path = bezierPath.CGPath;
}
+ (UIBezierPath *)maskPathForSize:(CGSize)size
isOutgoing:(BOOL)isOutgoing
isRTL:(BOOL)isRTL
{
UIBezierPath *bezierPath = [UIBezierPath new];
CGFloat bubbleLeft = 0.f;
CGFloat bubbleRight = size.width - kBubbleThornSideInset;
CGFloat bubbleTop = 0.f;
CGFloat bubbleBottom = size.height - kBubbleThornVInset;
[bezierPath moveToPoint:CGPointMake(bubbleLeft + kBubbleHRounding, bubbleTop)];
[bezierPath addLineToPoint:CGPointMake(bubbleRight - kBubbleHRounding, bubbleTop)];
[bezierPath addQuadCurveToPoint:CGPointMake(bubbleRight, bubbleTop + kBubbleVRounding)
controlPoint:CGPointMake(bubbleRight, bubbleTop)];
[bezierPath addLineToPoint:CGPointMake(bubbleRight, bubbleBottom - kBubbleVRounding)];
[bezierPath addQuadCurveToPoint:CGPointMake(bubbleRight - kBubbleHRounding, bubbleBottom)
controlPoint:CGPointMake(bubbleRight, bubbleBottom)];
[bezierPath addLineToPoint:CGPointMake(bubbleLeft + kBubbleHRounding, bubbleBottom)];
[bezierPath addQuadCurveToPoint:CGPointMake(bubbleLeft, bubbleBottom - kBubbleVRounding)
controlPoint:CGPointMake(bubbleLeft, bubbleBottom)];
[bezierPath addLineToPoint:CGPointMake(bubbleLeft, bubbleTop + kBubbleVRounding)];
[bezierPath addQuadCurveToPoint:CGPointMake(bubbleLeft + kBubbleHRounding, bubbleTop)
controlPoint:CGPointMake(bubbleLeft, bubbleTop)];
// Thorn Tip
CGPoint thornTip = CGPointMake(size.width,
size.height);
CGPoint thornA = CGPointMake(bubbleRight - kBubbleHRounding, bubbleBottom);
CGPoint thornB = CGPointMake(bubbleRight, bubbleBottom - kBubbleVRounding);
[bezierPath moveToPoint:thornTip];
// [bezierPath addLineToPoint:CGPointMake(bubbleRight - kBubbleHRounding * 0.85f, bubbleBottom)];
// [bezierPath addLineToPoint:thornA];
// [bezierPath addLineToPoint:thornB];
// [bezierPath addLineToPoint:CGPointMake(bubbleRight, bubbleBottom - kBubbleVRounding * 0.5f)];
[bezierPath addQuadCurveToPoint:CGPointMake(bubbleRight - kBubbleHRounding * 0.8f, bubbleBottom)
controlPoint:CGPointMake(bubbleRight - kBubbleHRounding * 0.4f, bubbleBottom)];
[bezierPath addLineToPoint:thornA];
[bezierPath addLineToPoint:thornB];
[bezierPath addLineToPoint:CGPointMake(bubbleRight, bubbleBottom - kBubbleVRounding * 0.7f)];
[bezierPath addQuadCurveToPoint:thornTip
controlPoint:CGPointMake(bubbleRight, bubbleBottom - kBubbleVRounding * 0.3f)];
// // Thorn Tip
// [bezierPath moveToPoint:CGPointMake(bubbleRight - kBubbleHRounding, bubbleBottom)];
// [bezierPath addLineToPoint:CGPointMake(bubbleRight, bubbleBottom - kBubbleVRounding)];
// [bezierPath addQuadCurveToPoint:CGPointMake(bubbleRight + kBubbleThornSideInset, bubbleBottom - 0.f)
// controlPoint:CGPointMake(bubbleRight, bubbleBottom)];
// // [bezierPath addQuadCurveToPoint:CGPointMake(bubbleRight + kBubbleThornSideInset - 1.f, bubbleBottom -
// 0.5f)
// // controlPoint:CGPointMake(bubbleRight, bubbleBottom)];
// // [bezierPath addQuadCurveToPoint:CGPointMake(bubbleRight + kBubbleThornSideInset, bubbleBottom)
// // controlPoint:CGPointMake(bubbleRight + kBubbleThornSideInset, bubbleBottom - 0.5f)];
// [bezierPath addLineToPoint:CGPointMake(bubbleRight + kBubbleThornSideInset, bubbleBottom)];
// // Thorn Tip
// CGFloat kThornPinchingA = 0.f;
// CGFloat kThornPinchingB = 3.5f;
// CGPoint thornTip = CGPointMake(self.width,
// self.height);
// CGPoint thornA = CGPointMake(bubbleRight - kBubbleHRounding, bubbleBottom - kThornPinchingA);
// CGPoint thornB = CGPointMake(bubbleRight - kThornPinchingB, bubbleBottom - kBubbleVRounding);
// [bezierPath moveToPoint:thornTip];
// [bezierPath addQuadCurveToPoint:thornA
// controlPoint:CGPointMake(bubbleRight - kBubbleHRounding, bubbleBottom - kThornPinchingA)];
// [bezierPath addLineToPoint:thornB];
// [bezierPath addQuadCurveToPoint:thornTip
// controlPoint:CGPointMake(bubbleRight - kThornPinchingB, bubbleBottom - kBubbleVRounding * 0.1f)];
// Thorn Tip
// CGFloat kThornPinchingA = 0.f;
// CGFloat kThornPinchingB = 3.5f;
// CGPoint thornA = CGPointMake(bubbleRight, bubbleBottom - kBubbleVRounding * 1.65f);
// CGPoint thornB = CGPointMake(bubbleRight, bubbleBottom - kBubbleVRounding * 1.f);
// CGPoint thornA = CGPointMake(bubbleRight, bubbleTop + kBubbleVRounding * 1.f);
// CGPoint thornB = CGPointMake(bubbleRight, bubbleTop + kBubbleVRounding * 1.65f);
// CGPoint thornTip = CGPointMake(bubbleRight + kBubbleThornSideInset * 0.85f,
// (thornA.y + thornB.y) * 0.5f);
// [bezierPath moveToPoint:thornTip];
// [bezierPath addLineToPoint:thornA];
// [bezierPath addLineToPoint:thornB];
// [bezierPath addQuadCurveToPoint:thornA
// controlPoint:CGPointMake(bubbleRight - kBubbleHRounding, bubbleBottom - kThornPinchingA)];
// [bezierPath addLineToPoint:thornB];
// [bezierPath addQuadCurveToPoint:thornTip
// controlPoint:CGPointMake(bubbleRight - kThornPinchingB, bubbleBottom - kBubbleVRounding * 0.1f)];
// Horizontal Flip If Necessary
BOOL shouldFlip = isOutgoing == isRTL;
if (shouldFlip) {
CGAffineTransform flipTransform = CGAffineTransformMakeTranslation(size.width, 0.0);
flipTransform = CGAffineTransformScale(flipTransform, -1.0, 1.0);
[bezierPath applyTransform:flipTransform];
}
return bezierPath;
}
@end
#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.
@ -481,7 +218,6 @@ static const CGFloat kBubbleTextVInset = 6.f;
[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGesture:)];
[self.bubbleView addGestureRecognizer:tap];
UILongPressGestureRecognizer *longPress =
[[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPressGesture:)];
[self.bubbleView addGestureRecognizer:longPress];
@ -684,6 +420,11 @@ static const CGFloat kBubbleTextVInset = 6.f;
OWSAssert(self.viewItem.interaction);
OWSAssert([self.viewItem.interaction isKindOfClass:[TSMessage class]]);
// TODO: We might not need to hide it.
self.bubbleView.hidden = NO;
self.bubbleView.isOutgoing = self.isOutgoing;
// TODO: Hide tails/thorns here?
if (self.shouldHaveFailedSendBadge) {
self.failedSendBadgeView = [UIImageView new];
self.failedSendBadgeView.image =
@ -722,6 +463,7 @@ static const CGFloat kBubbleTextVInset = 6.f;
} else {
// Media-only messages should have no background color; they will fill the bubble's bounds
// and we don't want artifacts at the edges.
self.bubbleView.bubbleColor = nil;
}
//<<<<<<< HEAD

View File

@ -0,0 +1,13 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
@interface OWSMessageTextView : UITextView
@property (nonatomic) BOOL shouldIgnoreEvents;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,65 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "OWSMessageTextView.h"
NS_ASSUME_NONNULL_BEGIN
@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
NS_ASSUME_NONNULL_END

View File

@ -325,7 +325,7 @@ class MediaGalleryViewController: UINavigationController, MediaGalleryDataSource
detailView.backgroundColor = .clear
self.view.backgroundColor = .clear
self.presentationView.layer.cornerRadius = OWSMessageCellCornerRadius
self.presentationView.layer.cornerRadius = kOWSMessageCellCornerRadius
fromViewController.present(self, animated: false) {
@ -478,7 +478,7 @@ class MediaGalleryViewController: UINavigationController, MediaGalleryDataSource
if changedItems {
self.presentationView.alpha = 0
} else {
self.presentationView.layer.cornerRadius = OWSMessageCellCornerRadius
self.presentationView.layer.cornerRadius = kOWSMessageCellCornerRadius
}
},
completion: { (_: Bool) in