mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
Bubble collapse.
This commit is contained in:
parent
6525ccdb05
commit
cb00b22870
|
@ -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 */,
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -6,8 +6,6 @@
|
|||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
extern const CGFloat OWSMessageCellCornerRadius;
|
||||
|
||||
@interface OWSMessageCell : ConversationViewCell
|
||||
|
||||
+ (NSString *)cellReuseIdentifier;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue