diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleShapeView.h b/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleShapeView.h index f05cc46fe..e804347d1 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleShapeView.h +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleShapeView.h @@ -24,11 +24,21 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, nullable) UIColor *strokeColor; @property (nonatomic) CGFloat strokeThickness; +@property (nonatomic, nullable) UIColor *innerShadowColor; +@property (nonatomic) CGFloat innerShadowRadius; +@property (nonatomic) float innerShadowOpacity; + - (instancetype)init NS_UNAVAILABLE; -+ (OWSBubbleShapeView *)bubbleDrawView; -+ (OWSBubbleShapeView *)bubbleShadowView; -+ (OWSBubbleShapeView *)bubbleClipView; +- (instancetype)initDraw NS_DESIGNATED_INITIALIZER; +- (instancetype)initShadow NS_DESIGNATED_INITIALIZER; +; +- (instancetype)initClip NS_DESIGNATED_INITIALIZER; +; +- (instancetype)initInnerShadowWithColor:(UIColor *)color + radius:(CGFloat)radius + opacity:(float)opacity NS_DESIGNATED_INITIALIZER; +; @end diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleShapeView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleShapeView.m index 7d387696d..ac2d0a5f1 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleShapeView.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSBubbleShapeView.m @@ -13,6 +13,7 @@ typedef NS_ENUM(NSUInteger, OWSBubbleShapeViewMode) { OWSBubbleShapeViewMode_Draw, OWSBubbleShapeViewMode_Shadow, OWSBubbleShapeViewMode_Clip, + OWSBubbleShapeViewMode_InnerShadow, }; @interface OWSBubbleShapeView () @@ -30,13 +31,8 @@ typedef NS_ENUM(NSUInteger, OWSBubbleShapeViewMode) { @implementation OWSBubbleShapeView -- (instancetype)init +- (void)configure { - self = [super init]; - if (!self) { - return self; - } - self.mode = OWSBubbleShapeViewMode_Draw; self.opaque = NO; self.backgroundColor = [UIColor clearColor]; @@ -46,29 +42,67 @@ typedef NS_ENUM(NSUInteger, OWSBubbleShapeViewMode) { [self.layer addSublayer:self.shapeLayer]; self.maskLayer = [CAShapeLayer new]; +} + + +- (instancetype)initDraw +{ + self = [super init]; + if (!self) { + return self; + } + + self.mode = OWSBubbleShapeViewMode_Draw; + + [self configure]; return self; } -+ (OWSBubbleShapeView *)bubbleDrawView +- (instancetype)initShadow { - OWSBubbleShapeView *instance = [OWSBubbleShapeView new]; - instance.mode = OWSBubbleShapeViewMode_Draw; - return instance; + self = [super init]; + if (!self) { + return self; + } + + self.mode = OWSBubbleShapeViewMode_Shadow; + + [self configure]; + + return self; } -+ (OWSBubbleShapeView *)bubbleShadowView +- (instancetype)initClip { - OWSBubbleShapeView *instance = [OWSBubbleShapeView new]; - instance.mode = OWSBubbleShapeViewMode_Shadow; - return instance; + self = [super init]; + if (!self) { + return self; + } + + self.mode = OWSBubbleShapeViewMode_Clip; + + [self configure]; + + return self; } -+ (OWSBubbleShapeView *)bubbleClipView +- (instancetype)initInnerShadowWithColor:(UIColor *)color radius:(CGFloat)radius opacity:(float)opacity { - OWSBubbleShapeView *instance = [OWSBubbleShapeView new]; - instance.mode = OWSBubbleShapeViewMode_Clip; - return instance; + self = [super init]; + if (!self) { + return self; + } + + self.mode = OWSBubbleShapeViewMode_InnerShadow; + _innerShadowColor = color; + _innerShadowRadius = radius; + _innerShadowOpacity = opacity; + + [self configure]; + [self updateLayers]; + + return self; } - (void)setFillColor:(nullable UIColor *)fillColor @@ -92,6 +126,27 @@ typedef NS_ENUM(NSUInteger, OWSBubbleShapeViewMode) { [self updateLayers]; } +- (void)setInnerShadowColor:(nullable UIColor *)innerShadowColor +{ + _innerShadowColor = innerShadowColor; + + [self updateLayers]; +} + +- (void)setInnerShadowRadius:(CGFloat)innerShadowRadius +{ + _innerShadowRadius = innerShadowRadius; + + [self updateLayers]; +} + +- (void)setInnerShadowOpacity:(float)innerShadowOpacity +{ + _innerShadowOpacity = innerShadowOpacity; + + [self updateLayers]; +} + - (void)setFrame:(CGRect)frame { BOOL didChange = !CGRectEqualToRect(self.frame, frame); @@ -188,6 +243,32 @@ typedef NS_ENUM(NSUInteger, OWSBubbleShapeViewMode) { self.maskLayer.path = bezierPath.CGPath; self.layer.mask = self.maskLayer; break; + case OWSBubbleShapeViewMode_InnerShadow: { + self.maskLayer.path = bezierPath.CGPath; + self.layer.mask = self.maskLayer; + + // Inner shadow. + // This should usually not be visible; it is used to distinguish + // profile pics from the background if they are similar. + self.shapeLayer.frame = self.bounds; + self.shapeLayer.masksToBounds = YES; + CGRect shadowBounds = self.bounds; + UIBezierPath *shadowPath = [bezierPath copy]; + // This can be any value large enough to cast a sufficiently large shadow. + CGFloat shadowInset = -(self.innerShadowRadius * 4.f); + [shadowPath + appendPath:[UIBezierPath bezierPathWithRect:CGRectInset(shadowBounds, shadowInset, shadowInset)]]; + // This can be any color since the fill should be clipped. + self.shapeLayer.fillColor = UIColor.blackColor.CGColor; + self.shapeLayer.path = shadowPath.CGPath; + self.shapeLayer.fillRule = kCAFillRuleEvenOdd; + self.shapeLayer.shadowColor = self.innerShadowColor.CGColor; + self.shapeLayer.shadowRadius = self.innerShadowRadius; + self.shapeLayer.shadowOpacity = self.innerShadowOpacity; + self.shapeLayer.shadowOffset = CGSizeZero; + + break; + } } [CATransaction commit]; diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m index 0e0c2aa79..04cf543f5 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageBubbleView.m @@ -352,13 +352,14 @@ const UIDataDetectorTypes kOWSAllowedDataDetectorTypes if (self.hasBodyMediaWithThumbnail) { [self.stackView addArrangedSubview:bodyMediaView]; - OWSBubbleShapeView *strokeView = [OWSBubbleShapeView bubbleDrawView]; - strokeView.strokeThickness = CGHairlineWidth(); - strokeView.strokeColor = (Theme.isDarkThemeEnabled ? [UIColor colorWithWhite:1.f alpha:0.2f] - : [UIColor colorWithWhite:0.f alpha:0.2f]); - [bodyMediaView addSubview:strokeView]; - [self.bubbleView addPartnerView:strokeView]; - [self.viewConstraints addObjectsFromArray:[strokeView ows_autoPinToSuperviewEdges]]; + OWSBubbleShapeView *innerShadowView = [[OWSBubbleShapeView alloc] + initInnerShadowWithColor:(Theme.isDarkThemeEnabled ? UIColor.ows_whiteColor + : UIColor.ows_blackColor) + radius:0.5f + opacity:0.15f]; + [bodyMediaView addSubview:innerShadowView]; + [self.bubbleView addPartnerView:innerShadowView]; + [self.viewConstraints addObjectsFromArray:[innerShadowView ows_autoPinToSuperviewEdges]]; } else { OWSAssertDebug(self.cellType == OWSMessageCellType_ContactShare); @@ -496,8 +497,8 @@ const UIDataDetectorTypes kOWSAllowedDataDetectorTypes UIView *proxyView = [UIView new]; [self.stackView addArrangedSubview:proxyView]; - OWSBubbleShapeView *shadowView = [OWSBubbleShapeView bubbleShadowView]; - OWSBubbleShapeView *clipView = [OWSBubbleShapeView bubbleClipView]; + OWSBubbleShapeView *shadowView = [[OWSBubbleShapeView alloc] initShadow]; + OWSBubbleShapeView *clipView = [[OWSBubbleShapeView alloc] initClip]; [self addSubview:shadowView]; [self addSubview:clipView];