Various Improvements

This commit is contained in:
Ilya Laktyushin 2021-07-24 09:52:45 +03:00
parent da9d213e8b
commit c5e3bf2eff
11 changed files with 644 additions and 59 deletions

View File

@ -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)

View File

@ -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;

View File

@ -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

View File

@ -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;
}

View File

@ -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;

View File

@ -182,6 +182,7 @@
- (void)setZoomLevel:(CGFloat)zoomLevel displayNeeded:(bool)displayNeeded
{
[_zoomView setZoomLevel:zoomLevel displayNeeded:displayNeeded];
[_zoomModeView setZoomLevel:zoomLevel];
}
- (void)zoomChangingEnded

View File

@ -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

View File

@ -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){

View File

@ -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)

View File

@ -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)

View File

@ -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
}