Swiftgram/submodules/LegacyComponents/Sources/TGModernConversationTitleActivityIndicator.m
2020-02-22 15:38:54 +04:00

373 lines
13 KiB
Objective-C

#import "TGModernConversationTitleActivityIndicator.h"
#import "LegacyComponentsInternal.h"
#import "TGColor.h"
#import "TGImageUtils.h"
#import "POPBasicAnimation.h"
#import <math.h>
typedef enum {
TGModernConversationTitleActivityIndicatorTypeNone = 0,
TGModernConversationTitleActivityIndicatorTypeTyping = 1,
TGModernConversationTitleActivityIndicatorTypeAudioRecording = 2,
TGModernConversationTitleActivityIndicatorTypeVideoRecording = 3,
TGModernConversationTitleActivityIndicatorTypeUploading = 4,
TGModernConversationTitleActivityIndicatorTypePlaying = 5
} TGModernConversationTitleActivityIndicatorType;
@interface TGModernConversationTitleActivityIndicator ()
{
TGModernConversationTitleActivityIndicatorType _type;
UIColor *_color;
}
@property (nonatomic) CGFloat animationValue;
@end
@implementation TGModernConversationTitleActivityIndicator
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self != nil)
{
self.opaque = false;
self.backgroundColor = [UIColor clearColor];
_color = TGAccentColor();
}
return self;
}
- (void)setColor:(UIColor *)color {
_color = color;
[self setNeedsDisplay];
}
- (void)setNone
{
[self pop_removeAnimationForKey:@"animationValue"];
_type = TGModernConversationTitleActivityIndicatorTypeNone;
_animationValue = 0.0f;
}
- (void)_beginAnimationWithDuration:(NSTimeInterval)duration linear:(bool)linear
{
if ([self pop_animationForKey:@"animationValue"] != nil)
[self pop_removeAnimationForKey:@"animationValue"];
POPBasicAnimation *animation = [POPBasicAnimation animation];
animation.property = [POPAnimatableProperty propertyWithName:@"animationValue" initializer:^(POPMutableAnimatableProperty *prop)
{
prop.readBlock = ^(TGModernConversationTitleActivityIndicator *view, CGFloat values[])
{
if (view != nil)
values[0] = view.animationValue;
};
prop.writeBlock = ^(TGModernConversationTitleActivityIndicator *view, const CGFloat values[])
{
if (view != nil)
view.animationValue = values[0];
};
prop.threshold = 0.01f;
}];
animation.fromValue = @(0.0f);
animation.toValue = @(1.0f);
animation.timingFunction = [CAMediaTimingFunction functionWithName:linear ? kCAMediaTimingFunctionLinear : kCAMediaTimingFunctionEaseInEaseOut];
animation.duration = duration;
animation.repeatForever = true;
[self pop_addAnimation:animation forKey:@"animationValue"];
}
- (void)setTyping
{
if (_type != TGModernConversationTitleActivityIndicatorTypeTyping)
{
_type = TGModernConversationTitleActivityIndicatorTypeTyping;
[self setNeedsDisplay];
[self _beginAnimationWithDuration:0.7 linear:true];
}
}
- (void)setAudioRecording
{
if (_type != TGModernConversationTitleActivityIndicatorTypeAudioRecording)
{
_type = TGModernConversationTitleActivityIndicatorTypeAudioRecording;
[self setNeedsDisplay];
[self _beginAnimationWithDuration:0.7 linear:true];
}
}
- (void)setVideoRecording
{
if (_type != TGModernConversationTitleActivityIndicatorTypeVideoRecording)
{
_type = TGModernConversationTitleActivityIndicatorTypeVideoRecording;
[self setNeedsDisplay];
[self _beginAnimationWithDuration:0.9 linear:false];
}
}
- (void)setUploading
{
if (_type != TGModernConversationTitleActivityIndicatorTypeUploading)
{
_type = TGModernConversationTitleActivityIndicatorTypeUploading;
[self setNeedsDisplay];
[self _beginAnimationWithDuration:1.75 linear:false];
}
}
- (void)setPlaying
{
if (_type != TGModernConversationTitleActivityIndicatorTypePlaying)
{
_type = TGModernConversationTitleActivityIndicatorTypePlaying;
[self setNeedsDisplay];
[self _beginAnimationWithDuration:0.9 linear:true];
}
}
- (void)setAnimationValue:(CGFloat)animationValue
{
_animationValue = animationValue;
[self setNeedsDisplay];
}
const CGFloat minDiameter = 3.0f;
const CGFloat maxDiameter = 4.5f;
- (CGFloat)interpolateFrom:(CGFloat)from to:(CGFloat)to value:(CGFloat)value
{
return (1.0f - value) * from + value * to;
}
- (CGFloat)radiusFunction:(CGFloat)value timeOffset:(CGFloat)timeOffset
{
CGFloat clampedValue = value + timeOffset;
if (clampedValue > 1.0f)
{
clampedValue = clampedValue - CGFloor(clampedValue);
}
if (clampedValue < 0.4f)
{
return [self interpolateFrom:minDiameter to:maxDiameter value:clampedValue / 0.4f];
}
else if (clampedValue < 0.8f)
{
return [self interpolateFrom:maxDiameter to:minDiameter value:(clampedValue - 0.4f) / 0.4f];
}
else
return minDiameter;
}
- (void)drawRect:(CGRect)__unused rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
switch (_type)
{
case TGModernConversationTitleActivityIndicatorTypeTyping:
{
CGFloat leftPadding = 6.0f;
CGFloat topPadding = 9.0f;
CGFloat distance = 11.0f / 2.0f;
CGFloat minAlpha = 0.75f;
CGFloat deltaAlpha = 1.0f - minAlpha;
CGFloat radius = 0.0f;
radius = [self radiusFunction:_animationValue timeOffset:0.4f];
radius = (MAX(minDiameter, radius) - minDiameter) / (maxDiameter - minDiameter);
radius = radius * 1.5f;
UIColor *dotsColor = nil;
dotsColor = [_color colorWithAlphaComponent:(radius * deltaAlpha + minAlpha)];
CGContextSetFillColorWithColor(context, dotsColor.CGColor);
CGContextFillEllipseInRect(context, CGRectMake(leftPadding-minDiameter / 2.0f - radius / 2.0f, topPadding -minDiameter / 2.0f - radius / 2.0f, minDiameter + radius, minDiameter + radius));
radius = [self radiusFunction:_animationValue timeOffset:0.2f];
radius = (MAX(minDiameter, radius) - minDiameter) / (maxDiameter - minDiameter);
radius = radius * 1.5f;
dotsColor = [_color colorWithAlphaComponent:(radius * deltaAlpha + minAlpha)];
CGContextSetFillColorWithColor(context, dotsColor.CGColor);
CGContextFillEllipseInRect(context, CGRectMake(leftPadding + distance - minDiameter / 2.0f - radius / 2.0f, topPadding - minDiameter / 2.0f - radius / 2.0f, minDiameter + radius, minDiameter + radius));
radius = [self radiusFunction:_animationValue timeOffset:0.0f];
radius = (MAX(minDiameter, radius) - minDiameter)/(maxDiameter-minDiameter);
radius = radius * 1.5f;
dotsColor = [_color colorWithAlphaComponent:(radius * deltaAlpha + minAlpha)];
CGContextSetFillColorWithColor(context, dotsColor.CGColor);
CGContextFillEllipseInRect(context, CGRectMake(leftPadding + distance * 2.0f - minDiameter / 2.0f - radius / 2.0f, topPadding - minDiameter / 2.0f - radius / 2.0f, minDiameter + radius, minDiameter + radius));
break;
}
case TGModernConversationTitleActivityIndicatorTypeAudioRecording:
{
CGContextSetStrokeColorWithColor(context, _color.CGColor);
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetLineWidth(context, 2);
CGFloat delta = 5.0f;
CGFloat x = 3.0f;
CGFloat y = self.bounds.size.height / 2.0f + 1.0f;
CGFloat angle = (CGFloat)(18.0f * M_PI / 180.0);
CGFloat animationValue = _animationValue * delta;
CGFloat radius;
CGFloat alpha;
radius = animationValue;
alpha = radius/(3*delta);
alpha = 1.0f - (CGFloat)pow(cos(alpha * M_PI), 50);
CGContextSetAlpha(context, alpha);
CGContextBeginPath(context);
CGContextAddArc(context, x, y, radius, -angle, angle, 0);
CGContextStrokePath(context);
radius = animationValue + delta;
alpha = radius / (3.0f * delta);
alpha = 1.0f - (CGFloat)pow(cos(alpha * M_PI), 10);
CGContextSetAlpha(context, alpha);
CGContextBeginPath(context);
CGContextAddArc(context, x, y, radius, -angle, angle, 0);
CGContextStrokePath(context);
radius = animationValue + delta*2;
alpha = radius / (3.0f * delta);
alpha = 1.0f - (CGFloat)pow(cos(alpha * M_PI), 10);
CGContextSetAlpha(context, alpha);
CGContextBeginPath(context);
CGContextAddArc(context, x, y, radius, -angle, angle, 0);
CGContextStrokePath(context);
break;
}
case TGModernConversationTitleActivityIndicatorTypeVideoRecording:
{
CGContextSetFillColorWithColor(context, _color.CGColor);
CGFloat animValue = _animationValue;
if (animValue < 0.5f)
animValue /= 0.5f;
else
animValue = (1 - animValue) / 0.5f;
CGFloat alpha = 1.0f - animValue * 0.6;
CGFloat radius = 3.5f - animValue * 0.66f;
CGContextSetAlpha(context, alpha);
CGContextFillEllipseInRect(context, CGRectMake(16.0f - radius, 9.0 - radius, radius * 2.0f, radius * 2.0f));
break;
}
case TGModernConversationTitleActivityIndicatorTypeUploading:
{
CGFloat leftPadding = 11.0f / 2.0f - 1.0f;
CGFloat topPadding = 12.0f / 2.0f + 1.0f;
CGFloat progressWidth = 26.0f / 2.0f;
CGFloat progressHeight = 8.0f / 2.0f;
CGFloat progress;
CGFloat round = 1.25;
UIBezierPath *path;
UIColor *dotsColor = _color;
CGContextSetFillColorWithColor(context, dotsColor.CGColor);
path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(leftPadding, topPadding, progressWidth, progressHeight) cornerRadius:round];
[path fillWithBlendMode:kCGBlendModeNormal alpha:1.0f];
dotsColor = [_color colorWithAlphaComponent:0.3f];
CGContextSetFillColorWithColor(context, dotsColor.CGColor);
path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(leftPadding, topPadding, progressWidth, progressHeight) cornerRadius:round];
[path fillWithBlendMode:kCGBlendModeNormal alpha:1.0f];
progress = [self interpolateFrom:0.0f to:progressWidth * 2.0f value:_animationValue];
dotsColor = _color;
CGContextSetFillColorWithColor(context, dotsColor.CGColor);
path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(leftPadding - progressWidth + progress, topPadding, progressWidth, progressHeight) cornerRadius:round];
[path fillWithBlendMode:kCGBlendModeSourceIn alpha:1.0f];
break;
}
case TGModernConversationTitleActivityIndicatorTypePlaying:
{
UIColor *mainColor = _color;
UIColor *dotsColor = [mainColor colorWithAlphaComponent:0.5f];
CGContextSetFillColorWithColor(context, dotsColor.CGColor);
CGFloat distance = 4.0f;
CGFloat x = (self.bounds.size.width - distance * 2) / 2.0f + 4.0f;
CGFloat y = self.bounds.size.height / 2.0f + 1.0f;
CGFloat radius = 1.0f;
CGFloat dotsProgress = ((int)(_animationValue * 100) % 50) / 50.0f;
CGFloat dotsX = 1.5f + x - distance * dotsProgress;
CGContextFillEllipseInRect(context, CGRectMake(dotsX - radius, y - radius, radius * 2.0f, radius * 2.0f));
CGContextFillEllipseInRect(context, CGRectMake(dotsX - radius + distance, y - radius, radius * 2.0f, radius * 2.0f));
CGContextSetAlpha(context, dotsProgress);
CGContextFillEllipseInRect(context, CGRectMake(dotsX - radius + distance * 2, y - radius, radius * 2.0f, radius * 2.0f));
CGContextSetAlpha(context, 1.0f);
CGFloat angle = 42.0f * M_PI / 180.0;
radius = 3.5f;
bool closing = (int)(_animationValue * 4) % 2;
CGFloat bite = ((int)(_animationValue * 100.0f) % 25) / 25.0f;
if (closing)
bite = 1.0f - bite;
CGFloat startAngle = [self interpolateFrom:0.0f to:-angle value:bite];
CGFloat endAngle = [self interpolateFrom:0.0f to:angle value:bite];
if (bite < FLT_EPSILON)
{
startAngle = M_PI * 2;
endAngle = 0;
}
x = radius + 4.5f;
CGContextSetAlpha(context, 1.0f);
CGContextSetFillColorWithColor(context, mainColor.CGColor);
CGContextBeginPath(context);
CGContextMoveToPoint(context, x, y);
CGContextAddArc(context, x, y, radius, startAngle, endAngle, true);
CGContextFillPath(context);
break;
}
default:
break;
}
}
@end