2019-10-08 14:30:24 +04:00

574 lines
19 KiB
Objective-C

#import "ProxyWindow.h"
#define UIColorRGB(rgb) ([[UIColor alloc] initWithRed:(((rgb >> 16) & 0xff) / 255.0f) green:(((rgb >> 8) & 0xff) / 255.0f) blue:(((rgb) & 0xff) / 255.0f) alpha:1.0f])
#define UIColorRGBA(rgb,a) ([[UIColor alloc] initWithRed:(((rgb >> 16) & 0xff) / 255.0f) green:(((rgb >> 8) & 0xff) / 255.0f) blue:(((rgb) & 0xff) / 255.0f) alpha:a])
#ifdef __LP64__
# define CGFloor floor
#else
# define CGFloor floorf
#endif
static inline void dispatchAfter(double delay, dispatch_queue_t queue, dispatch_block_t block)
{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)((delay) * NSEC_PER_SEC)), queue, block);
}
static UIFont *mediumSystemFontOfSize(CGFloat size) {
static bool useSystem = false;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
useSystem = [[[UIDevice currentDevice] systemVersion] intValue] >= 9;
});
if (useSystem) {
return [UIFont systemFontOfSize:size weight:UIFontWeightMedium];
} else {
return [UIFont fontWithName:@"HelveticaNeue-Medium" size:size];
}
}
static bool readCGFloat(NSString *string, int *position, CGFloat *result) {
int start = *position;
bool seenDot = false;
int length = (int)string.length;
while (*position < length) {
unichar c = [string characterAtIndex:*position];
*position++;
if (c == '.') {
if (seenDot) {
return false;
} else {
seenDot = true;
}
} else if ((c < '0' || c > '9') && c != '-') {
if (*position == start) {
*result = 0.0f;
return true;
} else {
*result = [[string substringWithRange:NSMakeRange(start, *position - start)] floatValue];
return true;
}
}
}
if (*position == start) {
*result = 0.0f;
return true;
} else {
*result = [[string substringWithRange:NSMakeRange(start, *position - start)] floatValue];
return true;
}
return true;
}
static void drawSvgPath(CGContextRef context, NSString *path) {
int position = 0;
int length = (int)path.length;
while (position < length) {
unichar c = [path characterAtIndex:position];
position++;
if (c == ' ') {
continue;
}
if (c == 'M') { // M
CGFloat x = 0.0f;
CGFloat y = 0.0f;
readCGFloat(path, &position, &x);
readCGFloat(path, &position, &y);
CGContextMoveToPoint(context, x, y);
} else if (c == 'L') { // L
CGFloat x = 0.0f;
CGFloat y = 0.0f;
readCGFloat(path, &position, &x);
readCGFloat(path, &position, &y);
CGContextAddLineToPoint(context, x, y);
} else if (c == 'C') { // C
CGFloat x1 = 0.0f;
CGFloat y1 = 0.0f;
CGFloat x2 = 0.0f;
CGFloat y2 = 0.0f;
CGFloat x = 0.0f;
CGFloat y = 0.0f;
readCGFloat(path, &position, &x1);
readCGFloat(path, &position, &y1);
readCGFloat(path, &position, &x2);
readCGFloat(path, &position, &y2);
readCGFloat(path, &position, &x);
readCGFloat(path, &position, &y);
CGContextAddCurveToPoint(context, x1, y1, x2, y2, x, y);
} else if (c == 'Z') { // Z
CGContextClosePath(context);
CGContextFillPath(context);
CGContextBeginPath(context);
} else if (c == 'S') { // Z
CGContextClosePath(context);
CGContextStrokePath(context);
CGContextBeginPath(context);
} else if (c == 'U') { // Z
CGContextStrokePath(context);
CGContextBeginPath(context);
}
}
}
static bool ProxyWindowIsLight = true;
@interface ProxySpinnerView : UIView
@property (nonatomic, copy) void (^onSuccess)(void);
- (instancetype)initWithFrame:(CGRect)frame light:(bool)light;
- (void)setSucceed;
@end
@interface ProxyWindowController ()
{
bool _light;
NSString *_text;
UIImage *_icon;
bool _isShield;
UIVisualEffectView *_effectView;
UIView *_backgroundView;
ProxySpinnerView *_spinner;
UIImageView *_shield;
UILabel *_label;
}
@property (nonatomic, weak) UIWindow *weakWindow;
@property (nonatomic, strong) UIView *containerView;
@end
@implementation ProxyWindowController
+ (UIImage *)generateShieldImage:(bool)isLight {
UIColor *color = isLight ? UIColorRGB(0x5a5a5a) : [UIColor whiteColor];
NSString *code = @"M100,6.56393754 L6,48.2657557 L6,110.909091 C6,169.509174 46.3678836,223.966692 100,237.814087 C153.632116,223.966692 194,169.509174 194,110.909091 L194,48.2657557 L100,6.56393754 S";
UIGraphicsBeginImageContextWithOptions(CGSizeMake(67, 82), false, 0.0f);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextScaleCTM(context, 0.333333f, 0.333333f);
CGContextSetLineWidth(context, 12.0f);
CGContextSetStrokeColorWithColor(context, color.CGColor);
drawSvgPath(context, code);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
- (instancetype)initWithLight:(bool)light text:(NSString *)text icon:(UIImage *)icon isShield:(bool)isShield {
self = [super init];
if (self != nil) {
_light = light;
_text = text;
_icon = icon;
_isShield = isShield;
}
return self;
}
- (void)loadView
{
[super loadView];
if (self.view.bounds.size.width > FLT_EPSILON) {
[self updateLayout];
}
}
- (void)updateLayout {
CGSize spinnerSize = CGSizeMake(48.0, 48.0);
CGSize containerSize = CGSizeMake(156.0, 176.0);
if (_icon == nil) {
containerSize = CGSizeMake(207.0, 177.0);
spinnerSize = CGSizeMake(40.0, 40.0);
}
if (_text.length != 0) {
NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init];
style.lineBreakMode = NSLineBreakByWordWrapping;
style.lineSpacing = 2.0f;
style.alignment = NSTextAlignmentCenter;
NSDictionary *attributes = @{NSForegroundColorAttributeName:_light ? UIColorRGB(0x5a5a5a) : [UIColor whiteColor], NSFontAttributeName:mediumSystemFontOfSize(17.0f), NSParagraphStyleAttributeName:style};
NSAttributedString *string = [[NSAttributedString alloc] initWithString:_text attributes:attributes];
UILabel *label = [[UILabel alloc] init];
label.font = [UIFont systemFontOfSize:15.0f];
label.numberOfLines = 0;
label.textAlignment = NSTextAlignmentCenter;
label.attributedText = string;
CGSize labelSize = [label sizeThatFits:CGSizeMake(containerSize.width - 10.0 * 2.0, CGFLOAT_MAX)];
containerSize.height += labelSize.height - 38.0;
}
CGRect spinnerFrame = CGRectMake((containerSize.width - spinnerSize.width) / 2.0f, _icon != nil ? 40.0f : 45.0, spinnerSize.width, spinnerSize.height);
if (_containerView == nil) {
_containerView = [[UIView alloc] initWithFrame:CGRectMake(CGFloor(self.view.frame.size.width - containerSize.width) / 2, CGFloor(self.view.frame.size.height - containerSize.height) / 2, containerSize.width, containerSize.height)];
_containerView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin;
_containerView.alpha = 0.0f;
_containerView.clipsToBounds = true;
_containerView.layer.cornerRadius = 20.0f;
[self.view addSubview:_containerView];
if ([[[UIDevice currentDevice] systemVersion] intValue] >= 9) {
_effectView = [[UIVisualEffectView alloc] initWithEffect:_light ? [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight] : [UIBlurEffect effectWithStyle:UIBlurEffectStyleDark]];
_effectView.frame = _containerView.bounds;
[_containerView addSubview:_effectView];
if (_light)
{
UIView *tintView = [[UIView alloc] initWithFrame:_effectView.bounds];
tintView.backgroundColor = UIColorRGBA(0xf4f4f4, 0.75f);
[_containerView addSubview:tintView];
}
} else {
_backgroundView = [[UIView alloc] initWithFrame:_containerView.bounds];
_backgroundView.backgroundColor = _light ? UIColorRGBA(0xeaeaea, 0.92f) : UIColorRGBA(0x000000, 0.9f);
[_containerView addSubview:_backgroundView];
}
UIColor *color = _light ? UIColorRGB(0x5a5a5a) : [UIColor whiteColor];
UIImage *image = nil;
if (_icon != nil) {
image = _icon;
} else {
CGSize size = CGSizeMake(66.0, 66.0);
UIGraphicsBeginImageContextWithOptions(size, false, 0.0);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, color.CGColor);
CGFloat lineWidth = 4.0f;
CGContextSetLineWidth(context, lineWidth);
CGContextStrokeEllipseInRect(context, CGRectMake(lineWidth / 2.0f, lineWidth / 2.0f, size.width - lineWidth, size.height - lineWidth));
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
_shield = [[UIImageView alloc] initWithImage:image];
_shield.frame = CGRectMake((_containerView.frame.size.width - _shield.frame.size.width) / 2.0f, _isShield ? 23.0f : 30.0, _shield.frame.size.width, _shield.frame.size.height);
[_containerView addSubview:_shield];
_spinner = [[ProxySpinnerView alloc] initWithFrame:spinnerFrame light:_light];
[_containerView addSubview:_spinner];
NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init];
style.lineBreakMode = NSLineBreakByWordWrapping;
style.lineSpacing = 2.0f;
style.alignment = NSTextAlignmentCenter;
NSDictionary *attributes = @{NSForegroundColorAttributeName:_light ? UIColorRGB(0x5a5a5a) : [UIColor whiteColor], NSFontAttributeName:mediumSystemFontOfSize(17.0f), NSParagraphStyleAttributeName:style};
NSAttributedString *string = [[NSAttributedString alloc] initWithString:_text attributes:attributes];
UILabel *label = [[UILabel alloc] init];
label.font = [UIFont systemFontOfSize:15.0f];
label.numberOfLines = 0;
label.textAlignment = NSTextAlignmentCenter;
label.attributedText = string;
_label = label;
CGSize labelSize = [label sizeThatFits:CGSizeMake(_containerView.frame.size.width - 10.0 * 2.0, CGFLOAT_MAX)];
label.frame = CGRectMake((_containerView.frame.size.width - labelSize.width) / 2.0f, _containerView.frame.size.height - labelSize.height - 18.0f, labelSize.width, labelSize.height);
[_containerView addSubview:label];
} else {
_containerView.frame = CGRectMake(CGFloor(self.view.frame.size.width - containerSize.width) / 2, CGFloor(self.view.frame.size.height - containerSize.width) / 2, containerSize.width, containerSize.height);
_effectView.frame = _containerView.bounds;
_backgroundView.frame = _containerView.bounds;
_spinner.frame = spinnerFrame;
_shield.frame = CGRectMake((_containerView.frame.size.width - _shield.frame.size.width) / 2.0f, _isShield ? 23.0f : 30.0, _shield.frame.size.width, _shield.frame.size.height);
[_label sizeToFit];
_label.frame = CGRectMake((_containerView.frame.size.width - _label.frame.size.width) / 2.0f, _containerView.frame.size.height - _label.frame.size.height - 18.0f, _label.frame.size.width, _label.frame.size.height);
}
}
- (void)dismissWithSuccess:(void (^)(void))completion increasedDelay:(bool)increasedDelay
{
void (^dismissBlock)(void) = ^{
[UIView animateWithDuration:0.3 delay:increasedDelay ? 2.1 : 0.55 options:0 animations:^{
_containerView.alpha = 0.0f;
} completion:^(__unused BOOL finished) {
if (completion) {
completion();
}
}];
};
_containerView.transform = CGAffineTransformMakeScale(0.6f, 0.6f);
[UIView animateWithDuration:0.3 delay:0.0 options:7 << 16 animations:^{
_containerView.transform = CGAffineTransformIdentity;
} completion:nil];
[UIView animateWithDuration:0.3f animations:^{
_containerView.alpha = 1.0f;
} completion:^(__unused BOOL finished) {
dismissBlock();
}];
if (_icon == nil) {
dispatchAfter(0.15, dispatch_get_main_queue(), ^{
[_spinner setSucceed];
});
}
}
- (BOOL)canBecomeFirstResponder {
return false;
}
@end
@interface ProxySpinnerViewInternal : UIView
@property (nonatomic, copy) void (^onDraw)(void);
@property (nonatomic, copy) void (^onSuccess)(void);
- (instancetype)initWithFrame:(CGRect)frame light:(bool)light;
- (void)setSucceed:(bool)fromRotation progress:(CGFloat)progress;
@end
@interface ProxySpinnerView ()
{
ProxySpinnerViewInternal *_internalView;
bool _progressing;
}
@end
@implementation ProxySpinnerView
- (instancetype)initWithFrame:(CGRect)frame light:(bool)light {
self = [super initWithFrame:frame];
if (self != nil) {
self.backgroundColor = [UIColor clearColor];
self.opaque = false;
self.userInteractionEnabled = false;
_internalView = [[ProxySpinnerViewInternal alloc] initWithFrame:self.bounds light:light];
_internalView.hidden = true;
[self addSubview:_internalView];
}
return self;
}
- (void)setSucceed {
_internalView.hidden = false;
[_internalView setSucceed:false progress:0.0f];
}
@end
@interface ProxySpinnerViewInternal ()
{
CADisplayLink *_displayLink;
bool _light;
bool _isProgressing;
CGFloat _rotationValue;
bool _isRotating;
CGFloat _checkValue;
bool _delay;
bool _isSucceed;
bool _isChecking;
NSTimeInterval _previousTime;
}
@end
@implementation ProxySpinnerViewInternal
- (instancetype)initWithFrame:(CGRect)frame light:(bool)light {
self = [super initWithFrame:frame];
if (self != nil) {
_light = light;
self.backgroundColor = [UIColor clearColor];
self.opaque = false;
self.userInteractionEnabled = false;
}
return self;
}
- (void)dealloc {
_displayLink.paused = true;
[_displayLink removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
}
- (CADisplayLink *)displayLink {
if (_displayLink == nil) {
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayLinkUpdate)];
_displayLink.paused = true;
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
}
return _displayLink;
}
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGPoint centerPoint = CGPointMake(rect.size.width / 2.0f, rect.size.height / 2.0f);
CGFloat lineWidth = 4.0f;
CGFloat inset = 3.0f;
if (rect.size.width < 44.0) {
inset = 0.0f;
}
UIColor *foregroundColor = _light ? UIColorRGB(0x5a5a5a) : [UIColor whiteColor];
CGContextSetFillColorWithColor(context, foregroundColor.CGColor);
CGContextSetStrokeColorWithColor(context, foregroundColor.CGColor);
if (_isProgressing)
{
CGMutablePathRef path = CGPathCreateMutable();
CGFloat offset = -_rotationValue * 2.0f * M_PI;
CGPathAddArc(path, NULL, centerPoint.x, centerPoint.y, (rect.size.width - inset * 2.0f - lineWidth) / 2.0f, offset, offset + (3.0f * M_PI_2) * (1.0f - _checkValue), false);
CGPathRef strokedArc = CGPathCreateCopyByStrokingPath(path, NULL, lineWidth, kCGLineCapRound, kCGLineJoinMiter, 10);
CGContextAddPath(context, strokedArc);
CGPathRelease(strokedArc);
CGPathRelease(path);
CGContextFillPath(context);
}
if (_checkValue > FLT_EPSILON)
{
CGContextSetLineWidth(context, 4.0f);
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetLineJoin(context, kCGLineJoinRound);
CGContextSetMiterLimit(context, 10);
CGFloat firstSegment = MIN(1.0f, _checkValue * 3.0f);
CGPoint s = CGPointMake(inset + 5.0f, centerPoint.y + 1.0f);
CGPoint p1 = CGPointMake(10.0f, 10.0f);
CGPoint p2 = CGPointMake(23.0f, -23.0f);
if (rect.size.width < 44.0) {
p1 = CGPointMake(9.0f, 9.0f);
p2 = CGPointMake(23.0f, -23.0f);
}
if (firstSegment < 1.0f)
{
CGContextMoveToPoint(context, s.x + p1.x * firstSegment, s.y + p1.y * firstSegment);
CGContextAddLineToPoint(context, s.x, s.y);
}
else
{
CGFloat secondSegment = (_checkValue - 0.33f) * 1.5f;
if (rect.size.width < 44.0) {
secondSegment = (_checkValue - 0.33f) * 1.35f;
}
CGContextMoveToPoint(context, s.x + p1.x + p2.x * secondSegment, s.y + p1.y + p2.y * secondSegment);
CGContextAddLineToPoint(context, s.x + p1.x, s.y + p1.y);
CGContextAddLineToPoint(context, s.x, s.y);
}
CGContextStrokePath(context);
}
}
- (void)displayLinkUpdate
{
NSTimeInterval previousTime = _previousTime;
NSTimeInterval currentTime = CACurrentMediaTime();
_previousTime = currentTime;
NSTimeInterval delta = previousTime > DBL_EPSILON ? currentTime - previousTime : 0.0;
if (delta < DBL_EPSILON)
return;
if (_isRotating)
{
_rotationValue += delta * 1.35f;
}
if (_isSucceed && _isRotating && !_delay && _rotationValue >= 0.5f)
{
_rotationValue = 0.5f;
_isRotating = false;
_isChecking = true;
}
if (_isChecking)
_checkValue += delta * M_PI * 1.6f;
if (_rotationValue > 1.0f)
{
_rotationValue = 0.0f;
_delay = false;
}
if (_checkValue > 1.0f)
{
_checkValue = 1.0f;
[self displayLink].paused = true;
if (self.onSuccess != nil)
{
void (^onSuccess)(void) = [self.onSuccess copy];
self.onSuccess = nil;
onSuccess();
}
}
[self setNeedsDisplay];
if (self.onDraw != nil)
{
void (^onDraw)(void) = [self.onDraw copy];
self.onDraw = nil;
onDraw();
}
}
- (void)setProgress {
_isRotating = true;
_isProgressing = true;
[self displayLink].paused = false;
}
- (void)setSucceed:(bool)fromRotation progress:(CGFloat)progress {
if (_isSucceed)
return;
if (fromRotation) {
_isRotating = true;
_isProgressing = true;
_rotationValue = progress;
}
_isSucceed = true;
if (!_isRotating)
_isChecking = true;
else if (_rotationValue > 0.5f)
_delay = true;
[self displayLink].paused = false;
}
- (bool)isSucceed
{
return _isSucceed;
}
@end