mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Various Improvements
This commit is contained in:
parent
da9d213e8b
commit
c5e3bf2eff
@ -491,6 +491,9 @@ open class ListViewItemNode: ASDisplayNode, AccessibilityFocusableNode {
|
||||
if let update = update {
|
||||
update(progress, currentValue)
|
||||
}
|
||||
if progress == 1.0 {
|
||||
strongSelf.apparentHeightTransition = nil
|
||||
}
|
||||
}
|
||||
})
|
||||
self.setAnimationForKey("apparentHeight", animation: animation)
|
||||
|
@ -10,6 +10,8 @@
|
||||
@class TGCameraFlipButton;
|
||||
@class TGCameraTimeCodeView;
|
||||
@class TGCameraZoomView;
|
||||
@class TGCameraZoomModeView;
|
||||
@class TGCameraZoomWheelView;
|
||||
@class TGCameraToastView;
|
||||
@class TGMediaPickerPhotoCounterButton;
|
||||
@class TGMediaPickerPhotoStripView;
|
||||
@ -32,6 +34,8 @@
|
||||
TGMediaPickerPhotoStripView *_selectedPhotosView;
|
||||
|
||||
TGCameraZoomView *_zoomView;
|
||||
TGCameraZoomModeView *_zoomModeView;
|
||||
TGCameraZoomWheelView *_zoomWheelView;
|
||||
|
||||
@public
|
||||
TGModernButton *_cancelButton;
|
||||
|
@ -14,3 +14,24 @@
|
||||
- (void)hideAnimated:(bool)animated;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface TGCameraZoomModeView : UIView
|
||||
|
||||
@property (copy, nonatomic) void(^zoomChanged)(CGFloat zoomLevel, bool done);
|
||||
|
||||
@property (nonatomic, assign) CGFloat zoomLevel;
|
||||
- (void)setZoomLevel:(CGFloat)zoomLevel animated:(bool)animated;
|
||||
|
||||
- (void)setHidden:(bool)hidden animated:(bool)animated;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface TGCameraZoomWheelView : UIView
|
||||
|
||||
@property (nonatomic, assign) CGFloat zoomLevel;
|
||||
|
||||
- (void)setHidden:(bool)hidden animated:(bool)animated;
|
||||
|
||||
@end
|
||||
|
@ -700,14 +700,25 @@ const NSInteger PGCameraFrameRate = 30;
|
||||
|
||||
+ (AVCaptureDevice *)_deviceWithPosition:(AVCaptureDevicePosition)position
|
||||
{
|
||||
NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
|
||||
if (iosMajorVersion() >= 10 && position == AVCaptureDevicePositionBack) {
|
||||
AVCaptureDevice *device = nil;
|
||||
if (iosMajorVersion() >= 13) {
|
||||
device = [AVCaptureDevice defaultDeviceWithDeviceType:AVCaptureDeviceTypeBuiltInTripleCamera mediaType:AVMediaTypeVideo position:position];
|
||||
}
|
||||
if (device == nil) {
|
||||
device = [AVCaptureDevice defaultDeviceWithDeviceType:AVCaptureDeviceTypeBuiltInDualCamera mediaType:AVMediaTypeVideo position:position];
|
||||
}
|
||||
if (device != nil) {
|
||||
return device;
|
||||
}
|
||||
}
|
||||
|
||||
NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
|
||||
for (AVCaptureDevice *device in devices)
|
||||
{
|
||||
if (device.position == position)
|
||||
return device;
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
@ -68,6 +68,9 @@
|
||||
|
||||
TGCameraFlipButton *_topFlipButton;
|
||||
|
||||
TGCameraZoomModeView *_zoomModeView;
|
||||
TGCameraZoomWheelView *_zoomWheelView;
|
||||
|
||||
bool _hasResults;
|
||||
|
||||
CGFloat _topPanelOffset;
|
||||
@ -267,6 +270,7 @@
|
||||
// [self addSubview:_flashActiveView];
|
||||
|
||||
_toastView = [[TGCameraToastView alloc] initWithFrame:CGRectMake(0, frame.size.height - _bottomPanelHeight - 42, frame.size.width, 32)];
|
||||
_toastView.userInteractionEnabled = false;
|
||||
[self addSubview:_toastView];
|
||||
|
||||
_zoomView = [[TGCameraZoomView alloc] initWithFrame:CGRectMake(10, frame.size.height - _bottomPanelHeight - _bottomPanelOffset - 18, frame.size.width - 20, 1.5f)];
|
||||
@ -281,7 +285,33 @@
|
||||
[strongSelf _layoutFlashActiveViewForInterfaceOrientation:strongSelf->_interfaceOrientation zoomViewHidden:!active];
|
||||
} completion:nil];
|
||||
};
|
||||
[self addSubview:_zoomView];
|
||||
// [self addSubview:_zoomView];
|
||||
|
||||
_zoomModeView = [[TGCameraZoomModeView alloc] initWithFrame:CGRectMake(floor((frame.size.width - 129.0) / 2.0), frame.size.height - _bottomPanelHeight - _bottomPanelOffset - 18 - 43, 129, 43)];
|
||||
_zoomModeView.zoomChanged = ^(CGFloat zoomLevel, bool done) {
|
||||
__strong TGCameraMainPhoneView *strongSelf = weakSelf;
|
||||
if (strongSelf == nil)
|
||||
return;
|
||||
|
||||
if (!done) {
|
||||
[strongSelf->_zoomWheelView setZoomLevel:zoomLevel];
|
||||
[strongSelf->_zoomModeView setHidden:true animated:true];
|
||||
[strongSelf->_zoomWheelView setHidden:false animated:true];
|
||||
} else {
|
||||
[strongSelf->_zoomWheelView setZoomLevel:zoomLevel];
|
||||
[strongSelf->_zoomModeView setZoomLevel:zoomLevel animated:false];
|
||||
[strongSelf->_zoomModeView setHidden:false animated:true];
|
||||
[strongSelf->_zoomWheelView setHidden:true animated:true];
|
||||
}
|
||||
};
|
||||
[_zoomModeView setZoomLevel:1.0];
|
||||
[self addSubview:_zoomModeView];
|
||||
|
||||
_zoomWheelView = [[TGCameraZoomWheelView alloc] initWithFrame:CGRectMake(0.0, frame.size.height - _bottomPanelHeight - _bottomPanelOffset - 132, frame.size.width, 132)];
|
||||
[_zoomWheelView setHidden:true animated:false];
|
||||
[_zoomWheelView setZoomLevel:1.0];
|
||||
_zoomWheelView.userInteractionEnabled = false;
|
||||
[self addSubview:_zoomWheelView];
|
||||
|
||||
_flashControl.modeChanged = ^(PGCameraFlashMode mode)
|
||||
{
|
||||
@ -427,7 +457,7 @@
|
||||
{
|
||||
UIView *view = [super hitTest:point withEvent:event];
|
||||
|
||||
if ([view isDescendantOfView:_topPanelView] || [view isDescendantOfView:_bottomPanelView] || [view isDescendantOfView:_videoLandscapePanelView] || [view isDescendantOfView:_tooltipContainerView] || [view isDescendantOfView:_selectedPhotosView])
|
||||
if ([view isDescendantOfView:_topPanelView] || [view isDescendantOfView:_bottomPanelView] || [view isDescendantOfView:_videoLandscapePanelView] || [view isDescendantOfView:_tooltipContainerView] || [view isDescendantOfView:_selectedPhotosView] || [view isDescendantOfView:_zoomModeView] || view == _zoomModeView)
|
||||
return view;
|
||||
|
||||
return nil;
|
||||
|
@ -182,6 +182,7 @@
|
||||
- (void)setZoomLevel:(CGFloat)zoomLevel displayNeeded:(bool)displayNeeded
|
||||
{
|
||||
[_zoomView setZoomLevel:zoomLevel displayNeeded:displayNeeded];
|
||||
[_zoomModeView setZoomLevel:zoomLevel];
|
||||
}
|
||||
|
||||
- (void)zoomChangingEnded
|
||||
|
@ -1,6 +1,10 @@
|
||||
#import "TGCameraZoomView.h"
|
||||
#import "TGCameraInterfaceAssets.h"
|
||||
|
||||
#import "TGModernButton.h"
|
||||
#import "TGImageUtils.h"
|
||||
#import "TGPhotoEditorUtils.h"
|
||||
|
||||
#import "LegacyComponentsInternal.h"
|
||||
|
||||
@interface TGCameraZoomView ()
|
||||
@ -174,3 +178,287 @@
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface TGCameraZoomModeItemView: TGModernButton
|
||||
{
|
||||
UIImageView *_backgroundView;
|
||||
UILabel *_label;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation TGCameraZoomModeItemView
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self != nil) {
|
||||
_backgroundView = [[UIImageView alloc] initWithFrame:CGRectMake(3, 3, 37, 37)];
|
||||
_backgroundView.image = TGCircleImage(37, [UIColor colorWithWhite:0.0 alpha:0.4]);
|
||||
|
||||
_label = [[UILabel alloc] initWithFrame:self.bounds];
|
||||
_label.textAlignment = NSTextAlignmentCenter;
|
||||
|
||||
[self addSubview:_backgroundView];
|
||||
[self addSubview:_label];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setValue:(NSString *)value selected:(bool)selected animated:(bool)animated {
|
||||
CGFloat scale = selected ? 1.0 : 0.7;
|
||||
CGFloat textScale = selected ? 1.0 : 0.85;
|
||||
|
||||
_label.text = value;
|
||||
_label.textColor = selected ? [TGCameraInterfaceAssets accentColor] : [UIColor whiteColor];
|
||||
_label.font = [TGCameraInterfaceAssets boldFontOfSize:13.0];
|
||||
|
||||
if (animated) {
|
||||
[UIView animateWithDuration:0.3f animations:^
|
||||
{
|
||||
_backgroundView.transform = CGAffineTransformMakeScale(scale, scale);
|
||||
_label.transform = CGAffineTransformMakeScale(textScale, textScale);
|
||||
}];
|
||||
} else {
|
||||
_backgroundView.transform = CGAffineTransformMakeScale(scale, scale);
|
||||
_label.transform = CGAffineTransformMakeScale(textScale, textScale);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface TGCameraZoomModeView ()
|
||||
{
|
||||
UIView *_backgroundView;
|
||||
|
||||
TGCameraZoomModeItemView *_leftItem;
|
||||
TGCameraZoomModeItemView *_centerItem;
|
||||
TGCameraZoomModeItemView *_rightItem;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation TGCameraZoomModeView
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame
|
||||
{
|
||||
self = [super initWithFrame:frame];
|
||||
if (self != nil)
|
||||
{
|
||||
_backgroundView = [[UIView alloc] initWithFrame:self.bounds];
|
||||
_backgroundView.backgroundColor = [UIColor colorWithWhite:0.0 alpha:0.15];
|
||||
_backgroundView.layer.cornerRadius = self.bounds.size.height / 2.0;
|
||||
|
||||
_leftItem = [[TGCameraZoomModeItemView alloc] initWithFrame:CGRectMake(0, 0, 43, 43)];
|
||||
[_leftItem addTarget:self action:@selector(leftPressed) forControlEvents:UIControlEventTouchUpInside];
|
||||
|
||||
_centerItem = [[TGCameraZoomModeItemView alloc] initWithFrame:CGRectMake(43, 0, 43, 43)];
|
||||
[_centerItem addTarget:self action:@selector(centerPressed) forControlEvents:UIControlEventTouchUpInside];
|
||||
|
||||
_rightItem = [[TGCameraZoomModeItemView alloc] initWithFrame:CGRectMake(86, 0, 43, 43)];
|
||||
[_rightItem addTarget:self action:@selector(rightPressed) forControlEvents:UIControlEventTouchUpInside];
|
||||
|
||||
[self addSubview:_backgroundView];
|
||||
[self addSubview:_leftItem];
|
||||
[self addSubview:_centerItem];
|
||||
[self addSubview:_rightItem];
|
||||
|
||||
UIPanGestureRecognizer *gestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGesture:)];
|
||||
[self addGestureRecognizer:gestureRecognizer];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
- (void)panGesture:(UIPanGestureRecognizer *)gestureRecognizer {
|
||||
CGPoint translation = [gestureRecognizer translationInView:self];
|
||||
|
||||
switch (gestureRecognizer.state) {
|
||||
case UIGestureRecognizerStateBegan:
|
||||
self.zoomChanged(_zoomLevel, false);
|
||||
break;
|
||||
|
||||
case UIGestureRecognizerStateChanged:
|
||||
_zoomLevel = MAX(0.5, MIN(10.0, _zoomLevel - translation.x / 100.0));
|
||||
self.zoomChanged(_zoomLevel, false);
|
||||
break;
|
||||
|
||||
case UIGestureRecognizerStateEnded:
|
||||
self.zoomChanged(_zoomLevel, true);
|
||||
break;
|
||||
|
||||
case UIGestureRecognizerStateCancelled:
|
||||
self.zoomChanged(_zoomLevel, true);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
[gestureRecognizer setTranslation:CGPointZero inView:self];
|
||||
}
|
||||
|
||||
- (void)leftPressed {
|
||||
[self setZoomLevel:0.5 animated:true];
|
||||
self.zoomChanged(0.5, true);
|
||||
}
|
||||
|
||||
- (void)centerPressed {
|
||||
[self setZoomLevel:1.0 animated:true];
|
||||
self.zoomChanged(1.0, true);
|
||||
}
|
||||
|
||||
- (void)rightPressed {
|
||||
[self setZoomLevel:2.0 animated:true];
|
||||
self.zoomChanged(2.0, true);
|
||||
}
|
||||
|
||||
- (void)setZoomLevel:(CGFloat)zoomLevel {
|
||||
[self setZoomLevel:zoomLevel animated:false];
|
||||
}
|
||||
|
||||
- (void)setZoomLevel:(CGFloat)zoomLevel animated:(bool)animated
|
||||
{
|
||||
_zoomLevel = zoomLevel;
|
||||
if (zoomLevel < 1.0) {
|
||||
[_leftItem setValue:[NSString stringWithFormat:@"%.1fx", zoomLevel] selected:true animated:animated];
|
||||
[_centerItem setValue:@"1" selected:false animated:animated];
|
||||
[_rightItem setValue:@"2" selected:false animated:animated];
|
||||
} else if (zoomLevel < 2.0) {
|
||||
[_leftItem setValue:@"0.5" selected:false animated:animated];
|
||||
if ((zoomLevel - 1.0) < FLT_EPSILON) {
|
||||
[_centerItem setValue:@"1x" selected:true animated:animated];
|
||||
} else {
|
||||
[_centerItem setValue:[NSString stringWithFormat:@"%.1fx", zoomLevel] selected:true animated:animated];
|
||||
}
|
||||
[_rightItem setValue:@"2" selected:false animated:animated];
|
||||
} else {
|
||||
[_leftItem setValue:@"0.5" selected:false animated:animated];
|
||||
[_centerItem setValue:@"1" selected:false animated:animated];
|
||||
|
||||
CGFloat near = round(zoomLevel);
|
||||
if (ABS(zoomLevel - near) < FLT_EPSILON) {
|
||||
[_rightItem setValue:[NSString stringWithFormat:@"%d", (int)zoomLevel] selected:true animated:animated];
|
||||
} else {
|
||||
[_rightItem setValue:[NSString stringWithFormat:@"%.1fx", zoomLevel] selected:true animated:animated];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setHidden:(BOOL)hidden
|
||||
{
|
||||
self.alpha = hidden ? 0.0f : 1.0f;
|
||||
super.hidden = hidden;
|
||||
}
|
||||
|
||||
- (void)setHidden:(bool)hidden animated:(bool)animated
|
||||
{
|
||||
if (animated)
|
||||
{
|
||||
super.hidden = false;
|
||||
self.userInteractionEnabled = false;
|
||||
|
||||
[UIView animateWithDuration:0.25f animations:^
|
||||
{
|
||||
self.alpha = hidden ? 0.0f : 1.0f;
|
||||
} completion:^(BOOL finished)
|
||||
{
|
||||
self.userInteractionEnabled = true;
|
||||
|
||||
if (finished)
|
||||
self.hidden = hidden;
|
||||
}];
|
||||
}
|
||||
else
|
||||
{
|
||||
self.alpha = hidden ? 0.0f : 1.0f;
|
||||
super.hidden = hidden;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
if (_leftItem.isHidden) {
|
||||
|
||||
} else {
|
||||
_leftItem.frame = CGRectMake(0, 0, 43, 43.0);
|
||||
_centerItem.frame = CGRectMake(43, 0, 43, 43.0);
|
||||
_rightItem.frame = CGRectMake(86, 0, 43, 43.0);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface TGCameraZoomWheelView ()
|
||||
{
|
||||
UIImageView *_backgroundView;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation TGCameraZoomWheelView
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame
|
||||
{
|
||||
self = [super initWithFrame:frame];
|
||||
if (self != nil)
|
||||
{
|
||||
self.clipsToBounds = true;
|
||||
|
||||
_backgroundView = [[UIImageView alloc] initWithFrame:CGRectMake(-28.0, 0.0, 446.0, 446.0)];
|
||||
_backgroundView.alpha = 0.75;
|
||||
|
||||
[self addSubview:_backgroundView];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setZoomLevel:(CGFloat)zoomLevel {
|
||||
zoomLevel = MAX(0.5, zoomLevel);
|
||||
_zoomLevel = zoomLevel;
|
||||
|
||||
CGFloat angle = 0.0;
|
||||
if (zoomLevel < 1.0) {
|
||||
CGFloat delta = (zoomLevel - 0.5) / 0.5;
|
||||
angle = TGDegreesToRadians(20.8) * (1.0 - delta);
|
||||
} else if (zoomLevel < 2.0) {
|
||||
CGFloat delta = zoomLevel - 1.0;
|
||||
angle = TGDegreesToRadians(-22.0) * delta;
|
||||
} else if (zoomLevel < 10.0) {
|
||||
CGFloat delta = (zoomLevel - 2.0) / 8.0;
|
||||
angle = TGDegreesToRadians(-22.0) + TGDegreesToRadians(-68.0) * delta;
|
||||
}
|
||||
|
||||
_backgroundView.transform = CGAffineTransformMakeRotation(angle);
|
||||
}
|
||||
|
||||
- (void)setHidden:(BOOL)hidden
|
||||
{
|
||||
self.alpha = hidden ? 0.0f : 1.0f;
|
||||
super.hidden = hidden;
|
||||
}
|
||||
|
||||
- (void)setHidden:(bool)hidden animated:(bool)animated
|
||||
{
|
||||
if (animated)
|
||||
{
|
||||
super.hidden = false;
|
||||
self.userInteractionEnabled = false;
|
||||
|
||||
[UIView animateWithDuration:0.25f animations:^
|
||||
{
|
||||
self.alpha = hidden ? 0.0f : 1.0f;
|
||||
} completion:^(BOOL finished)
|
||||
{
|
||||
self.userInteractionEnabled = true;
|
||||
|
||||
if (finished)
|
||||
self.hidden = hidden;
|
||||
}];
|
||||
}
|
||||
else
|
||||
{
|
||||
self.alpha = hidden ? 0.0f : 1.0f;
|
||||
super.hidden = hidden;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
@ -300,8 +300,8 @@ typedef enum
|
||||
}
|
||||
|
||||
CGFloat minSide = MIN(_wrapperView.frame.size.width, _wrapperView.frame.size.height);
|
||||
CGFloat diameter = minSide > 320.0f ? 240.0f : 216.0f;
|
||||
CGFloat shadowSize = minSide > 320.0f ? 21.0f : 19.0f;
|
||||
CGFloat diameter = minSide - 24.0f;
|
||||
CGFloat shadowSize = 21.0f;
|
||||
|
||||
CGFloat circleWrapperViewLength = diameter + shadowSize * 2.0;
|
||||
_circleWrapperView = [[UIView alloc] initWithFrame:(CGRect){
|
||||
|
@ -37,6 +37,10 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
|
||||
private var appliedCurrentlyPlaying = false
|
||||
private var appliedAutomaticDownload = false
|
||||
|
||||
private var animatingHeight: Bool {
|
||||
return self.apparentHeightTransition != nil
|
||||
}
|
||||
|
||||
private var forwardInfoNode: ChatMessageForwardInfoNode?
|
||||
private var forwardBackgroundNode: ASImageNode?
|
||||
|
||||
@ -51,9 +55,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
|
||||
private var currentSwipeToReplyTranslation: CGFloat = 0.0
|
||||
|
||||
private var recognizer: TapLongTapOrDoubleTapGestureRecognizer?
|
||||
|
||||
private var seekRecognizer: UIPanGestureRecognizer?
|
||||
|
||||
|
||||
private var currentSwipeAction: ChatControllerInteractionSwipeAction?
|
||||
|
||||
override var visibility: ListViewItemNodeVisibility {
|
||||
@ -82,6 +84,9 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
|
||||
if !strongSelf.interactiveVideoNode.frame.contains(location) {
|
||||
return false
|
||||
}
|
||||
if strongSelf.appliedCurrentlyPlaying && !strongSelf.interactiveVideoNode.isPlaying {
|
||||
return false
|
||||
}
|
||||
if let action = strongSelf.gestureRecognized(gesture: .tap, location: location, recognizer: nil) {
|
||||
if case .action = action {
|
||||
return false
|
||||
@ -161,6 +166,9 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
|
||||
if strongSelf.selectionNode != nil {
|
||||
return false
|
||||
}
|
||||
if strongSelf.appliedCurrentlyPlaying && !strongSelf.interactiveVideoNode.isPlaying {
|
||||
return false
|
||||
}
|
||||
let action = item.controllerInteraction.canSetupReply(item.message)
|
||||
strongSelf.currentSwipeAction = action
|
||||
if case .none = action {
|
||||
@ -172,12 +180,6 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
|
||||
return false
|
||||
}
|
||||
self.view.addGestureRecognizer(replyRecognizer)
|
||||
|
||||
let seekRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.seekGesture(_:)))
|
||||
seekRecognizer.isEnabled = false
|
||||
seekRecognizer.delegate = self
|
||||
self.seekRecognizer = seekRecognizer
|
||||
self.interactiveVideoNode.view.addGestureRecognizer(seekRecognizer)
|
||||
}
|
||||
|
||||
override func updateAccessibilityData(_ accessibilityData: ChatMessageAccessibilityData) {
|
||||
@ -540,14 +542,13 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
|
||||
videoLayoutData = .constrained(left: max(0.0, availableContentWidth - videoFrame.width), right: 0.0)
|
||||
}
|
||||
|
||||
if currentItem != nil && currentPlaying != isPlaying {
|
||||
} else {
|
||||
let animating = (currentItem != nil && currentPlaying != isPlaying) || strongSelf.animatingHeight
|
||||
if !animating {
|
||||
strongSelf.interactiveVideoNode.frame = videoFrame
|
||||
videoApply(videoLayoutData, transition)
|
||||
}
|
||||
|
||||
strongSelf.interactiveVideoNode.view.disablesInteractiveTransitionGestureRecognizer = isPlaying
|
||||
strongSelf.seekRecognizer?.isEnabled = isPlaying
|
||||
|
||||
strongSelf.contextSourceNode.contentRect = videoFrame
|
||||
strongSelf.containerNode.targetNodeForActivationProgressContentRect = strongSelf.contextSourceNode.contentRect
|
||||
@ -861,31 +862,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
if let panGestureRecognizer = gestureRecognizer as? UIPanGestureRecognizer, panGestureRecognizer === self.seekRecognizer {
|
||||
let velocity = panGestureRecognizer.velocity(in: self.interactiveVideoNode.view)
|
||||
return abs(velocity.x) > abs(velocity.y)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private var wasPlaying = false
|
||||
@objc func seekGesture(_ recognizer: UIPanGestureRecognizer) {
|
||||
let location = recognizer.location(in: self.interactiveVideoNode.view)
|
||||
switch recognizer.state {
|
||||
case .began:
|
||||
self.interactiveVideoNode.pause()
|
||||
case .changed:
|
||||
self.interactiveVideoNode.seekTo(Double(location.x / self.interactiveVideoNode.bounds.size.width))
|
||||
case .ended, .cancelled:
|
||||
self.interactiveVideoNode.seekTo(Double(location.x / self.interactiveVideoNode.bounds.size.width))
|
||||
self.interactiveVideoNode.play()
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@objc func swipeToReplyGesture(_ recognizer: ChatSwipeToReplyRecognizer) {
|
||||
switch recognizer.state {
|
||||
case .began:
|
||||
@ -1082,7 +1059,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
|
||||
override func addAccessoryItemNode(_ accessoryItemNode: ListViewAccessoryItemNode) {
|
||||
self.contextSourceNode.contentNode.addSubnode(accessoryItemNode)
|
||||
}
|
||||
|
||||
|
||||
override func animateFrameTransition(_ progress: CGFloat, _ currentValue: CGFloat) {
|
||||
super.animateFrameTransition(progress, currentValue)
|
||||
|
||||
|
@ -607,7 +607,6 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
|
||||
if self.statusNode == nil {
|
||||
let statusNode = RadialStatusNode(backgroundNodeColor: item.presentationData.theme.theme.chat.message.mediaOverlayControlColors.fillColor)
|
||||
self.isUserInteractionEnabled = false
|
||||
statusNode.frame = CGRect(origin: CGPoint(x: videoFrame.origin.x + floorToScreenPixels((videoFrame.size.width - 50.0) / 2.0), y: videoFrame.origin.y + floorToScreenPixels((videoFrame.size.height - 50.0) / 2.0)), size: CGSize(width: 50.0, height: 50.0))
|
||||
self.statusNode = statusNode
|
||||
self.addSubnode(statusNode)
|
||||
}
|
||||
@ -620,6 +619,8 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
self.statusNode?.frame = CGRect(origin: CGPoint(x: videoFrame.origin.x + floorToScreenPixels((videoFrame.size.width - 50.0) / 2.0), y: videoFrame.origin.y + floorToScreenPixels((videoFrame.size.height - 50.0) / 2.0)), size: CGSize(width: 50.0, height: 50.0))
|
||||
|
||||
var state: RadialStatusNodeState
|
||||
switch status.mediaStatus {
|
||||
case var .fetchStatus(fetchStatus):
|
||||
@ -680,6 +681,15 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
|
||||
playbackStatusNode = current
|
||||
} else {
|
||||
playbackStatusNode = InstantVideoRadialStatusNode(color: UIColor(white: 1.0, alpha: 0.6))
|
||||
playbackStatusNode.seekTo = { [weak self] position, play in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.seekTo(position)
|
||||
if play {
|
||||
strongSelf.play()
|
||||
}
|
||||
}
|
||||
self.addSubnode(playbackStatusNode)
|
||||
self.playbackStatusNode = playbackStatusNode
|
||||
}
|
||||
@ -696,9 +706,10 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
|
||||
} else {
|
||||
if let playbackStatusNode = self.playbackStatusNode {
|
||||
self.playbackStatusNode = nil
|
||||
playbackStatusNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak playbackStatusNode] _ in
|
||||
playbackStatusNode?.removeFromSupernode()
|
||||
})
|
||||
playbackStatusNode.removeFromSupernode()
|
||||
// playbackStatusNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak playbackStatusNode] _ in
|
||||
// playbackStatusNode?.removeFromSupernode()
|
||||
// })
|
||||
}
|
||||
|
||||
self.durationNode?.status = .single(nil)
|
||||
@ -757,6 +768,14 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
|
||||
if !self.bounds.contains(point) {
|
||||
return nil
|
||||
}
|
||||
if let playbackNode = self.playbackStatusNode, !self.isPlaying, !playbackNode.frame.insetBy(dx: 0.15 * playbackNode.frame.width, dy: 0.15 * playbackNode.frame.height).contains(point) {
|
||||
let distanceFromCenter = point.distanceTo(playbackNode.position)
|
||||
if distanceFromCenter < 0.15 * playbackNode.frame.width {
|
||||
return self.view
|
||||
} else {
|
||||
return playbackNode.view
|
||||
}
|
||||
}
|
||||
if let statusNode = self.statusNode, statusNode.supernode != nil, !statusNode.isHidden, statusNode.frame.contains(point) {
|
||||
return self.view
|
||||
}
|
||||
@ -831,6 +850,14 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
var isPlaying: Bool {
|
||||
if let status = self.status, case let .playbackStatus(playbackStatus) = status.mediaStatus, case .playing = playbackStatus {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func seekTo(_ position: Double) {
|
||||
if let duration = self.playbackStatusNode?.duration {
|
||||
self.videoNode?.seek(position * duration)
|
||||
|
@ -9,15 +9,35 @@ import LegacyComponents
|
||||
private final class InstantVideoRadialStatusNodeParameters: NSObject {
|
||||
let color: UIColor
|
||||
let progress: CGFloat
|
||||
let dimProgress: CGFloat
|
||||
let playProgress: CGFloat
|
||||
|
||||
init(color: UIColor, progress: CGFloat) {
|
||||
init(color: UIColor, progress: CGFloat, dimProgress: CGFloat, playProgress: CGFloat) {
|
||||
self.color = color
|
||||
self.progress = progress
|
||||
self.dimProgress = dimProgress
|
||||
self.playProgress = playProgress
|
||||
}
|
||||
}
|
||||
|
||||
final class InstantVideoRadialStatusNode: ASDisplayNode {
|
||||
private extension CGFloat {
|
||||
var degrees: CGFloat {
|
||||
return self * CGFloat(180) / .pi
|
||||
}
|
||||
}
|
||||
|
||||
private extension CGPoint {
|
||||
func angle(to otherPoint: CGPoint) -> CGFloat {
|
||||
let originX = otherPoint.x - x
|
||||
let originY = otherPoint.y - y
|
||||
let bearingRadians = atan2f(Float(originY), Float(originX))
|
||||
return CGFloat(bearingRadians)
|
||||
}
|
||||
}
|
||||
|
||||
final class InstantVideoRadialStatusNode: ASDisplayNode, UIGestureRecognizerDelegate {
|
||||
private let color: UIColor
|
||||
private let hapticFeedback = HapticFeedback()
|
||||
|
||||
private var effectiveProgress: CGFloat = 0.0 {
|
||||
didSet {
|
||||
@ -25,6 +45,22 @@ final class InstantVideoRadialStatusNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
private var seeking = false
|
||||
private var seekingProgress: CGFloat?
|
||||
|
||||
private var dimmed = false
|
||||
private var effectiveDimProgress: CGFloat = 0.0 {
|
||||
didSet {
|
||||
self.setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
private var effectivePlayProgress: CGFloat = 0.0 {
|
||||
didSet {
|
||||
self.setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
private var _statusValue: MediaPlayerStatus?
|
||||
private var statusValue: MediaPlayerStatus? {
|
||||
get {
|
||||
@ -58,6 +94,11 @@ final class InstantVideoRadialStatusNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
var tapGestureRecognizer: UITapGestureRecognizer?
|
||||
var panGestureRecognizer: UIPanGestureRecognizer?
|
||||
|
||||
var seekTo: ((Double, Bool) -> Void)?
|
||||
|
||||
init(color: UIColor) {
|
||||
self.color = color
|
||||
|
||||
@ -66,19 +107,113 @@ final class InstantVideoRadialStatusNode: ASDisplayNode {
|
||||
self.isOpaque = false
|
||||
|
||||
self.statusDisposable = (self.statusValuePromise.get()
|
||||
|> deliverOnMainQueue).start(next: { [weak self] status in
|
||||
if let strongSelf = self {
|
||||
strongSelf.statusValue = status
|
||||
}
|
||||
})
|
||||
|> deliverOnMainQueue).start(next: { [weak self] status in
|
||||
if let strongSelf = self {
|
||||
strongSelf.statusValue = status
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.statusDisposable?.dispose()
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))
|
||||
tapGestureRecognizer.delegate = self
|
||||
self.view.addGestureRecognizer(tapGestureRecognizer)
|
||||
|
||||
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:)))
|
||||
panGestureRecognizer.delegate = self
|
||||
self.view.addGestureRecognizer(panGestureRecognizer)
|
||||
}
|
||||
|
||||
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
if gestureRecognizer === self.tapGestureRecognizer || gestureRecognizer === self.panGestureRecognizer {
|
||||
let center = CGPoint(x: self.bounds.width / 2.0, y: self.bounds.height / 2.0)
|
||||
let location = gestureRecognizer.location(in: self.view)
|
||||
let distanceFromCenter = location.distanceTo(center)
|
||||
if distanceFromCenter < self.bounds.width * 0.15 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
if gestureRecognizer === self.panGestureRecognizer, let otherGestureRecognizer = otherGestureRecognizer as? UIPanGestureRecognizer {
|
||||
otherGestureRecognizer.isEnabled = false
|
||||
otherGestureRecognizer.isEnabled = true
|
||||
return true
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@objc private func tapGesture(_ gestureRecognizer: UITapGestureRecognizer) {
|
||||
let center = CGPoint(x: self.bounds.width / 2.0, y: self.bounds.height / 2.0)
|
||||
let location = gestureRecognizer.location(in: self.view)
|
||||
|
||||
var angle = center.angle(to: location) + CGFloat.pi / 2.0
|
||||
if angle < 0.0 {
|
||||
angle = CGFloat.pi * 2.0 + angle
|
||||
}
|
||||
let fraction = max(0.0, min(1.0, Double(angle / (2.0 * CGFloat.pi))))
|
||||
self.seekTo?(min(0.99, fraction), true)
|
||||
}
|
||||
|
||||
@objc private func panGesture(_ gestureRecognizer: UIPanGestureRecognizer) {
|
||||
let center = CGPoint(x: self.bounds.width / 2.0, y: self.bounds.height / 2.0)
|
||||
let location = gestureRecognizer.location(in: self.view)
|
||||
var angle = center.angle(to: location) + CGFloat.pi / 2.0
|
||||
if angle < 0.0 {
|
||||
angle = CGFloat.pi * 2.0 + angle
|
||||
}
|
||||
let fraction = max(0.0, min(1.0, Double(angle / (2.0 * CGFloat.pi))))
|
||||
|
||||
switch gestureRecognizer.state {
|
||||
case .began:
|
||||
self.seeking = true
|
||||
|
||||
let playAnimation = POPSpringAnimation()
|
||||
playAnimation.property = POPAnimatableProperty.property(withName: "playProgress", initializer: { property in
|
||||
property?.readBlock = { node, values in
|
||||
values?.pointee = (node as! InstantVideoRadialStatusNode).effectivePlayProgress
|
||||
}
|
||||
property?.writeBlock = { node, values in
|
||||
(node as! InstantVideoRadialStatusNode).effectivePlayProgress = values!.pointee
|
||||
}
|
||||
property?.threshold = 0.01
|
||||
}) as? POPAnimatableProperty
|
||||
playAnimation.fromValue = self.effectivePlayProgress as NSNumber
|
||||
playAnimation.toValue = 0.0 as NSNumber
|
||||
playAnimation.springSpeed = 20
|
||||
playAnimation.springBounciness = 8
|
||||
self.pop_add(playAnimation, forKey: "playProgress")
|
||||
case .changed:
|
||||
if let seekingProgress = self.seekingProgress {
|
||||
if seekingProgress > 0.98 && fraction > 0.0 && fraction < 0.05 {
|
||||
self.hapticFeedback.impact(.light)
|
||||
} else if seekingProgress > 0.0 && seekingProgress < 0.05 && fraction > 0.98 {
|
||||
self.hapticFeedback.impact(.light)
|
||||
}
|
||||
}
|
||||
self.seekTo?(min(0.99, fraction), false)
|
||||
self.seekingProgress = CGFloat(fraction)
|
||||
case .ended, .cancelled:
|
||||
self.seeking = false
|
||||
self.seekTo?(min(0.99, fraction), true)
|
||||
self.seekingProgress = nil
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
override func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? {
|
||||
return InstantVideoRadialStatusNodeParameters(color: self.color, progress: self.effectiveProgress)
|
||||
return InstantVideoRadialStatusNodeParameters(color: self.color, progress: self.effectiveProgress, dimProgress: self.effectiveDimProgress, playProgress: self.effectivePlayProgress)
|
||||
}
|
||||
|
||||
@objc override class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) {
|
||||
@ -93,20 +228,59 @@ final class InstantVideoRadialStatusNode: ASDisplayNode {
|
||||
if let parameters = parameters as? InstantVideoRadialStatusNodeParameters {
|
||||
context.setStrokeColor(parameters.color.cgColor)
|
||||
|
||||
if !parameters.dimProgress.isZero {
|
||||
context.setFillColor(UIColor(rgb: 0x000000, alpha: 0.35 * min(1.0, parameters.dimProgress)).cgColor)
|
||||
context.fillEllipse(in: bounds)
|
||||
}
|
||||
|
||||
context.setBlendMode(.normal)
|
||||
|
||||
var progress = parameters.progress
|
||||
let startAngle = -CGFloat.pi / 2.0
|
||||
let endAngle = CGFloat(progress) * 2.0 * CGFloat.pi + startAngle
|
||||
|
||||
progress = min(1.0, progress)
|
||||
|
||||
let lineWidth: CGFloat = 4.0
|
||||
var lineWidth: CGFloat = 4.0
|
||||
lineWidth += 1.0 * parameters.dimProgress
|
||||
|
||||
let pathDiameter = bounds.size.width - lineWidth - 8.0
|
||||
var pathDiameter = bounds.size.width - lineWidth - 8.0
|
||||
pathDiameter -= (18.0 * 2.0) * parameters.dimProgress
|
||||
|
||||
if !parameters.dimProgress.isZero {
|
||||
context.setStrokeColor(parameters.color.withAlphaComponent(0.2 * parameters.dimProgress).cgColor)
|
||||
context.setLineWidth(lineWidth)
|
||||
context.strokeEllipse(in: CGRect(x: (bounds.size.width - pathDiameter) / 2.0 , y: (bounds.size.height - pathDiameter) / 2.0, width: pathDiameter, height: pathDiameter))
|
||||
|
||||
if !parameters.playProgress.isZero {
|
||||
context.saveGState()
|
||||
context.translateBy(x: bounds.width / 2.0, y: bounds.height / 2.0)
|
||||
context.scaleBy(x: 1.0 + 1.4 * parameters.playProgress, y: 1.0 + 1.4 * parameters.playProgress)
|
||||
context.translateBy(x: -bounds.width / 2.0, y: -bounds.height / 2.0)
|
||||
|
||||
let iconSize = CGSize(width: 15.0, height: 18.0)
|
||||
context.translateBy(x: (bounds.width - iconSize.width) / 2.0 + 2.0, y: (bounds.height - iconSize.height) / 2.0)
|
||||
|
||||
context.setFillColor(UIColor(rgb: 0xffffff).withAlphaComponent(min(1.0, parameters.playProgress)).cgColor)
|
||||
let _ = try? drawSvgPath(context, path: "M1.71891969,0.209353049 C0.769586558,-0.350676705 0,0.0908839327 0,1.18800046 L0,16.8564753 C0,17.9569971 0.750549162,18.357187 1.67393713,17.7519379 L14.1073836,9.60224049 C15.0318735,8.99626906 15.0094718,8.04970371 14.062401,7.49100858 L1.71891969,0.209353049 ")
|
||||
context.fillPath()
|
||||
|
||||
context.restoreGState()
|
||||
}
|
||||
}
|
||||
|
||||
context.setStrokeColor(parameters.color.cgColor)
|
||||
let path = UIBezierPath(arcCenter: CGPoint(x: bounds.size.width / 2.0, y: bounds.size.height / 2.0), radius: pathDiameter / 2.0, startAngle: startAngle, endAngle: endAngle, clockwise:true)
|
||||
path.lineWidth = lineWidth
|
||||
path.lineCapStyle = .round
|
||||
path.stroke()
|
||||
|
||||
let handleSide = 16.0 * min(1.0, (parameters.dimProgress * 2.0))
|
||||
let handleSize = CGSize(width: handleSide, height: handleSide)
|
||||
let handlePosition = CGPoint(x: 0.5 * pathDiameter * cos(endAngle), y: 0.5 * pathDiameter * sin(endAngle)).offsetBy(dx: bounds.size.width / 2.0, dy: bounds.size.height / 2.0)
|
||||
let handleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(handlePosition.x - handleSize.width / 2.0), y: floorToScreenPixels(handlePosition.y - handleSize.height / 2.0)), size: handleSize)
|
||||
context.setFillColor(UIColor.white.cgColor)
|
||||
context.fillEllipse(in: handleFrame)
|
||||
}
|
||||
}
|
||||
|
||||
@ -118,7 +292,53 @@ final class InstantVideoRadialStatusNode: ASDisplayNode {
|
||||
timestampAndDuration = nil
|
||||
}
|
||||
|
||||
if let (timestamp, duration, baseRate) = timestampAndDuration, let statusValue = self.statusValue {
|
||||
var dimmed = false
|
||||
if let statusValue = self.statusValue {
|
||||
dimmed = statusValue.status == .paused
|
||||
}
|
||||
if self.seeking {
|
||||
dimmed = true
|
||||
}
|
||||
if dimmed != self.dimmed {
|
||||
self.dimmed = dimmed
|
||||
|
||||
let animation = POPSpringAnimation()
|
||||
animation.property = POPAnimatableProperty.property(withName: "dimProgress", initializer: { property in
|
||||
property?.readBlock = { node, values in
|
||||
values?.pointee = (node as! InstantVideoRadialStatusNode).effectiveDimProgress
|
||||
}
|
||||
property?.writeBlock = { node, values in
|
||||
(node as! InstantVideoRadialStatusNode).effectiveDimProgress = values!.pointee
|
||||
}
|
||||
property?.threshold = 0.01
|
||||
}) as? POPAnimatableProperty
|
||||
animation.fromValue = self.effectiveDimProgress as NSNumber
|
||||
animation.toValue = (dimmed ? 1.0 : 0.0) as NSNumber
|
||||
animation.springSpeed = 20
|
||||
animation.springBounciness = 8
|
||||
self.pop_add(animation, forKey: "dimProgress")
|
||||
|
||||
let playAnimation = POPSpringAnimation()
|
||||
playAnimation.property = POPAnimatableProperty.property(withName: "playProgress", initializer: { property in
|
||||
property?.readBlock = { node, values in
|
||||
values?.pointee = (node as! InstantVideoRadialStatusNode).effectivePlayProgress
|
||||
}
|
||||
property?.writeBlock = { node, values in
|
||||
(node as! InstantVideoRadialStatusNode).effectivePlayProgress = values!.pointee
|
||||
}
|
||||
property?.threshold = 0.01
|
||||
}) as? POPAnimatableProperty
|
||||
playAnimation.fromValue = self.effectivePlayProgress as NSNumber
|
||||
playAnimation.toValue = (dimmed ? 1.0 : 0.0) as NSNumber
|
||||
playAnimation.springSpeed = 20
|
||||
playAnimation.springBounciness = 8
|
||||
self.pop_add(playAnimation, forKey: "playProgress")
|
||||
}
|
||||
|
||||
if self.seeking, let progress = self.seekingProgress {
|
||||
self.pop_removeAnimation(forKey: "progress")
|
||||
self.effectiveProgress = progress
|
||||
} else if let (timestamp, duration, baseRate) = timestampAndDuration, let statusValue = self.statusValue {
|
||||
let progress = CGFloat(timestamp / duration)
|
||||
|
||||
if progress.isNaN || !progress.isFinite {
|
||||
@ -148,6 +368,9 @@ final class InstantVideoRadialStatusNode: ASDisplayNode {
|
||||
self.pop_add(animation, forKey: "progress")
|
||||
}
|
||||
} else {
|
||||
self.pop_removeAnimation(forKey: "dimProgress")
|
||||
self.effectiveDimProgress = 0.0
|
||||
|
||||
self.pop_removeAnimation(forKey: "progress")
|
||||
self.effectiveProgress = 0.0
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user