Merge branch 'charlesmchen/bubbleCollapse'
This commit is contained in:
commit
43e50e33c2
@ -206,12 +206,17 @@
|
||||
34D2CCD4206294B900CB1A14 /* OWSScreenLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D2CCD3206294B900CB1A14 /* OWSScreenLock.swift */; };
|
||||
34D2CCDA2062E7D000CB1A14 /* OWSScreenLockUI.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D2CCD92062E7D000CB1A14 /* OWSScreenLockUI.m */; };
|
||||
34D2CCD220618B3000CB1A14 /* OWSBackupLazyRestoreJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D2CCD120618B2F00CB1A14 /* OWSBackupLazyRestoreJob.swift */; };
|
||||
34D2CCDF206939B400CB1A14 /* DebugUIMessagesAction.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D2CCDB206939B100CB1A14 /* DebugUIMessagesAction.m */; };
|
||||
34D2CCE0206939B400CB1A14 /* DebugUIMessagesAssetLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D2CCDC206939B200CB1A14 /* DebugUIMessagesAssetLoader.m */; };
|
||||
34D5CCA91EAE3D30005515DB /* AvatarViewHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D5CCA81EAE3D30005515DB /* AvatarViewHelper.m */; };
|
||||
34D8C0271ED3673300188D7C /* DebugUIMessages.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D8C0241ED3673300188D7C /* DebugUIMessages.m */; };
|
||||
34D8C0281ED3673300188D7C /* DebugUITableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D8C0261ED3673300188D7C /* DebugUITableViewController.m */; };
|
||||
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 */; };
|
||||
34DBF007206C3CB200025978 /* OWSBubbleStrokeView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34DBF006206C3CB200025978 /* OWSBubbleStrokeView.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 */; };
|
||||
@ -814,6 +819,11 @@
|
||||
34D2CCD82062E7D000CB1A14 /* OWSScreenLockUI.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSScreenLockUI.h; sourceTree = "<group>"; };
|
||||
34D2CCD92062E7D000CB1A14 /* OWSScreenLockUI.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSScreenLockUI.m; sourceTree = "<group>"; };
|
||||
34D2CCD120618B2F00CB1A14 /* OWSBackupLazyRestoreJob.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWSBackupLazyRestoreJob.swift; sourceTree = "<group>"; };
|
||||
34D2CCDB206939B100CB1A14 /* DebugUIMessagesAction.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DebugUIMessagesAction.m; sourceTree = "<group>"; };
|
||||
34D2CCDC206939B200CB1A14 /* DebugUIMessagesAssetLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DebugUIMessagesAssetLoader.m; sourceTree = "<group>"; };
|
||||
34D2CCDD206939B200CB1A14 /* DebugUIMessagesAction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DebugUIMessagesAction.h; sourceTree = "<group>"; };
|
||||
34D2CCDE206939B400CB1A14 /* DebugUIMessagesAssetLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DebugUIMessagesAssetLoader.h; sourceTree = "<group>"; };
|
||||
34D2CCE220693A1700CB1A14 /* DebugUIMessagesUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DebugUIMessagesUtils.h; sourceTree = "<group>"; };
|
||||
34D5CCA71EAE3D30005515DB /* AvatarViewHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AvatarViewHelper.h; sourceTree = "<group>"; };
|
||||
34D5CCA81EAE3D30005515DB /* AvatarViewHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AvatarViewHelper.m; sourceTree = "<group>"; };
|
||||
34D8C0231ED3673300188D7C /* DebugUIMessages.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DebugUIMessages.h; sourceTree = "<group>"; };
|
||||
@ -828,6 +838,12 @@
|
||||
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>"; };
|
||||
34DBF005206C3CB100025978 /* OWSBubbleStrokeView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSBubbleStrokeView.h; sourceTree = "<group>"; };
|
||||
34DBF006206C3CB200025978 /* OWSBubbleStrokeView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSBubbleStrokeView.m; 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>"; };
|
||||
@ -1638,6 +1654,10 @@
|
||||
34D1F0971F867BFC0066283D /* ConversationViewCell.m */,
|
||||
34D1F0B81F8800D90066283D /* OWSAudioMessageView.h */,
|
||||
34D1F0B91F8800D90066283D /* OWSAudioMessageView.m */,
|
||||
34DBF005206C3CB100025978 /* OWSBubbleStrokeView.h */,
|
||||
34DBF006206C3CB200025978 /* OWSBubbleStrokeView.m */,
|
||||
34DBF002206BD5A500025978 /* OWSBubbleView.h */,
|
||||
34DBF001206BD5A500025978 /* OWSBubbleView.m */,
|
||||
34D1F09A1F867BFC0066283D /* OWSContactOffersCell.h */,
|
||||
34D1F09B1F867BFC0066283D /* OWSContactOffersCell.m */,
|
||||
34D1F09C1F867BFC0066283D /* OWSExpirableMessageView.h */,
|
||||
@ -1647,6 +1667,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 */,
|
||||
@ -1676,6 +1698,11 @@
|
||||
45B27B852037FFB400A539DF /* DebugUIFileBrowser.swift */,
|
||||
34D8C0231ED3673300188D7C /* DebugUIMessages.h */,
|
||||
34D8C0241ED3673300188D7C /* DebugUIMessages.m */,
|
||||
34D2CCDD206939B200CB1A14 /* DebugUIMessagesAction.h */,
|
||||
34D2CCDB206939B100CB1A14 /* DebugUIMessagesAction.m */,
|
||||
34D2CCDE206939B400CB1A14 /* DebugUIMessagesAssetLoader.h */,
|
||||
34D2CCDC206939B200CB1A14 /* DebugUIMessagesAssetLoader.m */,
|
||||
34D2CCE220693A1700CB1A14 /* DebugUIMessagesUtils.h */,
|
||||
341F2C0D1F2B8AE700D07D6B /* DebugUIMisc.h */,
|
||||
341F2C0E1F2B8AE700D07D6B /* DebugUIMisc.m */,
|
||||
457C87B72032645C008D52D6 /* DebugUINotifications.swift */,
|
||||
@ -3113,6 +3140,7 @@
|
||||
3461293E1FD1D72B00532771 /* ExperienceUpgradeFinder.swift in Sources */,
|
||||
34D1F0BD1F8D108C0066283D /* AttachmentUploadView.m in Sources */,
|
||||
452EC6DF205E9E30000E787C /* MediaGalleryViewController.swift in Sources */,
|
||||
34DBF007206C3CB200025978 /* OWSBubbleStrokeView.m in Sources */,
|
||||
34D1F0BA1F8800D90066283D /* OWSAudioMessageView.m in Sources */,
|
||||
34D8C02B1ED3685800188D7C /* DebugUIContacts.m in Sources */,
|
||||
45C9DEB81DF4E35A0065CA84 /* WebRTCCallMessageHandler.swift in Sources */,
|
||||
@ -3137,6 +3165,7 @@
|
||||
34CCAF381F0C0599004084F4 /* AppUpdateNag.m in Sources */,
|
||||
EF764C351DB67CC5000D9A87 /* UIViewController+Permissions.m in Sources */,
|
||||
45CD81EF1DC030E7004C9430 /* SyncPushTokensJob.swift in Sources */,
|
||||
34D2CCE0206939B400CB1A14 /* DebugUIMessagesAssetLoader.m in Sources */,
|
||||
45794E861E00620000066731 /* CallUIAdapter.swift in Sources */,
|
||||
340FC8BA204DAC8D007AEB0F /* FingerprintViewScanController.m in Sources */,
|
||||
4585C4681ED8F8D200896AEA /* SafetyNumberConfirmationAlert.swift in Sources */,
|
||||
@ -3200,6 +3229,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 */,
|
||||
@ -3231,6 +3261,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 */,
|
||||
@ -3246,6 +3277,7 @@
|
||||
3461299C1FD1EA9E00532771 /* NotificationsManager.m in Sources */,
|
||||
4521C3C01F59F3BA00B4C582 /* TextFieldHelper.swift in Sources */,
|
||||
34B3F87E1E8DF1700035BE1A /* InboxTableViewCell.m in Sources */,
|
||||
34D2CCDF206939B400CB1A14 /* DebugUIMessagesAction.m in Sources */,
|
||||
340FC8AC204DAC8D007AEB0F /* PrivacySettingsTableViewController.m in Sources */,
|
||||
34D2CCD4206294B900CB1A14 /* OWSScreenLock.swift in Sources */,
|
||||
340FC8C5204DE223007AEB0F /* DebugUIBackup.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"
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
@ -19,7 +19,6 @@ typedef void (^AttachmentStateBlock)(BOOL isAttachmentReady);
|
||||
@interface AttachmentUploadView : UIView
|
||||
|
||||
- (instancetype)initWithAttachment:(TSAttachmentStream *)attachment
|
||||
superview:(UIView *)superview
|
||||
attachmentStateCallback:(AttachmentStateBlock _Nullable)attachmentStateCallback;
|
||||
|
||||
@end
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "AttachmentUploadView.h"
|
||||
@ -32,21 +32,16 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
@implementation AttachmentUploadView
|
||||
|
||||
- (instancetype)initWithAttachment:(TSAttachmentStream *)attachment
|
||||
superview:(UIView *)superview
|
||||
attachmentStateCallback:(AttachmentStateBlock _Nullable)attachmentStateCallback
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self) {
|
||||
OWSAssert(attachment);
|
||||
OWSAssert(superview);
|
||||
|
||||
self.attachment = attachment;
|
||||
self.attachmentStateCallback = attachmentStateCallback;
|
||||
|
||||
[superview addSubview:self];
|
||||
[self autoPinToSuperviewEdges];
|
||||
|
||||
_bezierPathView = [OWSBezierPathView new];
|
||||
self.bezierPathView.configureShapeLayerBlock = ^(CAShapeLayer *layer, CGRect bounds) {
|
||||
layer.path = [UIBezierPath bezierPathWithRect:bounds].CGPath;
|
||||
|
||||
@ -0,0 +1,20 @@
|
||||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class OWSBubbleView;
|
||||
|
||||
@interface OWSBubbleStrokeView : UIView
|
||||
|
||||
@property (nonatomic, weak) OWSBubbleView *bubbleView;
|
||||
|
||||
@property (nonatomic) UIColor *strokeColor;
|
||||
@property (nonatomic) CGFloat strokeThickness;
|
||||
|
||||
- (void)updateLayers;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@ -0,0 +1,114 @@
|
||||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OWSBubbleStrokeView.h"
|
||||
#import "OWSBubbleView.h"
|
||||
#import <SignalMessaging/UIView+OWS.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface OWSBubbleStrokeView ()
|
||||
|
||||
@property (nonatomic) CAShapeLayer *shapeLayer;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation OWSBubbleStrokeView
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
if (!self) {
|
||||
return self;
|
||||
}
|
||||
|
||||
self.opaque = NO;
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
|
||||
self.shapeLayer = [CAShapeLayer new];
|
||||
[self.layer addSublayer:self.shapeLayer];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setStrokeColor:(UIColor *)strokeColor
|
||||
{
|
||||
_strokeColor = strokeColor;
|
||||
|
||||
[self updateLayers];
|
||||
}
|
||||
|
||||
- (void)setStrokeThickness:(CGFloat)strokeThickness
|
||||
{
|
||||
_strokeThickness = strokeThickness;
|
||||
|
||||
[self updateLayers];
|
||||
}
|
||||
|
||||
- (void)setFrame:(CGRect)frame
|
||||
{
|
||||
BOOL didChange = !CGRectEqualToRect(self.frame, frame);
|
||||
|
||||
[super setFrame:frame];
|
||||
|
||||
if (didChange || !self.shapeLayer) {
|
||||
[self updateLayers];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setBounds:(CGRect)bounds
|
||||
{
|
||||
BOOL didChange = !CGRectEqualToRect(self.bounds, bounds);
|
||||
|
||||
[super setBounds:bounds];
|
||||
|
||||
if (didChange || !self.shapeLayer) {
|
||||
[self updateLayers];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setCenter:(CGPoint)center
|
||||
{
|
||||
[super setCenter:center];
|
||||
|
||||
[self updateLayers];
|
||||
}
|
||||
|
||||
- (void)updateLayers
|
||||
{
|
||||
OWSAssert(self.shapeLayer);
|
||||
|
||||
// Don't fill the shape layer; we just want a stroke around the border.
|
||||
self.shapeLayer.fillColor = [UIColor clearColor].CGColor;
|
||||
|
||||
self.clipsToBounds = YES;
|
||||
|
||||
if (!self.bubbleView) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.shapeLayer.strokeColor = self.strokeColor.CGColor;
|
||||
self.shapeLayer.lineWidth = self.strokeThickness;
|
||||
self.shapeLayer.zPosition = 100.f;
|
||||
|
||||
UIBezierPath *bezierPath = [UIBezierPath new];
|
||||
|
||||
UIBezierPath *boundsBezierPath = [UIBezierPath bezierPathWithRect:self.bounds];
|
||||
[bezierPath appendPath:boundsBezierPath];
|
||||
|
||||
UIBezierPath *bubbleBezierPath = [self.bubbleView maskPath];
|
||||
// We need to convert between coordinate systems using layers, not views.
|
||||
CGPoint bubbleOffset = [self.layer convertPoint:CGPointZero fromLayer:self.bubbleView.layer];
|
||||
CGAffineTransform transform = CGAffineTransformMakeTranslation(bubbleOffset.x, bubbleOffset.y);
|
||||
[bubbleBezierPath applyTransform:transform];
|
||||
[bezierPath appendPath:bubbleBezierPath];
|
||||
|
||||
self.shapeLayer.path = bezierPath.CGPath;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@ -0,0 +1,32 @@
|
||||
//
|
||||
// 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;
|
||||
|
||||
@class OWSBubbleStrokeView;
|
||||
|
||||
@interface OWSBubbleView : UIView
|
||||
|
||||
@property (nonatomic, weak, nullable) OWSBubbleStrokeView *bubbleStrokeView;
|
||||
|
||||
@property (nonatomic) BOOL isOutgoing;
|
||||
@property (nonatomic) BOOL hideTail;
|
||||
@property (nonatomic) BOOL isTruncated;
|
||||
|
||||
@property (nonatomic, nullable) UIColor *bubbleColor;
|
||||
|
||||
- (UIBezierPath *)maskPath;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@ -0,0 +1,205 @@
|
||||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OWSBubbleView.h"
|
||||
#import "OWSBubbleStrokeView.h"
|
||||
#import <SignalMessaging/UIView+OWS.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
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;
|
||||
|
||||
@interface OWSBubbleView ()
|
||||
|
||||
@property (nonatomic) CAShapeLayer *maskLayer;
|
||||
@property (nonatomic) CAShapeLayer *shapeLayer;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation OWSBubbleView
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
if (!self) {
|
||||
return self;
|
||||
}
|
||||
|
||||
self.shapeLayer = [CAShapeLayer new];
|
||||
[self.layer addSublayer:self.shapeLayer];
|
||||
|
||||
self.maskLayer = [CAShapeLayer new];
|
||||
self.layer.mask = self.maskLayer;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setIsOutgoing:(BOOL)isOutgoing
|
||||
{
|
||||
BOOL didChange = _isOutgoing != isOutgoing;
|
||||
|
||||
_isOutgoing = isOutgoing;
|
||||
|
||||
if (didChange || !self.shapeLayer) {
|
||||
[self updateLayers];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setHideTail:(BOOL)hideTail
|
||||
{
|
||||
BOOL didChange = _hideTail != hideTail;
|
||||
|
||||
_hideTail = hideTail;
|
||||
|
||||
if (didChange || !self.shapeLayer) {
|
||||
[self updateLayers];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setIsTruncated:(BOOL)isTruncated
|
||||
{
|
||||
BOOL didChange = _isTruncated != isTruncated;
|
||||
|
||||
_isTruncated = isTruncated;
|
||||
|
||||
if (didChange || !self.shapeLayer) {
|
||||
[self updateLayers];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setFrame:(CGRect)frame
|
||||
{
|
||||
// We only need to update our layers if the _size_ of this view
|
||||
// changes since the contents of the layers are in local coordinates.
|
||||
BOOL didChangeSize = !CGSizeEqualToSize(self.frame.size, frame.size);
|
||||
|
||||
[super setFrame:frame];
|
||||
|
||||
if (didChangeSize || !self.shapeLayer) {
|
||||
[self updateLayers];
|
||||
}
|
||||
|
||||
// We always need to inform the "bubble stroke view" (if any) if our
|
||||
// frame/bounds/center changes. Its contents are not in local coordinates.
|
||||
[self.bubbleStrokeView updateLayers];
|
||||
}
|
||||
|
||||
- (void)setBounds:(CGRect)bounds
|
||||
{
|
||||
// We only need to update our layers if the _size_ of this view
|
||||
// changes since the contents of the layers are in local coordinates.
|
||||
BOOL didChangeSize = !CGSizeEqualToSize(self.bounds.size, bounds.size);
|
||||
|
||||
[super setBounds:bounds];
|
||||
|
||||
if (didChangeSize || !self.shapeLayer) {
|
||||
[self updateLayers];
|
||||
}
|
||||
|
||||
// We always need to inform the "bubble stroke view" (if any) if our
|
||||
// frame/bounds/center changes. Its contents are not in local coordinates.
|
||||
[self.bubbleStrokeView updateLayers];
|
||||
}
|
||||
|
||||
- (void)setCenter:(CGPoint)center
|
||||
{
|
||||
[super setCenter:center];
|
||||
|
||||
// We always need to inform the "bubble stroke view" (if any) if our
|
||||
// frame/bounds/center changes. Its contents are not in local coordinates.
|
||||
[self.bubbleStrokeView updateLayers];
|
||||
}
|
||||
|
||||
- (void)setBubbleColor:(nullable UIColor *)bubbleColor
|
||||
{
|
||||
_bubbleColor = bubbleColor;
|
||||
|
||||
if (!self.shapeLayer) {
|
||||
[self updateLayers];
|
||||
}
|
||||
self.shapeLayer.fillColor = bubbleColor.CGColor;
|
||||
}
|
||||
|
||||
- (void)updateLayers
|
||||
{
|
||||
OWSAssert(self.maskLayer);
|
||||
OWSAssert(self.shapeLayer);
|
||||
|
||||
UIBezierPath *bezierPath = [self maskPath];
|
||||
|
||||
self.shapeLayer.fillColor = self.bubbleColor.CGColor;
|
||||
self.shapeLayer.path = bezierPath.CGPath;
|
||||
self.maskLayer.path = bezierPath.CGPath;
|
||||
}
|
||||
|
||||
- (UIBezierPath *)maskPath
|
||||
{
|
||||
return [self.class maskPathForSize:self.bounds.size
|
||||
isOutgoing:self.isOutgoing
|
||||
hideTail:self.hideTail
|
||||
isTruncated:self.isTruncated
|
||||
isRTL:self.isRTL];
|
||||
}
|
||||
|
||||
+ (UIBezierPath *)maskPathForSize:(CGSize)size
|
||||
isOutgoing:(BOOL)isOutgoing
|
||||
hideTail:(BOOL)hideTail
|
||||
isTruncated:(BOOL)isTruncated
|
||||
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)];
|
||||
|
||||
if (!hideTail) {
|
||||
// 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;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
@ -2274,6 +2274,14 @@ typedef enum : NSUInteger {
|
||||
|
||||
- (void)scrollDownButtonTapped
|
||||
{
|
||||
#ifdef DEBUG
|
||||
CGPoint contentOffset = self.collectionView.contentOffset;
|
||||
contentOffset.y += self.collectionView.height
|
||||
- (self.collectionView.contentInset.top + self.collectionView.contentInset.bottom);
|
||||
[self.collectionView setContentOffset:contentOffset animated:NO];
|
||||
return;
|
||||
#endif
|
||||
|
||||
NSIndexPath *indexPathOfUnreadMessagesIndicator = [self indexPathOfUnreadMessagesIndicator];
|
||||
if (indexPathOfUnreadMessagesIndicator != nil) {
|
||||
NSInteger unreadRow = indexPathOfUnreadMessagesIndicator.row;
|
||||
@ -4455,8 +4463,10 @@ typedef enum : NSUInteger {
|
||||
// Update the "shouldShowDate" property of the view items.
|
||||
OWSInteractionType lastInteractionType = OWSInteractionType_Unknown;
|
||||
MessageRecipientStatus lastRecipientStatus = MessageRecipientStatusUploading;
|
||||
NSString *_Nullable lastIncomingSenderId = nil;
|
||||
for (ConversationViewItem *viewItem in viewItems.reverseObjectEnumerator) {
|
||||
BOOL shouldHideRecipientStatus = NO;
|
||||
BOOL shouldHideBubbleTail = NO;
|
||||
OWSInteractionType interactionType = viewItem.interaction.interactionType;
|
||||
|
||||
if (interactionType == OWSInteractionType_OutgoingMessage) {
|
||||
@ -4465,14 +4475,23 @@ typedef enum : NSUInteger {
|
||||
[MessageRecipientStatusUtils recipientStatusWithOutgoingMessage:outgoingMessage];
|
||||
|
||||
if (outgoingMessage.messageState == TSOutgoingMessageStateUnsent) {
|
||||
// always sow "failed to send" status
|
||||
// always show "failed to send" status
|
||||
shouldHideRecipientStatus = NO;
|
||||
} else {
|
||||
shouldHideRecipientStatus
|
||||
= (interactionType == lastInteractionType && recipientStatus == lastRecipientStatus);
|
||||
}
|
||||
|
||||
shouldHideBubbleTail = (interactionType == lastInteractionType && recipientStatus == lastRecipientStatus);
|
||||
|
||||
lastRecipientStatus = recipientStatus;
|
||||
} else if (interactionType == OWSInteractionType_IncomingMessage) {
|
||||
TSIncomingMessage *incomingMessage = (TSIncomingMessage *)viewItem.interaction;
|
||||
NSString *incomingSenderId = incomingMessage.authorId;
|
||||
OWSAssert(incomingSenderId.length > 0);
|
||||
shouldHideBubbleTail = (interactionType == lastInteractionType &&
|
||||
[NSObject isNullableObject:lastIncomingSenderId equalTo:incomingSenderId]);
|
||||
lastIncomingSenderId = incomingSenderId;
|
||||
}
|
||||
lastInteractionType = interactionType;
|
||||
|
||||
@ -4483,6 +4502,7 @@ typedef enum : NSUInteger {
|
||||
[rowsThatChangedSize addObject:@(viewItem.previousRow)];
|
||||
}
|
||||
viewItem.shouldHideRecipientStatus = shouldHideRecipientStatus;
|
||||
viewItem.shouldHideBubbleTail = shouldHideBubbleTail;
|
||||
}
|
||||
|
||||
self.viewItems = viewItems;
|
||||
|
||||
@ -43,9 +43,10 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType);
|
||||
@property (nonatomic, readonly) TSInteraction *interaction;
|
||||
|
||||
@property (nonatomic, readonly) BOOL isGroupThread;
|
||||
@property (nonatomic, readonly) BOOL hasText;
|
||||
@property (nonatomic, readonly) BOOL hasBodyText;
|
||||
@property (nonatomic) BOOL shouldShowDate;
|
||||
@property (nonatomic) BOOL shouldHideRecipientStatus;
|
||||
@property (nonatomic) BOOL shouldHideBubbleTail;
|
||||
|
||||
@property (nonatomic) NSInteger row;
|
||||
// During updates, we sometimes need the previous row index
|
||||
@ -79,7 +80,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType);
|
||||
|
||||
// These methods only apply to text & attachment messages.
|
||||
- (OWSMessageCellType)messageCellType;
|
||||
- (nullable DisplayableText *)displayableText;
|
||||
- (nullable DisplayableText *)displayableBodyText;
|
||||
- (nullable TSAttachmentStream *)attachmentStream;
|
||||
- (nullable TSAttachmentPointer *)attachmentPointer;
|
||||
- (CGSize)mediaSize;
|
||||
|
||||
@ -55,11 +55,10 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
|
||||
|
||||
@property (nonatomic) BOOL hasViewState;
|
||||
@property (nonatomic) OWSMessageCellType messageCellType;
|
||||
@property (nonatomic, nullable) DisplayableText *displayableText;
|
||||
@property (nonatomic, nullable) DisplayableText *displayableBodyText;
|
||||
@property (nonatomic, nullable) TSAttachmentStream *attachmentStream;
|
||||
@property (nonatomic, nullable) TSAttachmentPointer *attachmentPointer;
|
||||
@property (nonatomic) CGSize mediaSize;
|
||||
@property (nonatomic) BOOL hasText;
|
||||
|
||||
@end
|
||||
|
||||
@ -95,7 +94,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
|
||||
|
||||
self.hasViewState = NO;
|
||||
self.messageCellType = OWSMessageCellType_Unknown;
|
||||
self.displayableText = nil;
|
||||
self.displayableBodyText = nil;
|
||||
self.attachmentStream = nil;
|
||||
self.attachmentPointer = nil;
|
||||
self.mediaSize = CGSizeZero;
|
||||
@ -105,6 +104,11 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
|
||||
[self ensureViewState:transaction];
|
||||
}
|
||||
|
||||
- (BOOL)hasBodyText
|
||||
{
|
||||
return _displayableBodyText != nil;
|
||||
}
|
||||
|
||||
- (void)setShouldShowDate:(BOOL)shouldShowDate
|
||||
{
|
||||
if (_shouldShowDate == shouldShowDate) {
|
||||
@ -127,6 +131,17 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
|
||||
[self clearCachedLayoutState];
|
||||
}
|
||||
|
||||
- (void)setShouldHideBubbleTail:(BOOL)shouldHideBubbleTail
|
||||
{
|
||||
if (_shouldHideBubbleTail == shouldHideBubbleTail) {
|
||||
return;
|
||||
}
|
||||
|
||||
_shouldHideBubbleTail = shouldHideBubbleTail;
|
||||
|
||||
[self clearCachedLayoutState];
|
||||
}
|
||||
|
||||
- (void)clearCachedLayoutState
|
||||
{
|
||||
self.cachedCellSize = nil;
|
||||
@ -264,7 +279,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
|
||||
|
||||
// TODO: Now that we're caching the displayable text on the view items,
|
||||
// I don't think we need this cache any more.
|
||||
- (NSCache *)displayableTextCache
|
||||
- (NSCache *)displayableBodyTextCache
|
||||
{
|
||||
static NSCache *cache = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
@ -276,44 +291,45 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
|
||||
return cache;
|
||||
}
|
||||
|
||||
- (DisplayableText *)displayableTextForText:(NSString *)text interactionId:(NSString *)interactionId
|
||||
- (DisplayableText *)displayableBodyTextForText:(NSString *)text interactionId:(NSString *)interactionId
|
||||
{
|
||||
OWSAssert(text);
|
||||
OWSAssert(interactionId.length > 0);
|
||||
|
||||
return [self displayableTextForInteractionId:interactionId
|
||||
textBlock:^{
|
||||
return text;
|
||||
}];
|
||||
return [self displayableBodyTextForInteractionId:interactionId
|
||||
textBlock:^{
|
||||
return text;
|
||||
}];
|
||||
}
|
||||
|
||||
- (DisplayableText *)displayableTextForAttachmentStream:(TSAttachmentStream *)attachmentStream
|
||||
interactionId:(NSString *)interactionId
|
||||
- (DisplayableText *)displayableBodyTextForOversizeTextAttachment:(TSAttachmentStream *)attachmentStream
|
||||
interactionId:(NSString *)interactionId
|
||||
{
|
||||
OWSAssert(attachmentStream);
|
||||
OWSAssert(interactionId.length > 0);
|
||||
|
||||
return [self displayableTextForInteractionId:interactionId
|
||||
textBlock:^{
|
||||
NSData *textData = [NSData dataWithContentsOfURL:attachmentStream.mediaURL];
|
||||
NSString *text =
|
||||
[[NSString alloc] initWithData:textData encoding:NSUTF8StringEncoding];
|
||||
return text;
|
||||
}];
|
||||
return
|
||||
[self displayableBodyTextForInteractionId:interactionId
|
||||
textBlock:^{
|
||||
NSData *textData = [NSData dataWithContentsOfURL:attachmentStream.mediaURL];
|
||||
NSString *text =
|
||||
[[NSString alloc] initWithData:textData encoding:NSUTF8StringEncoding];
|
||||
return text;
|
||||
}];
|
||||
}
|
||||
|
||||
- (DisplayableText *)displayableTextForInteractionId:(NSString *)interactionId
|
||||
textBlock:(NSString * (^_Nonnull)(void))textBlock
|
||||
- (DisplayableText *)displayableBodyTextForInteractionId:(NSString *)interactionId
|
||||
textBlock:(NSString * (^_Nonnull)(void))textBlock
|
||||
{
|
||||
OWSAssert(interactionId.length > 0);
|
||||
|
||||
DisplayableText *_Nullable displayableText = [[self displayableTextCache] objectForKey:interactionId];
|
||||
if (!displayableText) {
|
||||
DisplayableText *_Nullable displayableBodyText = [[self displayableBodyTextCache] objectForKey:interactionId];
|
||||
if (!displayableBodyText) {
|
||||
NSString *text = textBlock();
|
||||
displayableText = [DisplayableText displayableText:text];
|
||||
[[self displayableTextCache] setObject:displayableText forKey:interactionId];
|
||||
displayableBodyText = [DisplayableText displayableText:text];
|
||||
[[self displayableBodyTextCache] setObject:displayableBodyText forKey:interactionId];
|
||||
}
|
||||
return displayableText;
|
||||
return displayableBodyText;
|
||||
}
|
||||
|
||||
- (nullable TSAttachment *)firstAttachmentIfAnyOfMessage:(TSMessage *)message
|
||||
@ -353,9 +369,8 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
|
||||
|
||||
if ([attachment.contentType isEqualToString:OWSMimeTypeOversizeTextMessage]) {
|
||||
self.messageCellType = OWSMessageCellType_OversizeTextMessage;
|
||||
self.displayableText =
|
||||
[self displayableTextForAttachmentStream:self.attachmentStream interactionId:message.uniqueId];
|
||||
self.hasText = YES;
|
||||
self.displayableBodyText = [self displayableBodyTextForOversizeTextAttachment:self.attachmentStream
|
||||
interactionId:message.uniqueId];
|
||||
} else if ([self.attachmentStream isAnimated] || [self.attachmentStream isImage] ||
|
||||
[self.attachmentStream isVideo]) {
|
||||
if ([self.attachmentStream isAnimated]) {
|
||||
@ -392,16 +407,20 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
|
||||
}
|
||||
}
|
||||
|
||||
// Ignore message body for oversize text attachments.
|
||||
if (message.body.length > 0) {
|
||||
self.hasText = YES;
|
||||
if (self.hasBodyText) {
|
||||
OWSFail(@"%@ oversize text message has unexpected caption.", self.logTag);
|
||||
}
|
||||
|
||||
// If we haven't already assigned an attachment type at this point, message.body isn't a caption,
|
||||
// it's a stand-alone text message.
|
||||
if (self.messageCellType == OWSMessageCellType_Unknown) {
|
||||
OWSAssert(message.attachmentIds.count == 0);
|
||||
self.messageCellType = OWSMessageCellType_TextMessage;
|
||||
}
|
||||
self.displayableText = [self displayableTextForText:message.body interactionId:message.uniqueId];
|
||||
OWSAssert(self.displayableText);
|
||||
self.displayableBodyText = [self displayableBodyTextForText:message.body interactionId:message.uniqueId];
|
||||
OWSAssert(self.displayableBodyText);
|
||||
}
|
||||
|
||||
if (self.messageCellType == OWSMessageCellType_Unknown) {
|
||||
@ -409,8 +428,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
|
||||
// are rendered like empty text messages, but without any interactivity.
|
||||
DDLogWarn(@"%@ Treating unknown message as empty text message: %@", self.logTag, message.description);
|
||||
self.messageCellType = OWSMessageCellType_TextMessage;
|
||||
self.hasText = YES;
|
||||
self.displayableText = [[DisplayableText alloc] initWithFullText:@"" displayText:@"" isTextTruncated:NO];
|
||||
self.displayableBodyText = [[DisplayableText alloc] initWithFullText:@"" displayText:@"" isTextTruncated:NO];
|
||||
}
|
||||
}
|
||||
|
||||
@ -421,16 +439,16 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
|
||||
return _messageCellType;
|
||||
}
|
||||
|
||||
- (nullable DisplayableText *)displayableText
|
||||
- (nullable DisplayableText *)displayableBodyText
|
||||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
OWSAssert(self.hasViewState);
|
||||
|
||||
OWSAssert(_displayableText);
|
||||
OWSAssert(_displayableText.displayText);
|
||||
OWSAssert(_displayableText.fullText);
|
||||
OWSAssert(_displayableBodyText);
|
||||
OWSAssert(_displayableBodyText.displayText);
|
||||
OWSAssert(_displayableBodyText.fullText);
|
||||
|
||||
return _displayableText;
|
||||
return _displayableBodyText;
|
||||
}
|
||||
|
||||
- (nullable TSAttachmentStream *)attachmentStream
|
||||
@ -533,13 +551,13 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
|
||||
- (BOOL)canPerformAction:(SEL)action
|
||||
{
|
||||
if (action == self.copyTextActionSelector) {
|
||||
return [self hasTextActionContent];
|
||||
return [self hasBodyTextActionContent];
|
||||
} else if (action == self.copyMediaActionSelector) {
|
||||
return [self hasMediaActionContent];
|
||||
} else if (action == self.saveMediaActionSelector) {
|
||||
return [self canSaveMedia];
|
||||
} else if (action == self.shareTextActionSelector) {
|
||||
return [self hasTextActionContent];
|
||||
return [self hasBodyTextActionContent];
|
||||
} else if (action == self.shareMediaActionSelector) {
|
||||
return [self hasMediaActionContent];
|
||||
} else if (action == self.deleteActionSelector) {
|
||||
@ -561,8 +579,8 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
|
||||
case OWSMessageCellType_Audio:
|
||||
case OWSMessageCellType_Video:
|
||||
case OWSMessageCellType_GenericAttachment: {
|
||||
OWSAssert(self.displayableText);
|
||||
[UIPasteboard.generalPasteboard setString:self.displayableText.fullText];
|
||||
OWSAssert(self.displayableBodyText);
|
||||
[UIPasteboard.generalPasteboard setString:self.displayableBodyText.fullText];
|
||||
break;
|
||||
}
|
||||
case OWSMessageCellType_DownloadingAttachment: {
|
||||
@ -620,8 +638,8 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
|
||||
case OWSMessageCellType_Audio:
|
||||
case OWSMessageCellType_Video:
|
||||
case OWSMessageCellType_GenericAttachment: {
|
||||
OWSAssert(self.displayableText);
|
||||
[AttachmentSharing showShareUIForText:self.displayableText.fullText];
|
||||
OWSAssert(self.displayableBodyText);
|
||||
[AttachmentSharing showShareUIForText:self.displayableBodyText.fullText];
|
||||
break;
|
||||
}
|
||||
case OWSMessageCellType_DownloadingAttachment: {
|
||||
@ -728,9 +746,9 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
|
||||
[self.interaction remove];
|
||||
}
|
||||
|
||||
- (BOOL)hasTextActionContent
|
||||
- (BOOL)hasBodyTextActionContent
|
||||
{
|
||||
return self.hasText && self.displayableText.fullText.length > 0;
|
||||
return self.hasBodyText && self.displayableBodyText.fullText.length > 0;
|
||||
}
|
||||
|
||||
- (BOOL)hasMediaActionContent
|
||||
|
||||
@ -1,13 +1,11 @@
|
||||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "DebugUIPage.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class TSThread;
|
||||
|
||||
@interface DebugUIMessages : DebugUIPage
|
||||
|
||||
@end
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
62
Signal/src/ViewControllers/DebugUI/DebugUIMessagesAction.h
Normal file
62
Signal/src/ViewControllers/DebugUI/DebugUIMessagesAction.h
Normal file
@ -0,0 +1,62 @@
|
||||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "DebugUIMessagesUtils.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface DebugUIMessagesAction : NSObject
|
||||
|
||||
@property (nonatomic) NSString *label;
|
||||
|
||||
- (void)prepareAndPerformNTimes:(NSUInteger)count;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@interface DebugUIMessagesSingleAction : DebugUIMessagesAction
|
||||
|
||||
+ (DebugUIMessagesAction *)actionWithLabel:(NSString *)label
|
||||
staggeredActionBlock:(StaggeredActionBlock)staggeredActionBlock;
|
||||
|
||||
+ (DebugUIMessagesAction *)actionWithLabel:(NSString *)label
|
||||
unstaggeredActionBlock:(UnstaggeredActionBlock)unstaggeredActionBlock;
|
||||
|
||||
+ (DebugUIMessagesAction *)actionWithLabel:(NSString *)label
|
||||
staggeredActionBlock:(StaggeredActionBlock)staggeredActionBlock
|
||||
prepareBlock:(ActionPrepareBlock)prepareBlock;
|
||||
|
||||
+ (DebugUIMessagesAction *)actionWithLabel:(NSString *)label
|
||||
unstaggeredActionBlock:(UnstaggeredActionBlock)unstaggeredActionBlock
|
||||
prepareBlock:(ActionPrepareBlock)prepareBlock;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
typedef NS_ENUM(NSUInteger, SubactionMode) {
|
||||
SubactionMode_Random = 0,
|
||||
SubactionMode_Ordered,
|
||||
};
|
||||
|
||||
@interface DebugUIMessagesGroupAction : DebugUIMessagesAction
|
||||
|
||||
@property (nonatomic, readonly) SubactionMode subactionMode;
|
||||
@property (nonatomic, readonly, nullable) NSArray<DebugUIMessagesAction *> *subactions;
|
||||
|
||||
// Given a group of subactions, perform a single random subaction each time.
|
||||
+ (DebugUIMessagesAction *)randomGroupActionWithLabel:(NSString *)label
|
||||
subactions:(NSArray<DebugUIMessagesAction *> *)subactions;
|
||||
|
||||
// Given a group of subactions, perform the subactions in order.
|
||||
//
|
||||
// If prepareAndPerformNTimes: is called with count == subactions.count, all of the subactions
|
||||
// are performed exactly once.
|
||||
+ (DebugUIMessagesAction *)allGroupActionWithLabel:(NSString *)label
|
||||
subactions:(NSArray<DebugUIMessagesAction *> *)subactions;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
288
Signal/src/ViewControllers/DebugUI/DebugUIMessagesAction.m
Normal file
288
Signal/src/ViewControllers/DebugUI/DebugUIMessagesAction.m
Normal file
@ -0,0 +1,288 @@
|
||||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "DebugUIMessagesAction.h"
|
||||
#import <SignalServiceKit/OWSPrimaryStorage.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class DebugUIMessagesSingleAction;
|
||||
|
||||
@interface DebugUIMessagesAction ()
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@interface DebugUIMessagesSingleAction ()
|
||||
|
||||
@property (nonatomic, nullable) ActionPrepareBlock prepareBlock;
|
||||
|
||||
// "Single" actions should have exactly one "staggered" or "unstaggered" action block.
|
||||
@property (nonatomic, nullable) StaggeredActionBlock staggeredActionBlock;
|
||||
@property (nonatomic, nullable) UnstaggeredActionBlock unstaggeredActionBlock;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation DebugUIMessagesAction
|
||||
|
||||
- (DebugUIMessagesSingleAction *)nextActionToPerform
|
||||
{
|
||||
return (DebugUIMessagesSingleAction *)self;
|
||||
}
|
||||
|
||||
- (void)prepare:(ActionSuccessBlock)success failure:(ActionFailureBlock)failure
|
||||
{
|
||||
OWSAssert(success);
|
||||
OWSAssert(failure);
|
||||
|
||||
OWS_ABSTRACT_METHOD();
|
||||
|
||||
success();
|
||||
}
|
||||
|
||||
- (void)prepareAndPerformNTimes:(NSUInteger)count
|
||||
{
|
||||
DDLogInfo(@"%@ %@ prepareAndPerformNTimes: %zd", self.logTag, self.label, count);
|
||||
[DDLog flushLog];
|
||||
|
||||
[self prepare:^{
|
||||
[self performNTimes:count
|
||||
success:^{
|
||||
}
|
||||
failure:^{
|
||||
}];
|
||||
}
|
||||
failure:^{
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)performNTimes:(NSUInteger)countParam success:(ActionSuccessBlock)success failure:(ActionFailureBlock)failure
|
||||
{
|
||||
OWSAssert(success);
|
||||
OWSAssert(failure);
|
||||
|
||||
DDLogInfo(@"%@ %@ performNTimes: %zd", self.logTag, self.label, countParam);
|
||||
[DDLog flushLog];
|
||||
|
||||
if (countParam < 1) {
|
||||
success();
|
||||
return;
|
||||
}
|
||||
|
||||
__block NSUInteger count = countParam;
|
||||
[OWSPrimaryStorage.sharedManager.newDatabaseConnection readWriteWithBlock:^(
|
||||
YapDatabaseReadWriteTransaction *transaction) {
|
||||
NSUInteger batchSize = 0;
|
||||
while (count > 0) {
|
||||
NSUInteger index = count;
|
||||
|
||||
DebugUIMessagesSingleAction *action = [self nextActionToPerform];
|
||||
OWSAssert([action isKindOfClass:[DebugUIMessagesSingleAction class]]);
|
||||
|
||||
if (action.staggeredActionBlock) {
|
||||
OWSAssert(!action.unstaggeredActionBlock);
|
||||
action.staggeredActionBlock(index,
|
||||
transaction,
|
||||
^{
|
||||
dispatch_after(
|
||||
dispatch_time(DISPATCH_TIME_NOW, (int64_t)1.f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
|
||||
DDLogInfo(@"%@ %@ performNTimes success: %zd", self.logTag, self.label, count);
|
||||
[self performNTimes:count - 1 success:success failure:failure];
|
||||
});
|
||||
},
|
||||
failure);
|
||||
|
||||
break;
|
||||
} else {
|
||||
OWSAssert(action.unstaggeredActionBlock);
|
||||
|
||||
// TODO: We could check result for failure.
|
||||
action.unstaggeredActionBlock(index, transaction);
|
||||
|
||||
const NSUInteger kMaxBatchSize = 2500;
|
||||
batchSize++;
|
||||
if (batchSize >= kMaxBatchSize) {
|
||||
dispatch_after(
|
||||
dispatch_time(DISPATCH_TIME_NOW, (int64_t)1.f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
|
||||
DDLogInfo(@"%@ %@ performNTimes success: %zd", self.logTag, self.label, count);
|
||||
[self performNTimes:count - 1 success:success failure:failure];
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
count--;
|
||||
}
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation DebugUIMessagesSingleAction
|
||||
|
||||
+ (DebugUIMessagesAction *)actionWithLabel:(NSString *)label
|
||||
staggeredActionBlock:(StaggeredActionBlock)staggeredActionBlock
|
||||
{
|
||||
OWSAssert(label.length > 0);
|
||||
OWSAssert(staggeredActionBlock);
|
||||
|
||||
DebugUIMessagesSingleAction *instance = [DebugUIMessagesSingleAction new];
|
||||
instance.label = label;
|
||||
instance.staggeredActionBlock = staggeredActionBlock;
|
||||
return instance;
|
||||
}
|
||||
|
||||
+ (DebugUIMessagesAction *)actionWithLabel:(NSString *)label
|
||||
unstaggeredActionBlock:(UnstaggeredActionBlock)unstaggeredActionBlock
|
||||
{
|
||||
OWSAssert(label.length > 0);
|
||||
OWSAssert(unstaggeredActionBlock);
|
||||
|
||||
DebugUIMessagesSingleAction *instance = [DebugUIMessagesSingleAction new];
|
||||
instance.label = label;
|
||||
instance.unstaggeredActionBlock = unstaggeredActionBlock;
|
||||
return instance;
|
||||
}
|
||||
|
||||
+ (DebugUIMessagesAction *)actionWithLabel:(NSString *)label
|
||||
staggeredActionBlock:(StaggeredActionBlock)staggeredActionBlock
|
||||
prepareBlock:(ActionPrepareBlock)prepareBlock
|
||||
{
|
||||
OWSAssert(label.length > 0);
|
||||
OWSAssert(staggeredActionBlock);
|
||||
OWSAssert(prepareBlock);
|
||||
|
||||
DebugUIMessagesSingleAction *instance = [DebugUIMessagesSingleAction new];
|
||||
instance.label = label;
|
||||
instance.staggeredActionBlock = staggeredActionBlock;
|
||||
instance.prepareBlock = prepareBlock;
|
||||
return instance;
|
||||
}
|
||||
|
||||
+ (DebugUIMessagesAction *)actionWithLabel:(NSString *)label
|
||||
unstaggeredActionBlock:(UnstaggeredActionBlock)unstaggeredActionBlock
|
||||
prepareBlock:(ActionPrepareBlock)prepareBlock
|
||||
{
|
||||
OWSAssert(label.length > 0);
|
||||
OWSAssert(unstaggeredActionBlock);
|
||||
OWSAssert(prepareBlock);
|
||||
|
||||
DebugUIMessagesSingleAction *instance = [DebugUIMessagesSingleAction new];
|
||||
instance.label = label;
|
||||
instance.unstaggeredActionBlock = unstaggeredActionBlock;
|
||||
instance.prepareBlock = prepareBlock;
|
||||
return instance;
|
||||
}
|
||||
|
||||
- (void)prepare:(ActionSuccessBlock)success failure:(ActionFailureBlock)failure
|
||||
{
|
||||
OWSAssert(success);
|
||||
OWSAssert(failure);
|
||||
|
||||
if (self.prepareBlock) {
|
||||
self.prepareBlock(success, failure);
|
||||
} else {
|
||||
success();
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@interface DebugUIMessagesGroupAction ()
|
||||
|
||||
@property (nonatomic) SubactionMode subactionMode;
|
||||
@property (nonatomic, nullable) NSArray<DebugUIMessagesAction *> *subactions;
|
||||
@property (nonatomic) NSUInteger subactionIndex;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation DebugUIMessagesGroupAction
|
||||
|
||||
- (DebugUIMessagesSingleAction *)nextActionToPerform
|
||||
{
|
||||
OWSAssert(self.subactions.count > 0);
|
||||
|
||||
switch (self.subactionMode) {
|
||||
case SubactionMode_Random: {
|
||||
DebugUIMessagesAction *subaction = self.subactions[arc4random_uniform((uint32_t)self.subactions.count)];
|
||||
OWSAssert(subaction);
|
||||
return subaction.nextActionToPerform;
|
||||
}
|
||||
case SubactionMode_Ordered: {
|
||||
DebugUIMessagesAction *subaction = self.subactions[self.subactionIndex];
|
||||
OWSAssert(subaction);
|
||||
self.subactionIndex = (self.subactionIndex + 1) % self.subactions.count;
|
||||
return subaction.nextActionToPerform;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)prepare:(ActionSuccessBlock)success failure:(ActionFailureBlock)failure
|
||||
{
|
||||
OWSAssert(success);
|
||||
OWSAssert(failure);
|
||||
|
||||
[DebugUIMessagesGroupAction prepareSubactions:[self.subactions mutableCopy] success:success failure:failure];
|
||||
}
|
||||
|
||||
+ (void)prepareSubactions:(NSMutableArray<DebugUIMessagesAction *> *)unpreparedSubactions
|
||||
success:(ActionSuccessBlock)success
|
||||
failure:(ActionFailureBlock)failure
|
||||
{
|
||||
OWSAssert(success);
|
||||
OWSAssert(failure);
|
||||
|
||||
if (unpreparedSubactions.count < 1) {
|
||||
return success();
|
||||
}
|
||||
|
||||
DebugUIMessagesAction *nextAction = unpreparedSubactions.lastObject;
|
||||
[unpreparedSubactions removeLastObject];
|
||||
DDLogInfo(@"%@ preparing: %@", self.logTag, nextAction.label);
|
||||
[DDLog flushLog];
|
||||
[nextAction prepare:^{
|
||||
[self prepareSubactions:unpreparedSubactions success:success failure:failure];
|
||||
}
|
||||
failure:^{
|
||||
}];
|
||||
}
|
||||
|
||||
+ (DebugUIMessagesAction *)randomGroupActionWithLabel:(NSString *)label
|
||||
subactions:(NSArray<DebugUIMessagesAction *> *)subactions
|
||||
{
|
||||
OWSAssert(label.length > 0);
|
||||
OWSAssert(subactions.count > 0);
|
||||
|
||||
DebugUIMessagesGroupAction *instance = [DebugUIMessagesGroupAction new];
|
||||
instance.label = label;
|
||||
instance.subactions = subactions;
|
||||
instance.subactionMode = SubactionMode_Random;
|
||||
return instance;
|
||||
}
|
||||
|
||||
+ (DebugUIMessagesAction *)allGroupActionWithLabel:(NSString *)label
|
||||
subactions:(NSArray<DebugUIMessagesAction *> *)subactions
|
||||
{
|
||||
OWSAssert(label.length > 0);
|
||||
OWSAssert(subactions.count > 0);
|
||||
|
||||
DebugUIMessagesGroupAction *instance = [DebugUIMessagesGroupAction new];
|
||||
instance.label = label;
|
||||
instance.subactions = subactions;
|
||||
instance.subactionMode = SubactionMode_Ordered;
|
||||
return instance;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@ -0,0 +1,43 @@
|
||||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "DebugUIMessagesUtils.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface DebugUIMessagesAssetLoader : NSObject
|
||||
|
||||
@property (nonatomic) NSString *filename;
|
||||
@property (nonatomic) NSString *mimeType;
|
||||
|
||||
@property (nonatomic) ActionPrepareBlock prepareBlock;
|
||||
|
||||
@property (nonatomic, nullable) NSString *filePath;
|
||||
|
||||
#pragma mark -
|
||||
|
||||
+ (instancetype)jpegInstance;
|
||||
+ (instancetype)gifInstance;
|
||||
+ (instancetype)largeGifInstance;
|
||||
+ (instancetype)mp3Instance;
|
||||
+ (instancetype)mp4Instance;
|
||||
+ (instancetype)compactPortraitPngInstance;
|
||||
+ (instancetype)compactLandscapePngInstance;
|
||||
+ (instancetype)tallPortraitPngInstance;
|
||||
+ (instancetype)wideLandscapePngInstance;
|
||||
+ (instancetype)largePngInstance;
|
||||
+ (instancetype)tinyPngInstance;
|
||||
+ (instancetype)pngInstanceWithSize:(CGSize)size
|
||||
backgroundColor:(UIColor *)backgroundColor
|
||||
textColor:(UIColor *)textColor
|
||||
label:(NSString *)label;
|
||||
+ (instancetype)tinyPdfInstance;
|
||||
+ (instancetype)largePdfInstance;
|
||||
+ (instancetype)missingPngInstance;
|
||||
+ (instancetype)missingPdfInstance;
|
||||
+ (instancetype)oversizeTextInstance;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
523
Signal/src/ViewControllers/DebugUI/DebugUIMessagesAssetLoader.m
Normal file
523
Signal/src/ViewControllers/DebugUI/DebugUIMessagesAssetLoader.m
Normal file
@ -0,0 +1,523 @@
|
||||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "DebugUIMessagesAssetLoader.h"
|
||||
#import <AFNetworking/AFHTTPSessionManager.h>
|
||||
#import <AFNetworking/AFNetworking.h>
|
||||
#import <Curve25519Kit/Randomness.h>
|
||||
#import <SignalServiceKit/MIMETypeUtil.h>
|
||||
#import <SignalServiceKit/OWSFileSystem.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@implementation DebugUIMessagesAssetLoader
|
||||
|
||||
+ (DebugUIMessagesAssetLoader *)fakeAssetLoaderWithUrl:(NSString *)fileUrl mimeType:(NSString *)mimeType
|
||||
{
|
||||
OWSAssert(fileUrl.length > 0);
|
||||
OWSAssert(mimeType.length > 0);
|
||||
|
||||
DebugUIMessagesAssetLoader *instance = [DebugUIMessagesAssetLoader new];
|
||||
instance.mimeType = mimeType;
|
||||
instance.filename = [NSURL URLWithString:fileUrl].lastPathComponent;
|
||||
__weak DebugUIMessagesAssetLoader *weakSelf = instance;
|
||||
instance.prepareBlock = ^(ActionSuccessBlock success, ActionFailureBlock failure) {
|
||||
[weakSelf ensureURLAssetLoaded:fileUrl success:success failure:failure];
|
||||
};
|
||||
return instance;
|
||||
}
|
||||
|
||||
- (void)ensureURLAssetLoaded:(NSString *)fileUrl success:(ActionSuccessBlock)success failure:(ActionFailureBlock)failure
|
||||
{
|
||||
OWSAssert(success);
|
||||
OWSAssert(failure);
|
||||
OWSAssert(self.filename.length > 0);
|
||||
OWSAssert(self.mimeType.length > 0);
|
||||
|
||||
if (self.filePath) {
|
||||
success();
|
||||
return;
|
||||
}
|
||||
|
||||
// Use a predictable file path so that we reuse the cache between app launches.
|
||||
NSString *temporaryDirectory = NSTemporaryDirectory();
|
||||
NSString *cacheDirectory = [temporaryDirectory stringByAppendingPathComponent:@"cached_random_files"];
|
||||
[OWSFileSystem ensureDirectoryExists:cacheDirectory];
|
||||
NSString *filePath = [cacheDirectory stringByAppendingPathComponent:self.filename];
|
||||
if ([NSFileManager.defaultManager fileExistsAtPath:filePath]) {
|
||||
self.filePath = filePath;
|
||||
return success();
|
||||
}
|
||||
|
||||
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
|
||||
AFHTTPSessionManager *sessionManager = [[AFHTTPSessionManager alloc] initWithSessionConfiguration:configuration];
|
||||
sessionManager.responseSerializer = [AFHTTPResponseSerializer serializer];
|
||||
OWSAssert(sessionManager.responseSerializer);
|
||||
[sessionManager GET:fileUrl
|
||||
parameters:nil
|
||||
progress:nil
|
||||
success:^(NSURLSessionDataTask *task, NSData *_Nullable responseObject) {
|
||||
if ([responseObject writeToFile:filePath atomically:YES]) {
|
||||
self.filePath = filePath;
|
||||
OWSAssert([NSFileManager.defaultManager fileExistsAtPath:filePath]);
|
||||
success();
|
||||
} else {
|
||||
OWSFail(@"Error write url response [%@]: %@", fileUrl, filePath);
|
||||
failure();
|
||||
}
|
||||
}
|
||||
failure:^(NSURLSessionDataTask *_Nullable task, NSError *requestError) {
|
||||
OWSFail(@"Error downloading url[%@]: %@", fileUrl, requestError);
|
||||
failure();
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
+ (DebugUIMessagesAssetLoader *)fakePngAssetLoaderWithImageSize:(CGSize)imageSize
|
||||
backgroundColor:(UIColor *)backgroundColor
|
||||
textColor:(UIColor *)textColor
|
||||
label:(NSString *)label
|
||||
{
|
||||
OWSAssert(imageSize.width > 0);
|
||||
OWSAssert(imageSize.height > 0);
|
||||
OWSAssert(backgroundColor);
|
||||
OWSAssert(textColor);
|
||||
OWSAssert(label.length > 0);
|
||||
|
||||
DebugUIMessagesAssetLoader *instance = [DebugUIMessagesAssetLoader new];
|
||||
instance.mimeType = OWSMimeTypeImagePng;
|
||||
instance.filename = @"image.png";
|
||||
__weak DebugUIMessagesAssetLoader *weakSelf = instance;
|
||||
instance.prepareBlock = ^(ActionSuccessBlock success, ActionFailureBlock failure) {
|
||||
[weakSelf ensurePngAssetLoaded:imageSize
|
||||
backgroundColor:backgroundColor
|
||||
textColor:textColor
|
||||
label:label
|
||||
success:success
|
||||
failure:failure];
|
||||
};
|
||||
return instance;
|
||||
}
|
||||
|
||||
- (void)ensurePngAssetLoaded:(CGSize)imageSize
|
||||
backgroundColor:(UIColor *)backgroundColor
|
||||
textColor:(UIColor *)textColor
|
||||
label:(NSString *)label
|
||||
success:(ActionSuccessBlock)success
|
||||
failure:(ActionFailureBlock)failure
|
||||
{
|
||||
OWSAssert(success);
|
||||
OWSAssert(failure);
|
||||
OWSAssert(self.filename.length > 0);
|
||||
OWSAssert(self.mimeType.length > 0);
|
||||
OWSAssert(imageSize.width > 0 && imageSize.height > 0);
|
||||
OWSAssert(backgroundColor);
|
||||
OWSAssert(textColor);
|
||||
OWSAssert(label.length > 0);
|
||||
|
||||
if (self.filePath) {
|
||||
success();
|
||||
return;
|
||||
}
|
||||
|
||||
@autoreleasepool {
|
||||
NSString *filePath = [OWSFileSystem temporaryFilePathWithFileExtension:@"png"];
|
||||
UIImage *image =
|
||||
[self createRandomPngWithSize:imageSize backgroundColor:backgroundColor textColor:textColor label:label];
|
||||
NSData *pngData = UIImagePNGRepresentation(image);
|
||||
[pngData writeToFile:filePath atomically:YES];
|
||||
self.filePath = filePath;
|
||||
OWSAssert([NSFileManager.defaultManager fileExistsAtPath:filePath]);
|
||||
success();
|
||||
}
|
||||
}
|
||||
|
||||
- (nullable UIImage *)createRandomPngWithSize:(CGSize)imageSize
|
||||
backgroundColor:(UIColor *)backgroundColor
|
||||
textColor:(UIColor *)textColor
|
||||
label:(NSString *)label
|
||||
{
|
||||
OWSAssert(imageSize.width > 0 && imageSize.height > 0);
|
||||
OWSAssert(backgroundColor);
|
||||
OWSAssert(textColor);
|
||||
OWSAssert(label.length > 0);
|
||||
|
||||
@autoreleasepool {
|
||||
CGRect frame = CGRectZero;
|
||||
frame.size = imageSize;
|
||||
CGFloat smallDimension = MIN(imageSize.width, imageSize.height);
|
||||
UIFont *font = [UIFont boldSystemFontOfSize:smallDimension * 0.5f];
|
||||
NSDictionary *textAttributes = @{ NSFontAttributeName : font, NSForegroundColorAttributeName : textColor };
|
||||
|
||||
CGRect textFrame =
|
||||
[label boundingRectWithSize:frame.size
|
||||
options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading)
|
||||
attributes:textAttributes
|
||||
context:nil];
|
||||
|
||||
UIGraphicsBeginImageContextWithOptions(frame.size, NO, [UIScreen mainScreen].scale);
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
|
||||
CGContextSetFillColorWithColor(context, backgroundColor.CGColor);
|
||||
CGContextFillRect(context, frame);
|
||||
[label drawAtPoint:CGPointMake(CGRectGetMidX(frame) - CGRectGetMidX(textFrame),
|
||||
CGRectGetMidY(frame) - CGRectGetMidY(textFrame))
|
||||
withAttributes:textAttributes];
|
||||
|
||||
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
|
||||
return image;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
+ (DebugUIMessagesAssetLoader *)fakeRandomAssetLoaderWithLength:(NSUInteger)dataLength mimeType:(NSString *)mimeType
|
||||
{
|
||||
OWSAssert(dataLength > 0);
|
||||
OWSAssert(mimeType.length > 0);
|
||||
|
||||
DebugUIMessagesAssetLoader *instance = [DebugUIMessagesAssetLoader new];
|
||||
instance.mimeType = mimeType;
|
||||
NSString *fileExtension = [MIMETypeUtil fileExtensionForMIMEType:mimeType];
|
||||
OWSAssert(fileExtension.length > 0);
|
||||
instance.filename = [@"attachment" stringByAppendingPathExtension:fileExtension];
|
||||
__weak DebugUIMessagesAssetLoader *weakSelf = instance;
|
||||
instance.prepareBlock = ^(ActionSuccessBlock success, ActionFailureBlock failure) {
|
||||
[weakSelf ensureRandomAssetLoaded:dataLength success:success failure:failure];
|
||||
};
|
||||
return instance;
|
||||
}
|
||||
|
||||
- (void)ensureRandomAssetLoaded:(NSUInteger)dataLength
|
||||
success:(ActionSuccessBlock)success
|
||||
failure:(ActionFailureBlock)failure
|
||||
{
|
||||
OWSAssert(dataLength > 0);
|
||||
OWSAssert(success);
|
||||
OWSAssert(failure);
|
||||
OWSAssert(self.filename.length > 0);
|
||||
OWSAssert(self.mimeType.length > 0);
|
||||
|
||||
if (self.filePath) {
|
||||
success();
|
||||
return;
|
||||
}
|
||||
|
||||
@autoreleasepool {
|
||||
NSString *fileExtension = [MIMETypeUtil fileExtensionForMIMEType:self.mimeType];
|
||||
OWSAssert(fileExtension.length > 0);
|
||||
NSData *data = [Randomness generateRandomBytes:(int)dataLength];
|
||||
OWSAssert(data);
|
||||
NSString *filePath = [OWSFileSystem temporaryFilePathWithFileExtension:fileExtension];
|
||||
BOOL didWrite = [data writeToFile:filePath atomically:YES];
|
||||
OWSAssert(didWrite);
|
||||
self.filePath = filePath;
|
||||
OWSAssert([NSFileManager.defaultManager fileExistsAtPath:filePath]);
|
||||
}
|
||||
|
||||
success();
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
+ (DebugUIMessagesAssetLoader *)fakeMissingAssetLoaderWithMimeType:(NSString *)mimeType
|
||||
{
|
||||
OWSAssert(mimeType.length > 0);
|
||||
|
||||
DebugUIMessagesAssetLoader *instance = [DebugUIMessagesAssetLoader new];
|
||||
instance.mimeType = mimeType;
|
||||
NSString *fileExtension = [MIMETypeUtil fileExtensionForMIMEType:mimeType];
|
||||
OWSAssert(fileExtension.length > 0);
|
||||
instance.filename = [@"attachment" stringByAppendingPathExtension:fileExtension];
|
||||
__weak DebugUIMessagesAssetLoader *weakSelf = instance;
|
||||
instance.prepareBlock = ^(ActionSuccessBlock success, ActionFailureBlock failure) {
|
||||
[weakSelf ensureMissingAssetLoaded:success failure:failure];
|
||||
};
|
||||
return instance;
|
||||
}
|
||||
|
||||
- (void)ensureMissingAssetLoaded:(ActionSuccessBlock)success failure:(ActionFailureBlock)failure
|
||||
{
|
||||
OWSAssert(success);
|
||||
OWSAssert(failure);
|
||||
OWSAssert(self.filename.length > 0);
|
||||
OWSAssert(self.mimeType.length > 0);
|
||||
|
||||
if (self.filePath) {
|
||||
success();
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *fileExtension = [MIMETypeUtil fileExtensionForMIMEType:self.mimeType];
|
||||
OWSAssert(fileExtension.length > 0);
|
||||
NSString *filePath = [OWSFileSystem temporaryFilePathWithFileExtension:fileExtension];
|
||||
BOOL didCreate = [NSFileManager.defaultManager createFileAtPath:filePath contents:nil attributes:nil];
|
||||
OWSAssert(didCreate);
|
||||
self.filePath = filePath;
|
||||
OWSAssert([NSFileManager.defaultManager fileExistsAtPath:filePath]);
|
||||
|
||||
success();
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
+ (DebugUIMessagesAssetLoader *)fakeOversizeTextAssetLoader
|
||||
{
|
||||
DebugUIMessagesAssetLoader *instance = [DebugUIMessagesAssetLoader new];
|
||||
instance.mimeType = OWSMimeTypeOversizeTextMessage;
|
||||
instance.filename = @"attachment.txt";
|
||||
__weak DebugUIMessagesAssetLoader *weakSelf = instance;
|
||||
instance.prepareBlock = ^(ActionSuccessBlock success, ActionFailureBlock failure) {
|
||||
[weakSelf ensureOversizeTextAssetLoaded:success failure:failure];
|
||||
};
|
||||
return instance;
|
||||
}
|
||||
|
||||
- (void)ensureOversizeTextAssetLoaded:(ActionSuccessBlock)success failure:(ActionFailureBlock)failure
|
||||
{
|
||||
OWSAssert(success);
|
||||
OWSAssert(failure);
|
||||
OWSAssert(self.filename.length > 0);
|
||||
OWSAssert(self.mimeType.length > 0);
|
||||
|
||||
if (self.filePath) {
|
||||
success();
|
||||
return;
|
||||
}
|
||||
|
||||
NSMutableString *message = [NSMutableString new];
|
||||
for (NSUInteger i = 0; i < 32; i++) {
|
||||
[message appendString:@"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse rutrum, nulla "
|
||||
@"vitae pretium hendrerit, tellus turpis pharetra libero, vitae sodales tortor ante vel "
|
||||
@"sem. Fusce sed nisl a lorem gravida tincidunt. Suspendisse efficitur non quam ac "
|
||||
@"sodales. Aenean ut velit maximus, posuere sem a, accumsan nunc. Donec ullamcorper "
|
||||
@"turpis lorem. Quisque dignissim purus eu placerat ultricies. Proin at urna eget mi "
|
||||
@"semper congue. Aenean non elementum ex. Praesent pharetra quam at sem vestibulum, "
|
||||
@"vestibulum ornare dolor elementum. Vestibulum massa tortor, scelerisque sit amet "
|
||||
@"pulvinar a, rhoncus vitae nisl. Sed mi nunc, tempus at varius in, malesuada vitae "
|
||||
@"dui. Vivamus efficitur pulvinar erat vitae congue. Proin vehicula turpis non felis "
|
||||
@"congue facilisis. Nullam aliquet dapibus ligula ac mollis. Etiam sit amet posuere "
|
||||
@"lorem, in rhoncus nisi.\n\n"];
|
||||
}
|
||||
|
||||
NSString *fileExtension = @"txt";
|
||||
NSString *filePath = [OWSFileSystem temporaryFilePathWithFileExtension:fileExtension];
|
||||
NSData *data = [message dataUsingEncoding:NSUTF8StringEncoding];
|
||||
OWSAssert(data);
|
||||
BOOL didWrite = [data writeToFile:filePath atomically:YES];
|
||||
OWSAssert(didWrite);
|
||||
self.filePath = filePath;
|
||||
OWSAssert([NSFileManager.defaultManager fileExistsAtPath:filePath]);
|
||||
|
||||
success();
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
+ (instancetype)jpegInstance
|
||||
{
|
||||
static DebugUIMessagesAssetLoader *instance = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
instance = [DebugUIMessagesAssetLoader
|
||||
fakeAssetLoaderWithUrl:@"https://s3.amazonaws.com/ows-data/example_attachment_media/random-jpg.JPG"
|
||||
mimeType:@"image/jpeg"];
|
||||
});
|
||||
return instance;
|
||||
}
|
||||
|
||||
+ (instancetype)gifInstance
|
||||
{
|
||||
static DebugUIMessagesAssetLoader *instance = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
instance = [DebugUIMessagesAssetLoader
|
||||
fakeAssetLoaderWithUrl:@"https://s3.amazonaws.com/ows-data/example_attachment_media/random-gif.gif"
|
||||
mimeType:@"image/gif"];
|
||||
});
|
||||
return instance;
|
||||
}
|
||||
|
||||
+ (instancetype)largeGifInstance
|
||||
{
|
||||
static DebugUIMessagesAssetLoader *instance = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
instance =
|
||||
[DebugUIMessagesAssetLoader fakeAssetLoaderWithUrl:@"https://i.giphy.com/media/LTw0F3GAdaao8/source.gif"
|
||||
mimeType:@"image/gif"];
|
||||
});
|
||||
return instance;
|
||||
}
|
||||
|
||||
+ (instancetype)mp3Instance
|
||||
{
|
||||
static DebugUIMessagesAssetLoader *instance = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
instance = [DebugUIMessagesAssetLoader
|
||||
fakeAssetLoaderWithUrl:@"https://s3.amazonaws.com/ows-data/example_attachment_media/random-mp3.mp3"
|
||||
mimeType:@"audio/mp3"];
|
||||
});
|
||||
return instance;
|
||||
}
|
||||
|
||||
+ (instancetype)mp4Instance
|
||||
{
|
||||
static DebugUIMessagesAssetLoader *instance = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
instance = [DebugUIMessagesAssetLoader
|
||||
fakeAssetLoaderWithUrl:@"https://s3.amazonaws.com/ows-data/example_attachment_media/random-mp4.mp4"
|
||||
mimeType:@"video/mp4"];
|
||||
});
|
||||
return instance;
|
||||
}
|
||||
|
||||
+ (instancetype)compactPortraitPngInstance
|
||||
{
|
||||
static DebugUIMessagesAssetLoader *instance = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
instance = [DebugUIMessagesAssetLoader fakePngAssetLoaderWithImageSize:CGSizeMake(60, 100)
|
||||
backgroundColor:[UIColor blueColor]
|
||||
textColor:[UIColor whiteColor]
|
||||
label:@"P"];
|
||||
});
|
||||
return instance;
|
||||
}
|
||||
|
||||
+ (instancetype)compactLandscapePngInstance
|
||||
{
|
||||
static DebugUIMessagesAssetLoader *instance = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
instance = [DebugUIMessagesAssetLoader fakePngAssetLoaderWithImageSize:CGSizeMake(100, 60)
|
||||
backgroundColor:[UIColor greenColor]
|
||||
textColor:[UIColor whiteColor]
|
||||
label:@"L"];
|
||||
});
|
||||
return instance;
|
||||
}
|
||||
|
||||
+ (instancetype)tallPortraitPngInstance
|
||||
{
|
||||
static DebugUIMessagesAssetLoader *instance = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
instance = [DebugUIMessagesAssetLoader fakePngAssetLoaderWithImageSize:CGSizeMake(10, 100)
|
||||
backgroundColor:[UIColor yellowColor]
|
||||
textColor:[UIColor whiteColor]
|
||||
label:@"P"];
|
||||
});
|
||||
return instance;
|
||||
}
|
||||
|
||||
+ (instancetype)wideLandscapePngInstance
|
||||
{
|
||||
static DebugUIMessagesAssetLoader *instance = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
instance = [DebugUIMessagesAssetLoader fakePngAssetLoaderWithImageSize:CGSizeMake(100, 10)
|
||||
backgroundColor:[UIColor purpleColor]
|
||||
textColor:[UIColor whiteColor]
|
||||
label:@"L"];
|
||||
});
|
||||
return instance;
|
||||
}
|
||||
|
||||
+ (instancetype)largePngInstance
|
||||
{
|
||||
static DebugUIMessagesAssetLoader *instance = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
instance = [DebugUIMessagesAssetLoader fakePngAssetLoaderWithImageSize:CGSizeMake(4000, 4000)
|
||||
backgroundColor:[UIColor brownColor]
|
||||
textColor:[UIColor whiteColor]
|
||||
label:@"B"];
|
||||
});
|
||||
return instance;
|
||||
}
|
||||
|
||||
+ (instancetype)tinyPngInstance
|
||||
{
|
||||
static DebugUIMessagesAssetLoader *instance = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
instance = [DebugUIMessagesAssetLoader fakePngAssetLoaderWithImageSize:CGSizeMake(2, 2)
|
||||
backgroundColor:[UIColor cyanColor]
|
||||
textColor:[UIColor whiteColor]
|
||||
label:@"T"];
|
||||
});
|
||||
return instance;
|
||||
}
|
||||
|
||||
+ (instancetype)pngInstanceWithSize:(CGSize)size
|
||||
backgroundColor:(UIColor *)backgroundColor
|
||||
textColor:(UIColor *)textColor
|
||||
label:(NSString *)label
|
||||
{
|
||||
return [DebugUIMessagesAssetLoader fakePngAssetLoaderWithImageSize:size
|
||||
backgroundColor:backgroundColor
|
||||
textColor:textColor
|
||||
label:label];
|
||||
}
|
||||
|
||||
+ (instancetype)tinyPdfInstance
|
||||
{
|
||||
static DebugUIMessagesAssetLoader *instance = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
instance = [DebugUIMessagesAssetLoader fakeRandomAssetLoaderWithLength:256 mimeType:@"application/pdf"];
|
||||
});
|
||||
return instance;
|
||||
}
|
||||
|
||||
+ (instancetype)largePdfInstance
|
||||
{
|
||||
static DebugUIMessagesAssetLoader *instance = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
instance =
|
||||
[DebugUIMessagesAssetLoader fakeRandomAssetLoaderWithLength:4 * 1024 * 1024 mimeType:@"application/pdf"];
|
||||
});
|
||||
return instance;
|
||||
}
|
||||
|
||||
+ (instancetype)missingPngInstance
|
||||
{
|
||||
static DebugUIMessagesAssetLoader *instance = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
instance = [DebugUIMessagesAssetLoader fakeMissingAssetLoaderWithMimeType:OWSMimeTypeImagePng];
|
||||
});
|
||||
return instance;
|
||||
}
|
||||
|
||||
+ (instancetype)missingPdfInstance
|
||||
{
|
||||
static DebugUIMessagesAssetLoader *instance = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
instance = [DebugUIMessagesAssetLoader fakeMissingAssetLoaderWithMimeType:@"application/pdf"];
|
||||
});
|
||||
return instance;
|
||||
}
|
||||
|
||||
+ (instancetype)oversizeTextInstance
|
||||
{
|
||||
static DebugUIMessagesAssetLoader *instance = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
instance = [DebugUIMessagesAssetLoader fakeOversizeTextAssetLoader];
|
||||
});
|
||||
return instance;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
18
Signal/src/ViewControllers/DebugUI/DebugUIMessagesUtils.h
Normal file
18
Signal/src/ViewControllers/DebugUI/DebugUIMessagesUtils.h
Normal file
@ -0,0 +1,18 @@
|
||||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class YapDatabaseReadWriteTransaction;
|
||||
|
||||
typedef void (^ActionSuccessBlock)(void);
|
||||
typedef void (^ActionFailureBlock)(void);
|
||||
typedef void (^ActionPrepareBlock)(ActionSuccessBlock success, ActionFailureBlock failure);
|
||||
typedef void (^StaggeredActionBlock)(NSUInteger index,
|
||||
YapDatabaseReadWriteTransaction *transaction,
|
||||
ActionSuccessBlock success,
|
||||
ActionFailureBlock failure);
|
||||
typedef void (^UnstaggeredActionBlock)(NSUInteger index, YapDatabaseReadWriteTransaction *transaction);
|
||||
|
||||
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
|
||||
|
||||
@ -307,10 +307,10 @@ class MessageDetailViewController: OWSViewController, UIScrollViewDelegate, Medi
|
||||
}
|
||||
|
||||
private func displayableTextIfText() -> String? {
|
||||
guard viewItem.hasText else {
|
||||
guard viewItem.hasBodyText else {
|
||||
return nil
|
||||
}
|
||||
guard let displayableText = viewItem.displayableText() else {
|
||||
guard let displayableText = viewItem.displayableBodyText() else {
|
||||
return nil
|
||||
}
|
||||
let messageBody = displayableText.fullText
|
||||
|
||||
@ -5,7 +5,6 @@
|
||||
import Foundation
|
||||
import JSQMessagesViewController
|
||||
import SignalServiceKit
|
||||
import SignalMessaging
|
||||
|
||||
@objc
|
||||
public class OWSMessagesBubbleImageFactory: NSObject {
|
||||
@ -21,22 +20,22 @@ public class OWSMessagesBubbleImageFactory: NSObject {
|
||||
}()
|
||||
|
||||
public lazy var incoming: JSQMessagesBubbleImage = {
|
||||
let color = UIColor.jsq_messageBubbleLightGray()!
|
||||
let color = bubbleColorIncoming
|
||||
return self.incoming(color: color)
|
||||
}()
|
||||
|
||||
public lazy var outgoing: JSQMessagesBubbleImage = {
|
||||
let color = UIColor.ows_materialBlue
|
||||
let color = bubbleColorOutgoingSent
|
||||
return self.outgoing(color: color)
|
||||
}()
|
||||
|
||||
public lazy var currentlyOutgoing: JSQMessagesBubbleImage = {
|
||||
let color = UIColor.ows_fadedBlue
|
||||
let color = bubbleColorOutgoingSending
|
||||
return self.outgoing(color: color)
|
||||
}()
|
||||
|
||||
public lazy var outgoingFailed: JSQMessagesBubbleImage = {
|
||||
let color = UIColor.gray
|
||||
let color = bubbleColorOutgoingUnsent
|
||||
return self.outgoing(color: color)
|
||||
}()
|
||||
|
||||
@ -58,6 +57,32 @@ public class OWSMessagesBubbleImageFactory: NSObject {
|
||||
}
|
||||
}
|
||||
|
||||
public static let bubbleColorIncoming = UIColor.jsq_messageBubbleLightGray()!
|
||||
|
||||
public static let bubbleColorOutgoingUnsent = UIColor.gray
|
||||
|
||||
public static let bubbleColorOutgoingSending = UIColor.ows_fadedBlue
|
||||
|
||||
public static let bubbleColorOutgoingSent = UIColor.ows_materialBlue
|
||||
|
||||
public func bubbleColor(message: TSMessage) -> UIColor {
|
||||
if message is TSIncomingMessage {
|
||||
return OWSMessagesBubbleImageFactory.bubbleColorIncoming
|
||||
} else if let outgoingMessage = message as? TSOutgoingMessage {
|
||||
switch outgoingMessage.messageState {
|
||||
case .unsent:
|
||||
return OWSMessagesBubbleImageFactory.bubbleColorOutgoingUnsent
|
||||
case .attemptingOut:
|
||||
return OWSMessagesBubbleImageFactory.bubbleColorOutgoingSending
|
||||
default:
|
||||
return OWSMessagesBubbleImageFactory.bubbleColorOutgoingSent
|
||||
}
|
||||
} else {
|
||||
owsFail("Unexpected message type: \(message)")
|
||||
return UIColor.ows_materialBlue
|
||||
}
|
||||
}
|
||||
|
||||
private func outgoing(color: UIColor) -> JSQMessagesBubbleImage {
|
||||
if isRTL {
|
||||
return jsqFactory.incomingMessagesBubbleImage(with: color)
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TSYapDatabaseObject.h"
|
||||
@ -133,6 +133,8 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
*/
|
||||
- (void)unarchiveThreadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction;
|
||||
|
||||
- (void)removeAllThreadInteractionsWithTransaction:(YapDatabaseReadWriteTransaction *)transaction;
|
||||
|
||||
#pragma mark Drafts
|
||||
|
||||
/**
|
||||
|
||||
@ -49,8 +49,13 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
- (void)removeWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
|
||||
{
|
||||
[super removeWithTransaction:transaction];
|
||||
[self removeAllThreadInteractionsWithTransaction:transaction];
|
||||
|
||||
[super removeWithTransaction:transaction];
|
||||
}
|
||||
|
||||
- (void)removeAllThreadInteractionsWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
|
||||
{
|
||||
// We can't safely delete interactions while enumerating them, so
|
||||
// we collect and delete separately.
|
||||
//
|
||||
|
||||
@ -132,26 +132,42 @@ void runAsyncRegistrationsForStorage(OWSStorage *storage)
|
||||
{
|
||||
OWSAssert(completion);
|
||||
|
||||
[((OWSDatabase *)self.database)collectRegistrationConnections];
|
||||
|
||||
runAsyncRegistrationsForStorage(self);
|
||||
|
||||
DDLogVerbose(@"%@ async registrations enqueued.", self.logTag);
|
||||
|
||||
// Block until all async registrations are complete.
|
||||
//
|
||||
// NOTE: This has to happen on the "registration connection" for this
|
||||
// NOTE: This has to happen on the "registration connections" for this
|
||||
// database.
|
||||
YapDatabaseConnection *dbConnection = self.registrationConnection;
|
||||
OWSAssert(self.registrationConnection);
|
||||
[dbConnection flushTransactionsWithCompletionQueue:dispatch_get_main_queue()
|
||||
completionBlock:^{
|
||||
OWSAssert(!self.areAsyncRegistrationsComplete);
|
||||
NSMutableSet<YapDatabaseConnection *> *pendingRegistrationConnectionSet =
|
||||
[[((OWSDatabase *)self.database)clearCollectedRegistrationConnections] mutableCopy];
|
||||
DDLogVerbose(@"%@ flushing registration connections: %zd.", self.logTag, pendingRegistrationConnectionSet.count);
|
||||
|
||||
DDLogVerbose(@"%@ async registrations complete.", self.logTag);
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
for (YapDatabaseConnection *dbConnection in pendingRegistrationConnectionSet) {
|
||||
[dbConnection
|
||||
flushTransactionsWithCompletionQueue:dispatch_get_main_queue()
|
||||
completionBlock:^{
|
||||
OWSAssertIsOnMainThread();
|
||||
OWSAssert(!self.areAsyncRegistrationsComplete);
|
||||
|
||||
self.areAsyncRegistrationsComplete = YES;
|
||||
[pendingRegistrationConnectionSet removeObject:dbConnection];
|
||||
if (pendingRegistrationConnectionSet.count > 0) {
|
||||
DDLogVerbose(@"%@ registration connection flushed.", self.logTag);
|
||||
return;
|
||||
}
|
||||
|
||||
completion();
|
||||
}];
|
||||
DDLogVerbose(@"%@ async registrations complete.", self.logTag);
|
||||
|
||||
self.areAsyncRegistrationsComplete = YES;
|
||||
|
||||
completion();
|
||||
}];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
+ (void)protectFiles
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import <YapDatabase/YapDatabaseConnection.h>
|
||||
#import <YapDatabase/YapDatabase.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@ -34,6 +34,26 @@ extern NSString *const StorageIsReadyNotification;
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@interface OWSDatabase : YapDatabase
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
- (id)initWithPath:(NSString *)inPath
|
||||
serializer:(nullable YapDatabaseSerializer)inSerializer
|
||||
deserializer:(YapDatabaseDeserializer)inDeserializer
|
||||
options:(YapDatabaseOptions *)inOptions
|
||||
delegate:(id<OWSDatabaseConnectionDelegate>)delegate NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
// Starts collecting references to the registration connections.
|
||||
- (void)collectRegistrationConnections;
|
||||
// Stops collecting references to the registration connections and returns
|
||||
// all collected connections.
|
||||
- (NSSet<YapDatabaseConnection *> *)clearCollectedRegistrationConnections;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@interface OWSStorage : NSObject
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
@ -130,17 +130,11 @@ typedef NSData *_Nullable (^CreateDatabaseMetadataBlock)(void);
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@interface OWSDatabase : YapDatabase
|
||||
@interface OWSDatabase ()
|
||||
|
||||
@property (atomic, weak) id<OWSDatabaseConnectionDelegate> delegate;
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
- (id)initWithPath:(NSString *)inPath
|
||||
serializer:(nullable YapDatabaseSerializer)inSerializer
|
||||
deserializer:(YapDatabaseDeserializer)inDeserializer
|
||||
options:(YapDatabaseOptions *)inOptions
|
||||
delegate:(id<OWSDatabaseConnectionDelegate>)delegate NS_DESIGNATED_INITIALIZER;
|
||||
@property (nonatomic, readonly, nullable) NSMutableSet<YapDatabaseConnection *> *registrationConnectionSet;
|
||||
|
||||
@end
|
||||
|
||||
@ -191,9 +185,29 @@ typedef NSData *_Nullable (^CreateDatabaseMetadataBlock)(void);
|
||||
((OWSDatabaseConnection *)connection).canWriteBeforeStorageReady = YES;
|
||||
#endif
|
||||
|
||||
[self.registrationConnectionSet addObject:connection];
|
||||
|
||||
return connection;
|
||||
}
|
||||
|
||||
- (void)collectRegistrationConnections
|
||||
{
|
||||
OWSAssert(!self.registrationConnectionSet);
|
||||
|
||||
_registrationConnectionSet = [NSMutableSet set];
|
||||
}
|
||||
|
||||
- (NSSet<YapDatabaseConnection *> *)clearCollectedRegistrationConnections
|
||||
{
|
||||
OWSAssert(self.registrationConnectionSet);
|
||||
|
||||
NSSet<YapDatabaseConnection *> *registrationConnectionSetCopy = [self.registrationConnectionSet copy];
|
||||
|
||||
_registrationConnectionSet = nil;
|
||||
|
||||
return registrationConnectionSetCopy;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@ -12,7 +12,7 @@ extern const NSTimeInterval kDayInterval;
|
||||
extern const NSTimeInterval kWeekInterval;
|
||||
extern const NSTimeInterval kMonthInterval;
|
||||
|
||||
#define kSecondInMs 1000
|
||||
#define kSecondInMs ((uint64_t)1000)
|
||||
#define kMinuteInMs (kSecondInMs * 60)
|
||||
#define kHourInMs (kMinuteInMs * 60)
|
||||
#define kDayInMs (kHourInMs * 24)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user