mirror of https://github.com/oxen-io/session-ios
Merge branch 'charlesmchen/bubbleCollapse'
commit
43e50e33c2
@ -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
|
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
|
File diff suppressed because it is too large
Load Diff
@ -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
|
@ -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
|
@ -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
|
@ -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
|
Loading…
Reference in New Issue