Video avatar fixes

This commit is contained in:
Ilya Laktyushin 2020-07-14 20:08:04 +03:00
parent 65c6e51e2c
commit 63a32bcbd8
23 changed files with 340 additions and 265 deletions

View File

@ -113,6 +113,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
private var nameOrder: PresentationPersonNameOrder
private var dateTimeFormat: PresentationDateTimeFormat
private let contentNode: ASDisplayNode
private let deleteButton: UIButton
private let actionButton: UIButton
private let maskNode: ASDisplayNode
@ -217,6 +218,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
}
didSet {
if let scrubberView = self.scrubberView {
scrubberView.setCollapsed(self.visibilityAlpha < 1.0 ? true : false, animated: false)
self.view.addSubview(scrubberView)
scrubberView.updateScrubbingVisual = { [weak self] value in
guard let strongSelf = self else {
@ -248,6 +250,12 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
}
}
override func setVisibilityAlpha(_ alpha: CGFloat) {
self.visibilityAlpha = alpha
self.contentNode.alpha = alpha
self.scrubberView?.setCollapsed(alpha < 1.0 ? true : false, animated: true)
}
init(context: AccountContext, presentationData: PresentationData, present: @escaping (ViewController, Any?) -> Void = { _, _ in }) {
self.context = context
self.presentationData = presentationData
@ -256,6 +264,8 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
self.nameOrder = presentationData.nameDisplayOrder
self.dateTimeFormat = presentationData.dateTimeFormat
self.contentNode = ASDisplayNode()
self.deleteButton = UIButton()
self.actionButton = UIButton()
@ -300,6 +310,8 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
super.init()
self.addSubnode(self.contentNode)
self.textNode.highlightAttributeAction = { attributes in
let highlightedAttributes = [TelegramTextAttributes.URL,
TelegramTextAttributes.PeerMention,
@ -326,21 +338,21 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
}
}
self.view.addSubview(self.deleteButton)
self.view.addSubview(self.actionButton)
self.addSubnode(self.scrollWrapperNode)
self.contentNode.view.addSubview(self.deleteButton)
self.contentNode.view.addSubview(self.actionButton)
self.contentNode.addSubnode(self.scrollWrapperNode)
self.scrollWrapperNode.addSubnode(self.scrollNode)
self.scrollNode.addSubnode(self.textNode)
self.addSubnode(self.authorNameNode)
self.addSubnode(self.dateNode)
self.contentNode.addSubnode(self.authorNameNode)
self.contentNode.addSubnode(self.dateNode)
self.addSubnode(self.backwardButton)
self.addSubnode(self.forwardButton)
self.addSubnode(self.playbackControlButton)
self.contentNode.addSubnode(self.backwardButton)
self.contentNode.addSubnode(self.forwardButton)
self.contentNode.addSubnode(self.playbackControlButton)
self.addSubnode(self.statusNode)
self.addSubnode(self.statusButtonNode)
self.contentNode.addSubnode(self.statusNode)
self.contentNode.addSubnode(self.statusButtonNode)
self.deleteButton.addTarget(self, action: #selector(self.deleteButtonPressed), for: [.touchUpInside])
self.actionButton.addTarget(self, action: #selector(self.actionButtonPressed), for: [.touchUpInside])
@ -673,6 +685,8 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
videoFrameTextNode.frame = CGRect(origin: CGPoint(x: CGFloat(textOffset), y: imageFrame.size.height - videoFrameTextNode.bounds.height - 5.0), size: videoFrameTextNode.bounds.size)
}
self.contentNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: width, height: panelHeight))
return panelHeight
}

View File

@ -120,6 +120,15 @@ final class ChatVideoGalleryItemScrubberView: UIView {
self.fetchStatusDisposable.dispose()
}
func setCollapsed(_ collapsed: Bool, animated: Bool) {
let alpha: CGFloat = collapsed ? 0.0 : 1.0
self.scrubberNode.setCollapsed(collapsed, animated: animated)
self.leftTimestampNode.alpha = alpha
self.rightTimestampNode.alpha = alpha
self.infoNode.alpha = alpha
}
func setStatusSignal(_ status: Signal<MediaPlayerStatus, NoError>?) {
let mappedStatus: Signal<MediaPlayerStatus, NoError>?
if let status = status {

View File

@ -63,6 +63,12 @@ open class GalleryControllerNode: ASDisplayNode, UIScrollViewDelegate, UIGesture
}
}
self.pager.updateControlsVisibility = { [weak self] visible in
if let strongSelf = self {
strongSelf.setControlsHidden(!visible, animated: true)
}
}
self.pager.dismiss = { [weak self] in
if let strongSelf = self {
var interfaceAnimationCompleted = false
@ -236,6 +242,10 @@ open class GalleryControllerNode: ASDisplayNode, UIScrollViewDelegate, UIGesture
transition.updateFrame(node: self.footerNode, frame: CGRect(origin: CGPoint(), size: layout.size))
if let navigationBar = self.navigationBar {
transition.updateFrame(node: navigationBar, frame: CGRect(origin: CGPoint(x: 0.0, y: self.areControlsHidden ? -navigationBarHeight : 0.0), size: CGSize(width: layout.size.width, height: navigationBarHeight)))
}
let displayThumbnailPanel = layout.size.width < layout.size.height
var thumbnailPanelHeight: CGFloat = 0.0
if let currentThumbnailContainerNode = self.currentThumbnailContainerNode {
@ -250,7 +260,7 @@ open class GalleryControllerNode: ASDisplayNode, UIScrollViewDelegate, UIGesture
self.updateThumbnailContainerNodeAlpha(transition)
}
self.footerNode.updateLayout(layout, footerContentNode: self.presentationState.footerContentNode, overlayContentNode: self.presentationState.overlayContentNode, thumbnailPanelHeight: thumbnailPanelHeight, transition: transition)
self.footerNode.updateLayout(layout, footerContentNode: self.presentationState.footerContentNode, overlayContentNode: self.presentationState.overlayContentNode, thumbnailPanelHeight: thumbnailPanelHeight, isHidden: self.areControlsHidden, transition: transition)
let previousContentHeight = self.scrollView.contentSize.height
let previousVerticalOffset = self.scrollView.contentOffset.y
@ -279,12 +289,20 @@ open class GalleryControllerNode: ASDisplayNode, UIScrollViewDelegate, UIGesture
self.footerNode.setVisibilityAlpha(alpha)
self.updateThumbnailContainerNodeAlpha(.immediate)
})
if let (navigationBarHeight, layout) = self.containerLayout {
self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .animated(duration: 0.3, curve: .easeInOut))
}
} else {
let alpha: CGFloat = self.areControlsHidden ? 0.0 : 1.0
self.navigationBar?.alpha = alpha
self.statusBar?.updateAlpha(alpha, transition: .immediate)
self.footerNode.setVisibilityAlpha(alpha)
self.updateThumbnailContainerNodeAlpha(.immediate)
if let (navigationBarHeight, layout) = self.containerLayout {
self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate)
}
}
}

View File

@ -20,6 +20,12 @@ open class GalleryFooterContentNode: ASDisplayNode {
public var requestLayout: ((ContainedViewLayoutTransition) -> Void)?
public var controllerInteraction: GalleryControllerInteraction?
var visibilityAlpha: CGFloat = 1.0
open func setVisibilityAlpha(_ alpha: CGFloat) {
self.visibilityAlpha = alpha
self.alpha = alpha
}
open func updateLayout(size: CGSize, metrics: LayoutMetrics, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, contentInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
return 0.0
}

View File

@ -8,7 +8,7 @@ public final class GalleryFooterNode: ASDisplayNode {
private var currentFooterContentNode: GalleryFooterContentNode?
private var currentOverlayContentNode: GalleryOverlayContentNode?
private var currentLayout: (ContainerViewLayout, CGFloat)?
private var currentLayout: (ContainerViewLayout, CGFloat, Bool)?
private let controllerInteraction: GalleryControllerInteraction
@ -27,12 +27,12 @@ public final class GalleryFooterNode: ASDisplayNode {
public func setVisibilityAlpha(_ alpha: CGFloat) {
self.visibilityAlpha = alpha
self.backgroundNode.alpha = alpha
self.currentFooterContentNode?.alpha = alpha
self.currentFooterContentNode?.setVisibilityAlpha(alpha)
self.currentOverlayContentNode?.setVisibilityAlpha(alpha)
}
public func updateLayout(_ layout: ContainerViewLayout, footerContentNode: GalleryFooterContentNode?, overlayContentNode: GalleryOverlayContentNode?, thumbnailPanelHeight: CGFloat, transition: ContainedViewLayoutTransition) {
self.currentLayout = (layout, thumbnailPanelHeight)
public func updateLayout(_ layout: ContainerViewLayout, footerContentNode: GalleryFooterContentNode?, overlayContentNode: GalleryOverlayContentNode?, thumbnailPanelHeight: CGFloat, isHidden: Bool, transition: ContainedViewLayoutTransition) {
self.currentLayout = (layout, thumbnailPanelHeight, isHidden)
let cleanInsets = layout.insets(options: [])
var dismissedCurrentFooterContentNode: GalleryFooterContentNode?
@ -43,11 +43,11 @@ public final class GalleryFooterNode: ASDisplayNode {
}
self.currentFooterContentNode = footerContentNode
if let footerContentNode = footerContentNode {
footerContentNode.alpha = self.visibilityAlpha
footerContentNode.setVisibilityAlpha(self.visibilityAlpha)
footerContentNode.controllerInteraction = self.controllerInteraction
footerContentNode.requestLayout = { [weak self] transition in
if let strongSelf = self, let (currentLayout, currentThumbnailPanelHeight) = strongSelf.currentLayout {
strongSelf.updateLayout(currentLayout, footerContentNode: strongSelf.currentFooterContentNode, overlayContentNode: strongSelf.currentOverlayContentNode, thumbnailPanelHeight: currentThumbnailPanelHeight, transition: transition)
if let strongSelf = self, let (currentLayout, currentThumbnailPanelHeight, isHidden) = strongSelf.currentLayout {
strongSelf.updateLayout(currentLayout, footerContentNode: strongSelf.currentFooterContentNode, overlayContentNode: strongSelf.currentOverlayContentNode, thumbnailPanelHeight: currentThumbnailPanelHeight, isHidden: isHidden, transition: transition)
}
}
self.addSubnode(footerContentNode)
@ -67,9 +67,10 @@ public final class GalleryFooterNode: ASDisplayNode {
}
var backgroundHeight: CGFloat = 0.0
let verticalOffset: CGFloat = isHidden ? (layout.size.width > layout.size.height ? 44.0 : 54.0) : 0.0
if let footerContentNode = self.currentFooterContentNode {
backgroundHeight = footerContentNode.updateLayout(size: layout.size, metrics: layout.metrics, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: cleanInsets.bottom, contentInset: thumbnailPanelHeight, transition: transition)
transition.updateFrame(node: footerContentNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - backgroundHeight), size: CGSize(width: layout.size.width, height: backgroundHeight)))
transition.updateFrame(node: footerContentNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - backgroundHeight + verticalOffset), size: CGSize(width: layout.size.width, height: backgroundHeight)))
if let dismissedCurrentFooterContentNode = dismissedCurrentFooterContentNode {
let contentTransition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)
footerContentNode.animateIn(fromHeight: dismissedCurrentFooterContentNode.bounds.height, previousContentNode: dismissedCurrentFooterContentNode, transition: contentTransition)
@ -78,16 +79,16 @@ public final class GalleryFooterNode: ASDisplayNode {
dismissedCurrentFooterContentNode.removeFromSupernode()
}
})
contentTransition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - backgroundHeight), size: CGSize(width: layout.size.width, height: backgroundHeight)))
contentTransition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - backgroundHeight + verticalOffset), size: CGSize(width: layout.size.width, height: backgroundHeight)))
} else {
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - backgroundHeight), size: CGSize(width: layout.size.width, height: backgroundHeight)))
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - backgroundHeight + verticalOffset), size: CGSize(width: layout.size.width, height: backgroundHeight)))
}
} else {
if let dismissedCurrentFooterContentNode = dismissedCurrentFooterContentNode {
dismissedCurrentFooterContentNode.removeFromSupernode()
}
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - backgroundHeight), size: CGSize(width: layout.size.width, height: backgroundHeight)))
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - backgroundHeight + verticalOffset), size: CGSize(width: layout.size.width, height: backgroundHeight)))
}
let contentTransition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)

View File

@ -21,6 +21,7 @@ open class GalleryItemNode: ASDisplayNode {
}
public var toggleControlsVisibility: () -> Void = { }
public var updateControlsVisibility: (Bool) -> Void = { _ in }
public var dismiss: () -> Void = { }
public var beginCustomDismiss: () -> Void = { }
public var completeCustomDismiss: () -> Void = { }

View File

@ -104,6 +104,7 @@ public final class GalleryPagerNode: ASDisplayNode, UIScrollViewDelegate, UIGest
private var invalidatedItems = false
public var centralItemIndexOffsetUpdated: (([GalleryItem]?, Int, CGFloat)?) -> Void = { _ in }
public var toggleControlsVisibility: () -> Void = { }
public var updateControlsVisibility: (Bool) -> Void = { _ in }
public var dismiss: () -> Void = { }
public var beginCustomDismiss: () -> Void = { }
public var completeCustomDismiss: () -> Void = { }
@ -446,6 +447,7 @@ public final class GalleryPagerNode: ASDisplayNode, UIScrollViewDelegate, UIGest
private func makeNodeForItem(at index: Int, synchronous: Bool) -> GalleryItemNode {
let node = self.items[index].node(synchronous: synchronous)
node.toggleControlsVisibility = self.toggleControlsVisibility
node.updateControlsVisibility = self.updateControlsVisibility
node.dismiss = self.dismiss
node.beginCustomDismiss = self.beginCustomDismiss
node.completeCustomDismiss = self.completeCustomDismiss

View File

@ -455,8 +455,17 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
}
}
private var controlsTimer: SwiftSignalKit.Timer?
private var previousPlaying: Bool?
private func setupControlsTimer() {
}
func setupItem(_ item: UniversalVideoGalleryItem) {
if self.item?.content.id != item.content.id {
self.previousPlaying = nil
if item.hideControls {
self.statusButtonNode.isHidden = true
}
@ -518,7 +527,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
if let item = strongSelf.item, let _ = item.content as? PlatformVideoContent {
strongSelf.videoNode?.play()
} else {
strongSelf.videoNode?.playOnceWithSound(playAndRecord: false, actionAtEnd: isAnimated ? .loop : .stop)
strongSelf.videoNode?.playOnceWithSound(playAndRecord: false, actionAtEnd: isAnimated ? .loop : strongSelf.actionAtEnd)
}
}
}
@ -652,6 +661,19 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
seekable = value.duration >= 30.0
}
if playing && strongSelf.previousPlaying != true {
let timer = SwiftSignalKit.Timer(timeout: 3.0, repeat: false, completion: { [weak self] in
self?.updateControlsVisibility(false)
self?.controlsTimer = nil
}, queue: Queue.mainQueue())
timer.start()
strongSelf.controlsTimer = timer
} else if !playing {
strongSelf.controlsTimer?.invalidate()
strongSelf.controlsTimer = nil
}
strongSelf.previousPlaying = playing
var fetching = false
if initialBuffering {
strongSelf.statusNode.transitionToState(.progress(color: .white, lineWidth: nil, value: nil, cancelEnabled: false), animated: false, completion: {})
@ -806,7 +828,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
videoNode.play()
} else if self.shouldAutoplayOnCentrality() {
self.initiallyActivated = true
videoNode.playOnceWithSound(playAndRecord: false, actionAtEnd: .stop)
videoNode.playOnceWithSound(playAndRecord: false, actionAtEnd: self.actionAtEnd)
}
} else {
if self.shouldAutoplayOnCentrality() {
@ -891,11 +913,20 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
} else {
self.hideStatusNodeUntilCentrality = false
self.statusButtonNode.isHidden = self.hideStatusNodeUntilCentrality || self.statusNodeShouldBeHidden
videoNode.playOnceWithSound(playAndRecord: false, seek: seek, actionAtEnd: .stop)
videoNode.playOnceWithSound(playAndRecord: false, seek: seek, actionAtEnd: self.actionAtEnd)
}
}
}
private var actionAtEnd: MediaPlayerPlayOnceWithSoundActionAtEnd {
if let item = self.item {
if let content = item.content as? NativeVideoContent, content.duration <= 30 {
return .loop
}
}
return .stop
}
override func animateIn(from node: (ASDisplayNode, CGRect, () -> (UIView?, UIView?)), addToTransitionSurface: (UIView) -> Void, completion: @escaping () -> Void) {
guard let videoNode = self.videoNode else {
return
@ -1277,18 +1308,18 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
if let fetchStatus = self.fetchStatus {
switch fetchStatus {
case .Local:
videoNode.playOnceWithSound(playAndRecord: false, seek: .none, actionAtEnd: .stop)
videoNode.playOnceWithSound(playAndRecord: false, seek: .none, actionAtEnd: self.actionAtEnd)
case .Remote:
if self.requiresDownload {
self.fetchControls?.fetch()
} else {
videoNode.playOnceWithSound(playAndRecord: false, seek: .none, actionAtEnd: .stop)
videoNode.playOnceWithSound(playAndRecord: false, seek: .none, actionAtEnd: self.actionAtEnd)
}
case .Fetching:
self.fetchControls?.cancel()
}
} else {
videoNode.playOnceWithSound(playAndRecord: false, seek: .none, actionAtEnd: .stop)
videoNode.playOnceWithSound(playAndRecord: false, seek: .none, actionAtEnd: self.actionAtEnd)
}
}
}

View File

@ -1,6 +1,7 @@
#import <UIKit/UIKit.h>
@class PGPhotoEditorView;
@class TGPhotoEntitiesContainerView;
@interface TGPhotoAvatarCropView : UIView
@ -20,7 +21,7 @@
@property (nonatomic, readonly) bool isTracking;
@property (nonatomic, readonly) bool isAnimating;
- (instancetype)initWithOriginalSize:(CGSize)originalSize screenSize:(CGSize)screenSize fullPreviewView:(PGPhotoEditorView *)fullPreviewView fullPaintingView:(UIImageView *)fullPaintingView;
- (instancetype)initWithOriginalSize:(CGSize)originalSize screenSize:(CGSize)screenSize fullPreviewView:(PGPhotoEditorView *)fullPreviewView fullPaintingView:(UIImageView *)fullPaintingView fullEntitiesView:(TGPhotoEntitiesContainerView *)fullEntitiesView;
- (void)setSnapshotImage:(UIImage *)image;
- (void)setSnapshotView:(UIView *)snapshotView;

View File

@ -7,20 +7,11 @@
@property(readwrite, nonatomic, copy) NSArray *blueControlPoints;
@property(readwrite, nonatomic, copy) NSArray *rgbCompositeControlPoints;
// Initialization and teardown
- (id)initWithACVData:(NSData*)data;
- (id)initWithACV:(NSString*)curveFilename;
- (id)initWithACVURL:(NSURL*)curveFileURL;
// This lets you set all three red, green, and blue tone curves at once.
// NOTE: Deprecated this function because this effect can be accomplished
// using the rgbComposite channel rather then setting all 3 R, G, and B channels.
- (void)setRGBControlPoints:(NSArray *)points DEPRECATED_ATTRIBUTE;
- (void)setPointsWithACV:(NSString*)curveFilename;
- (void)setPointsWithACVURL:(NSURL*)curveFileURL;
// Curve calculation
- (NSMutableArray *)getPreparedSplineCurve:(NSArray *)points;
- (NSMutableArray *)splineCurve:(NSArray *)points;

View File

@ -1,104 +1,8 @@
#import "GPUImageToneCurveFilter.h"
#pragma mark -
#pragma mark GPUImageACVFile Helper
// GPUImageACVFile
//
// ACV File format Parser
// Please refer to http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/PhotoshopFileFormats.htm#50577411_pgfId-1056330
//
@interface GPUImageACVFile : NSObject{
short version;
short totalCurves;
NSArray *rgbCompositeCurvePoints;
NSArray *redCurvePoints;
NSArray *greenCurvePoints;
NSArray *blueCurvePoints;
}
@property(strong,nonatomic) NSArray *rgbCompositeCurvePoints;
@property(strong,nonatomic) NSArray *redCurvePoints;
@property(strong,nonatomic) NSArray *greenCurvePoints;
@property(strong,nonatomic) NSArray *blueCurvePoints;
- (id) initWithACVFileData:(NSData*)data;
unsigned short int16WithBytes(Byte* bytes);
@end
@implementation GPUImageACVFile
@synthesize rgbCompositeCurvePoints, redCurvePoints, greenCurvePoints, blueCurvePoints;
- (id) initWithACVFileData:(NSData *)data {
self = [super init];
if (self != nil)
{
if (data.length == 0)
{
NSLog(@"failed to init ACVFile with data:%@", data);
return self;
}
Byte* rawBytes = (Byte*) [data bytes];
version = int16WithBytes(rawBytes);
rawBytes+=2;
totalCurves = int16WithBytes(rawBytes);
rawBytes+=2;
NSMutableArray *curves = [NSMutableArray new];
float pointRate = (1.0 / 255);
// The following is the data for each curve specified by count above
for (NSInteger x = 0; x<totalCurves; x++)
{
unsigned short pointCount = int16WithBytes(rawBytes);
rawBytes+=2;
NSMutableArray *points = [NSMutableArray new];
// point count * 4
// Curve points. Each curve point is a pair of short integers where
// the first number is the output value (vertical coordinate on the
// Curves dialog graph) and the second is the input value. All coordinates have range 0 to 255.
for (NSInteger y = 0; y<pointCount; y++)
{
unsigned short y = int16WithBytes(rawBytes);
rawBytes+=2;
unsigned short x = int16WithBytes(rawBytes);
rawBytes+=2;
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
[points addObject:[NSValue valueWithCGSize:CGSizeMake(x * pointRate, y * pointRate)]];
#else
[points addObject:[NSValue valueWithSize:CGSizeMake(x * pointRate, y * pointRate)]];
#endif
}
[curves addObject:points];
}
rgbCompositeCurvePoints = [curves objectAtIndex:0];
redCurvePoints = [curves objectAtIndex:1];
greenCurvePoints = [curves objectAtIndex:2];
blueCurvePoints = [curves objectAtIndex:3];
}
return self;
}
unsigned short int16WithBytes(Byte* bytes) {
uint16_t result;
memcpy(&result, bytes, sizeof(result));
return CFSwapInt16BigToHost(result);
}
@end
#pragma mark -
#pragma mark GPUImageToneCurveFilter Implementation
NSString *const kGPUImageToneCurveFragmentShaderString = SHADER_STRING
(
varying highp vec2 texCoord;
@ -158,57 +62,6 @@ NSString *const kGPUImageToneCurveFragmentShaderString = SHADER_STRING
return self;
}
// This pulls in Adobe ACV curve files to specify the tone curve
- (id)initWithACVData:(NSData *)data {
if (!(self = [super initWithFragmentShaderFromString:kGPUImageToneCurveFragmentShaderString]))
{
return nil;
}
toneCurveTextureUniform = [filterProgram uniformIndex:@"toneCurveTexture"];
GPUImageACVFile *curve = [[GPUImageACVFile alloc] initWithACVFileData:data];
[self setRgbCompositeControlPoints:curve.rgbCompositeCurvePoints];
[self setRedControlPoints:curve.redCurvePoints];
[self setGreenControlPoints:curve.greenCurvePoints];
[self setBlueControlPoints:curve.blueCurvePoints];
curve = nil;
return self;
}
- (id)initWithACV:(NSString*)curveFilename
{
return [self initWithACVURL:[[NSBundle mainBundle] URLForResource:curveFilename
withExtension:@"acv"]];
}
- (id)initWithACVURL:(NSURL*)curveFileURL
{
NSData* fileData = [NSData dataWithContentsOfURL:curveFileURL];
return [self initWithACVData:fileData];
}
- (void)setPointsWithACV:(NSString*)curveFilename
{
[self setPointsWithACVURL:[[NSBundle mainBundle] URLForResource:curveFilename withExtension:@"acv"]];
}
- (void)setPointsWithACVURL:(NSURL*)curveFileURL
{
NSData* fileData = [NSData dataWithContentsOfURL:curveFileURL];
GPUImageACVFile *curve = [[GPUImageACVFile alloc] initWithACVFileData:fileData];
[self setRgbCompositeControlPoints:curve.rgbCompositeCurvePoints];
[self setRedControlPoints:curve.redCurvePoints];
[self setGreenControlPoints:curve.greenCurvePoints];
[self setBlueControlPoints:curve.blueCurvePoints];
curve = nil;
}
- (void)dealloc
{
runSynchronouslyOnVideoProcessingQueue(^{

View File

@ -88,7 +88,7 @@ const CGFloat TGPhotoAvatarCropButtonsWrapperSize = 61.0f;
[self.view addSubview:_wrapperView];
PGPhotoEditor *photoEditor = self.photoEditor;
_cropView = [[TGPhotoAvatarCropView alloc] initWithOriginalSize:photoEditor.originalSize screenSize:[self referenceViewSize] fullPreviewView:nil fullPaintingView:nil];
_cropView = [[TGPhotoAvatarCropView alloc] initWithOriginalSize:photoEditor.originalSize screenSize:[self referenceViewSize] fullPreviewView:nil fullPaintingView:nil fullEntitiesView:nil];
[_cropView setCropRect:photoEditor.cropRect];
[_cropView setCropOrientation:photoEditor.cropOrientation];
[_cropView setCropMirrored:photoEditor.cropMirrored];

View File

@ -9,6 +9,7 @@
#import "TGPhotoEditorInterfaceAssets.h"
#import "PGPhotoEditorView.h"
#import "TGPhotoEntitiesContainerView.h"
const CGFloat TGPhotoAvatarCropViewOverscreenSize = 1000;
const CGFloat TGPhotoAvatarCropViewCurtainSize = 300;
@ -43,12 +44,13 @@ const CGFloat TGPhotoAvatarCropViewCurtainMargin = 200;
__weak PGPhotoEditorView *_fullPreviewView;
__weak UIImageView *_fullPaintingView;
__weak TGPhotoEntitiesContainerView *_fullEntitiesView;
}
@end
@implementation TGPhotoAvatarCropView
- (instancetype)initWithOriginalSize:(CGSize)originalSize screenSize:(CGSize)screenSize fullPreviewView:(PGPhotoEditorView *)fullPreviewView fullPaintingView:(UIImageView *)fullPaintingView
- (instancetype)initWithOriginalSize:(CGSize)originalSize screenSize:(CGSize)screenSize fullPreviewView:(PGPhotoEditorView *)fullPreviewView fullPaintingView:(UIImageView *)fullPaintingView fullEntitiesView:(TGPhotoEntitiesContainerView *)fullEntitiesView
{
self = [super initWithFrame:CGRectZero];
if (self != nil)
@ -83,12 +85,16 @@ const CGFloat TGPhotoAvatarCropViewCurtainMargin = 200;
CGFloat scale = _imageView.bounds.size.width / fittedSize.width;
_fullPreviewView.transform = CGAffineTransformMakeScale(self.cropMirrored ? -scale : scale, scale);
_fullPreviewView.userInteractionEnabled = false;
[_wrapperView addSubview:_fullPreviewView];
_fullPaintingView = fullPaintingView;
_fullPaintingView.frame = _fullPreviewView.frame;
[_wrapperView addSubview:_fullPreviewView];
[_wrapperView addSubview:_fullPaintingView];
_fullEntitiesView = fullEntitiesView;
_fullEntitiesView.frame = _fullPreviewView.frame;
[_wrapperView addSubview:_fullEntitiesView];
_flashView = [[UIView alloc] init];
_flashView.alpha = 0.0;
_flashView.backgroundColor = [UIColor whiteColor];

View File

@ -3,6 +3,7 @@
@class PGPhotoEditor;
@class PGPhotoTool;
@class TGPhotoEditorPreviewView;
@class TGPhotoEntitiesContainerView;
@class PGPhotoEditorView;
@class TGMediaPickerGalleryVideoScrubber;
@ -19,6 +20,7 @@
@property (nonatomic, weak) UIView *dotMarkerView;
@property (nonatomic, weak) PGPhotoEditorView *fullPreviewView;
@property (nonatomic, weak) UIImageView *fullPaintingView;
@property (nonatomic, weak) TGPhotoEntitiesContainerView *fullEntitiesView;
@property (nonatomic, weak) TGMediaPickerGalleryVideoScrubber *scrubberView;
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context photoEditor:(PGPhotoEditor *)photoEditor previewView:(TGPhotoEditorPreviewView *)previewView;

View File

@ -83,7 +83,7 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel
return;
if (strongSelf.isVideoPlaying != nil) {
strongSelf->_wasPlayingBeforeCropping = strongSelf.isVideoPlaying();
strongSelf->_wasPlayingBeforeCropping = strongSelf.isVideoPlaying() || strongSelf->_wasPlayingBeforeCropping;
}
strongSelf.controlVideoPlayback(false);
@ -103,7 +103,7 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel
};
PGPhotoEditor *photoEditor = self.photoEditor;
TGPhotoAvatarCropView *cropView = [[TGPhotoAvatarCropView alloc] initWithOriginalSize:photoEditor.originalSize screenSize:[self referenceViewSize] fullPreviewView:_fullPreviewView fullPaintingView:_fullPaintingView];
TGPhotoAvatarCropView *cropView = [[TGPhotoAvatarCropView alloc] initWithOriginalSize:photoEditor.originalSize screenSize:[self referenceViewSize] fullPreviewView:_fullPreviewView fullPaintingView:_fullPaintingView fullEntitiesView:_fullEntitiesView];
_cropView = cropView;
[_cropView setCropRect:photoEditor.cropRect];
[_cropView setCropOrientation:photoEditor.cropOrientation];

View File

@ -29,6 +29,7 @@
#import "TGPhotoToolbarView.h"
#import "TGPhotoEditorPreviewView.h"
#import "TGPhotoEntitiesContainerView.h"
#import <LegacyComponents/TGMenuView.h>
@ -69,6 +70,7 @@
TGPhotoToolbarView *_landscapeToolbarView;
TGPhotoEditorPreviewView *_previewView;
PGPhotoEditorView *_fullPreviewView;
TGPhotoEntitiesContainerView *_fullEntitiesView;
UIImageView *_fullPaintingView;
PGPhotoEditor *_photoEditor;
@ -341,6 +343,9 @@
_fullPaintingView = [[UIImageView alloc] init];
_fullPaintingView.frame = _fullPreviewView.frame;
_fullEntitiesView = [[TGPhotoEntitiesContainerView alloc] init];
_fullEntitiesView.frame = _fullPreviewView.frame;
}
_dotMarkerView = [[UIImageView alloc] initWithImage:TGCircleImage(7.0, [TGPhotoEditorInterfaceAssets accentColor])];
@ -1246,6 +1251,7 @@
case TGPhotoEditorCropTab:
{
_fullPaintingView.hidden = false;
[self updatePreviewView:true];
__block UIView *initialBackgroundView = nil;
@ -1259,6 +1265,7 @@
cropController.dotMarkerView = _dotMarkerView;
cropController.fullPreviewView = _fullPreviewView;
cropController.fullPaintingView = _fullPaintingView;
cropController.fullEntitiesView = _fullEntitiesView;
cropController.fromCamera = [self presentedFromCamera];
cropController.skipTransitionIn = skipInitialTransition;
if (snapshotImage != nil)
@ -1481,7 +1488,7 @@
case TGPhotoEditorPaintTab:
{
TGPhotoPaintController *paintController = [[TGPhotoPaintController alloc] initWithContext:_context photoEditor:_photoEditor previewView:_previewView];
TGPhotoPaintController *paintController = [[TGPhotoPaintController alloc] initWithContext:_context photoEditor:_photoEditor previewView:_previewView entitiesView:_fullEntitiesView];
paintController.stickersContext = _stickersContext;
paintController.toolbarLandscapeSize = TGPhotoEditorToolbarSize;
paintController.controlVideoPlayback = ^(bool play) {

View File

@ -11,7 +11,7 @@
@property (nonatomic, strong) id<TGPhotoPaintStickersContext> stickersContext;
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context photoEditor:(PGPhotoEditor *)photoEditor previewView:(TGPhotoEditorPreviewView *)previewView;
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context photoEditor:(PGPhotoEditor *)photoEditor previewView:(TGPhotoEditorPreviewView *)previewView entitiesView:(TGPhotoEntitiesContainerView *)entitiesView;
- (TGPaintingData *)paintingData;

View File

@ -139,7 +139,7 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
@implementation TGPhotoPaintController
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context photoEditor:(PGPhotoEditor *)photoEditor previewView:(TGPhotoEditorPreviewView *)previewView
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context photoEditor:(PGPhotoEditor *)photoEditor previewView:(TGPhotoEditorPreviewView *)previewView entitiesView:(TGPhotoEntitiesContainerView *)entitiesView
{
self = [super initWithContext:context];
if (self != nil)
@ -151,6 +151,7 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
self.photoEditor = photoEditor;
self.previewView = previewView;
_entitiesContainerView = entitiesView;
_brushes = @
[
@ -238,9 +239,11 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
_contentWrapperView.userInteractionEnabled = false;
[_contentView addSubview:_contentWrapperView];
_entitiesContainerView = [[TGPhotoEntitiesContainerView alloc] init];
_entitiesContainerView.clipsToBounds = true;
_entitiesContainerView.stickersContext = _stickersContext;
if (_entitiesContainerView == nil) {
_entitiesContainerView = [[TGPhotoEntitiesContainerView alloc] init];
_entitiesContainerView.clipsToBounds = true;
_entitiesContainerView.stickersContext = _stickersContext;
}
_entitiesContainerView.entitySelected = ^(TGPhotoPaintEntityView *sender)
{
__strong TGPhotoPaintController *strongSelf = weakSelf;

View File

@ -666,14 +666,16 @@ const CGFloat TGPhotoTextSelectionViewHandleSide = 30.0f;
[self.path appendPath:[UIBezierPath bezierPathWithRoundedRect:cur cornerRadius:_radius]];
if (i == 0) {
last = cur;
} else if (i > 0 && fabs(CGRectGetMaxY(last) - CGRectGetMinY(cur)) < 10.0) {
//&& fabs(CGRectGetMaxY(last) - CGRectGetMinY(cur)) < 10.0
} else if (i > 0) {
CGPoint a = cur.origin;
CGPoint b = CGPointMake(CGRectGetMaxX(cur), cur.origin.y);
CGPoint c = CGPointMake(last.origin.x, CGRectGetMaxY(last));
CGPoint d = CGPointMake(CGRectGetMaxX(last), CGRectGetMaxY(last));
if (a.x - c.x >= 2 * _radius) {
UIBezierPath * addPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(a.x - _radius, a.y + _radius) radius:_radius startAngle:M_PI_2 * 3 endAngle:0 clockwise:YES];
UIBezierPath *addPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(a.x - _radius, a.y + _radius) radius:_radius startAngle:M_PI_2 * 3 endAngle:0 clockwise:YES];
[addPath appendPath:[UIBezierPath bezierPathWithArcCenter:CGPointMake(a.x + _radius, a.y + _radius) radius:_radius startAngle:M_PI endAngle:3 * M_PI_2 clockwise:YES]];
[addPath addLineToPoint:CGPointMake(a.x - _radius, a.y)];
@ -686,7 +688,7 @@ const CGFloat TGPhotoTextSelectionViewHandleSide = 30.0f;
[self.path addArcWithCenter:CGPointMake(a.x + _radius, a.y - _radius) radius:_radius startAngle:M_PI_2 endAngle:M_PI clockwise:YES];
}
if (d.x - b.x >= 2 * _radius) {
UIBezierPath * addPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(b.x + _radius, b.y + _radius) radius:_radius startAngle:M_PI_2 * 3 endAngle:M_PI clockwise:NO];
UIBezierPath *addPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(b.x + _radius, b.y + _radius) radius:_radius startAngle:M_PI_2 * 3 endAngle:M_PI clockwise:NO];
[addPath appendPath:[UIBezierPath bezierPathWithArcCenter:CGPointMake(b.x - _radius, b.y + _radius) radius:_radius startAngle:0 endAngle:3 * M_PI_2 clockwise:NO]];
[addPath addLineToPoint:CGPointMake(b.x + _radius, b.y)];
[self.path appendPath:addPath];
@ -698,13 +700,13 @@ const CGFloat TGPhotoTextSelectionViewHandleSide = 30.0f;
[self.path addArcWithCenter:CGPointMake(b.x - _radius, b.y - _radius) radius:_radius startAngle:M_PI_2 endAngle:0 clockwise:NO];
}
if (c.x - a.x >= 2 * _radius) {
UIBezierPath * addPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(c.x - _radius, c.y - _radius) radius:_radius startAngle:M_PI_2 endAngle:0 clockwise:NO];
UIBezierPath *addPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(c.x - _radius, c.y - _radius) radius:_radius startAngle:M_PI_2 endAngle:0 clockwise:NO];
[addPath appendPath:[UIBezierPath bezierPathWithArcCenter:CGPointMake(c.x + _radius, c.y - _radius) radius:_radius startAngle:M_PI endAngle:M_PI_2 clockwise:NO]];
[addPath addLineToPoint:CGPointMake(c.x - _radius, c.y)];
[self.path appendPath:addPath];
}
if (b.x - d.x >= 2 * _radius) {
UIBezierPath * addPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(d.x + _radius, d.y - _radius) radius:_radius startAngle:M_PI_2 endAngle:M_PI clockwise:YES];
UIBezierPath *addPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(d.x + _radius, d.y - _radius) radius:_radius startAngle:M_PI_2 endAngle:M_PI clockwise:YES];
[addPath appendPath:[UIBezierPath bezierPathWithArcCenter:CGPointMake(d.x - _radius, d.y - _radius) radius:_radius startAngle:0 endAngle:M_PI_2 clockwise:YES]];
[addPath addLineToPoint:CGPointMake(d.x + _radius, d.y)];
[self.path appendPath:addPath];

View File

@ -195,7 +195,7 @@ SHADER_STRING
[composeFilter addTarget:sharpen];
self.sharpenFilter = sharpen;
self.initialFilters = @[maskGenerator,skinToneCurveFilter,dissolveFilter,composeFilter];
self.initialFilters = @[maskGenerator, skinToneCurveFilter, dissolveFilter, composeFilter];
self.terminalFilter = sharpen;
self.skinToneCurveFilter.rgbCompositeControlPoints = @[

View File

@ -488,6 +488,12 @@ public final class MediaPlayerScrubbingNode: ASDisplayNode {
})
}
deinit {
self.displayLink?.invalidate()
self.statusDisposable?.dispose()
self.bufferingStatusDisposable?.dispose()
}
private func setupContentNodes() {
if let subnodes = self.subnodes {
for subnode in subnodes {
@ -660,10 +666,27 @@ public final class MediaPlayerScrubbingNode: ASDisplayNode {
self.updateProgressAnimations()
}
deinit {
self.displayLink?.invalidate()
self.statusDisposable?.dispose()
self.bufferingStatusDisposable?.dispose()
public func setCollapsed(_ collapsed: Bool, animated: Bool) {
let alpha: CGFloat = collapsed ? 0.0 : 1.0
let backgroundScale: CGFloat = collapsed ? 0.4 : 1.0
let handleScale: CGFloat = collapsed ? 0.2 : 1.0
switch self.contentNodes {
case let .standard(node):
let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.3, curve: .easeInOut) : .immediate
node.foregroundContentNode.backgroundColor = collapsed ? .white : nil
if let handleNode = node.handleNodeContainer {
transition.updateAlpha(node: node.foregroundContentNode, alpha: collapsed ? 0.45 : 1.0)
transition.updateAlpha(node: handleNode, alpha: collapsed ? 0.0 : 1.0)
transition.updateTransformScale(node: handleNode, scale: CGPoint(x: 1.0, y: handleScale))
}
transition.updateAlpha(node: node.bufferingNode, alpha: alpha)
transition.updateAlpha(node: node.backgroundNode, alpha: alpha)
transition.updateTransformScale(node: node.foregroundContentNode, scale: CGPoint(x: 1.0, y: backgroundScale))
transition.updateTransformScale(node: node.backgroundNode, scale: CGPoint(x: 1.0, y: backgroundScale))
case .custom:
break
}
}
override public var frame: CGRect {
@ -772,8 +795,12 @@ public final class MediaPlayerScrubbingNode: ASDisplayNode {
switch self.contentNodes {
case let .standard(node):
let backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: floor((bounds.size.height - node.lineHeight) / 2.0)), size: CGSize(width: bounds.size.width, height: node.lineHeight))
node.backgroundNode.frame = backgroundFrame
node.foregroundContentNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: backgroundFrame.size.width, height: backgroundFrame.size.height))
node.backgroundNode.position = backgroundFrame.center
node.backgroundNode.bounds = CGRect(origin: CGPoint(), size: backgroundFrame.size)
let foregroundContentFrame = CGRect(origin: CGPoint(), size: CGSize(width: backgroundFrame.size.width, height: backgroundFrame.size.height))
node.foregroundContentNode.position = foregroundContentFrame.center
node.foregroundContentNode.bounds = CGRect(origin: CGPoint(), size: foregroundContentFrame.size)
node.bufferingNode.frame = backgroundFrame
node.bufferingNode.updateLayout(size: backgroundFrame.size, transition: .immediate)

View File

@ -188,7 +188,9 @@ final class PeerInfoAvatarListItemNode: ASDisplayNode {
let imageNode: TransformImageNode
private var videoNode: UniversalVideoNode?
private var videoContent: NativeVideoContent?
private var videoStartTimestamp: Double?
private let playbackStatusDisposable = MetaDisposable()
private let preloadDisposable = MetaDisposable()
let isReady = Promise<Bool>()
private var didSetReady: Bool = false
@ -202,7 +204,15 @@ final class PeerInfoAvatarListItemNode: ASDisplayNode {
}
}
var isCentral: Bool = false
var isCentral: Bool = false {
didSet {
if self.isCentral {
self.setupVideoPlayback()
} else {
// self.preloadDisposable.set(preloadVideoResource(postbox: self.context.account.postbox, resourceReference: ))
}
}
}
init(context: AccountContext, peerId: PeerId?) {
self.context = context
@ -215,16 +225,6 @@ final class PeerInfoAvatarListItemNode: ASDisplayNode {
self.imageNode.contentAnimations = [.firstUpdate, .subsequentUpdates]
self.addSubnode(self.imageNode)
self.imageNode.imageUpdated = { [weak self] _ in
guard let strongSelf = self else {
return
}
if !strongSelf.didSetReady {
strongSelf.didSetReady = true
strongSelf.isReady.set(.single(true))
}
}
}
deinit {
@ -240,6 +240,52 @@ final class PeerInfoAvatarListItemNode: ASDisplayNode {
}
}
private func setupVideoPlayback() {
guard let videoContent = self.videoContent, self.isCentral, self.videoNode == nil else {
return
}
let mediaManager = self.context.sharedContext.mediaManager
let videoNode = UniversalVideoNode(postbox: self.context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: videoContent, priority: .secondaryOverlay)
videoNode.isUserInteractionEnabled = false
videoNode.canAttachContent = true
videoNode.isHidden = true
if let _ = self.videoStartTimestamp {
self.playbackStatusDisposable.set((videoNode.status
|> map { status -> Bool in
if let status = status, case .playing = status.status {
return true
} else {
return false
}
}
|> filter { playing in
return playing
}
|> take(1)
|> deliverOnMainQueue).start(completed: { [weak self] in
if let strongSelf = self {
Queue.mainQueue().after(0.15) {
strongSelf.videoNode?.isHidden = false
}
}
}))
} else {
self.playbackStatusDisposable.set(nil)
videoNode.isHidden = false
}
videoNode.play()
self.videoNode = videoNode
let videoStartTimestamp = self.videoStartTimestamp
self.statusPromise.set(videoNode.status |> map { ($0, videoStartTimestamp) })
self.addSubnode(videoNode)
self.isReady.set(videoNode.ready |> map { return true })
}
func setup(item: PeerInfoAvatarListItem, synchronous: Bool) {
self.item = item
@ -266,57 +312,34 @@ final class PeerInfoAvatarListItemNode: ASDisplayNode {
self.imageNode.setSignal(chatAvatarGalleryPhoto(account: self.context.account, representations: representations, immediateThumbnailData: immediateThumbnailData, autoFetchFullSize: true, attemptSynchronously: synchronous), attemptSynchronously: synchronous, dispatchOnDisplayLink: false)
if let video = videoRepresentations.last, let id = id {
let mediaManager = self.context.sharedContext.mediaManager
let videoFileReference = FileMediaReference.standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.dimensions, flags: [])]))
let videoContent = NativeVideoContent(id: .profileVideo(id, nil), fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: true, autoFetchFullSizeThumbnail: true, startTimestamp: video.startTimestamp, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear)
if videoContent.id != self.videoContent?.id {
let videoNode = UniversalVideoNode(postbox: self.context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: videoContent, priority: .secondaryOverlay)
videoNode.isUserInteractionEnabled = false
videoNode.canAttachContent = true
videoNode.isHidden = true
if let _ = video.startTimestamp {
self.playbackStatusDisposable.set((videoNode.status
|> map { status -> Bool in
if let status = status, case .playing = status.status {
return true
} else {
return false
}
}
|> filter { playing in
return playing
}
|> take(1)
|> deliverOnMainQueue).start(completed: { [weak self] in
if let strongSelf = self {
Queue.mainQueue().after(0.15) {
strongSelf.videoNode?.isHidden = false
}
}
}))
} else {
self.playbackStatusDisposable.set(nil)
videoNode.isHidden = false
}
videoNode.play()
self.videoContent = videoContent
self.videoNode = videoNode
self.statusPromise.set(videoNode.status |> map { ($0, video.startTimestamp) })
self.addSubnode(videoNode)
self.videoStartTimestamp = video.startTimestamp
self.setupVideoPlayback()
}
} else {
if let videoNode = self.videoNode {
self.videoContent = nil
self.videoStartTimestamp = nil
self.videoNode = nil
videoNode.removeFromSupernode()
}
self.statusPromise.set(.single(nil))
self.imageNode.imageUpdated = { [weak self] _ in
guard let strongSelf = self else {
return
}
if !strongSelf.didSetReady {
strongSelf.didSetReady = true
strongSelf.isReady.set(.single(true))
}
}
}
}
@ -335,6 +358,67 @@ final class PeerInfoAvatarListItemNode: ASDisplayNode {
}
}
private class PeerInfoAvatarListLoadingStripNode: ASImageNode {
private var currentInHierarchy = false
let imageNode = ASImageNode()
override init() {
super.init()
self.addSubnode(self.imageNode)
}
override public var isHidden: Bool {
didSet {
self.updateAnimation()
}
}
private var isAnimating = false {
didSet {
if self.isAnimating != oldValue {
if self.isAnimating {
let basicAnimation = CABasicAnimation(keyPath: "opacity")
basicAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
basicAnimation.duration = 0.45
basicAnimation.fromValue = 0.1
basicAnimation.toValue = 0.75
basicAnimation.repeatCount = Float.infinity
basicAnimation.autoreverses = true
self.imageNode.layer.add(basicAnimation, forKey: "loading")
} else {
self.imageNode.layer.removeAnimation(forKey: "loading")
}
}
}
}
private func updateAnimation() {
self.isAnimating = !self.isHidden && self.currentInHierarchy
}
override public func willEnterHierarchy() {
super.willEnterHierarchy()
self.currentInHierarchy = true
self.updateAnimation()
}
override public func didExitHierarchy() {
super.didExitHierarchy()
self.currentInHierarchy = false
self.updateAnimation()
}
override func layout() {
super.layout()
self.imageNode.frame = self.bounds
}
}
final class PeerInfoAvatarListContainerNode: ASDisplayNode {
private let context: AccountContext
var peerId: PeerId?
@ -355,6 +439,7 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
private var itemNodes: [WrappedMediaResourceId: PeerInfoAvatarListItemNode] = [:]
private var stripNodes: [ASImageNode] = []
private var activeStripNode: ASImageNode
private var loadingStripNode: PeerInfoAvatarListLoadingStripNode
private let activeStripImage: UIImage
private var appliedStripNodeCurrentIndex: Int?
var currentIndex: Int = 0
@ -415,10 +500,15 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
}
private var playbackProgress: CGFloat?
private var loading: Bool = false
private func updateStatus() {
var position: CGFloat = 1.0
var loading = false
if let (status, videoStartTimestamp) = self.playerStatus, let playerStatus = status {
var playerPosition: Double
if case .buffering = playerStatus.status {
loading = true
}
if !playerStatus.generationTimestamp.isZero, case .playing = playerStatus.status {
playerPosition = playerStatus.timestamp + (CACurrentMediaTime() - playerStatus.generationTimestamp)
} else {
@ -443,6 +533,7 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
if let size = self.validLayout {
self.playbackProgress = position
self.loading = loading
self.updateItems(size: size, transition: .immediate, stripTransition: .animated(duration: 0.3, curve: .spring))
}
}
@ -504,6 +595,9 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
self.activeStripNode = ASImageNode()
self.activeStripNode.image = self.activeStripImage
self.loadingStripNode = PeerInfoAvatarListLoadingStripNode()
self.loadingStripNode.imageNode.image = self.activeStripImage
self.highlightContainerNode = ASDisplayNode()
self.highlightContainerNode.addSubnode(self.leftHighlightNode)
self.highlightContainerNode.addSubnode(self.rightHighlightNode)
@ -895,6 +989,8 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
self.itemNodes[self.items[i].id] = itemNode
self.contentNode.addSubnode(itemNode)
}
itemNode.isCentral = i == self.currentIndex
let indexOffset = CGFloat(i - self.currentIndex)
let itemFrame = CGRect(origin: CGPoint(x: indexOffset * size.width + self.transitionFraction * size.width - size.width / 2.0, y: -size.height / 2.0), size: size)
@ -945,6 +1041,7 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
}
}
self.stripContainerNode.addSubnode(self.activeStripNode)
self.stripContainerNode.addSubnode(self.loadingStripNode)
}
if self.appliedStripNodeCurrentIndex != self.currentIndex || itemsAdded {
if !self.itemNodes.isEmpty {
@ -985,11 +1082,13 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
if self.currentIndex >= 0 && self.currentIndex < self.stripNodes.count {
var frame = self.stripNodes[self.currentIndex].frame
stripTransition.updateFrame(node: self.loadingStripNode, frame: frame)
if let playbackProgress = self.playbackProgress {
frame.size.width = max(frame.size.height, frame.size.width * playbackProgress)
frame.size.width = max(0.0, frame.size.width * playbackProgress)
}
stripTransition.updateFrameAdditive(node: self.activeStripNode, frame: frame)
self.activeStripNode.isHidden = self.stripNodes.count < 2
self.loadingStripNode.isHidden = !self.loading
}
if let item = self.items.first, let itemNode = self.itemNodes[item.id] {
@ -2722,7 +2821,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
avatarScale = 1.0 * (1.0 - titleCollapseFraction) + avatarMinScale * titleCollapseFraction
avatarOffset = apparentTitleLockOffset + 0.0 * (1.0 - titleCollapseFraction) + 10.0 * titleCollapseFraction
}
if self.isAvatarExpanded {
self.avatarListNode.listContainerNode.isHidden = false
if !transitionSourceAvatarFrame.width.isZero {
@ -2732,9 +2831,9 @@ final class PeerInfoHeaderNode: ASDisplayNode {
transition.updateCornerRadius(node: self.avatarListNode.listContainerNode, cornerRadius: 0.0)
transition.updateCornerRadius(node: self.avatarListNode.listContainerNode.controlsClippingNode, cornerRadius: 0.0)
}
} else if self.avatarListNode.listContainerNode.cornerRadius != 50.0 {
transition.updateCornerRadius(node: self.avatarListNode.listContainerNode.controlsClippingNode, cornerRadius: 50.0)
transition.updateCornerRadius(node: self.avatarListNode.listContainerNode, cornerRadius: 50.0, completion: { [weak self] _ in
} else if self.avatarListNode.listContainerNode.cornerRadius != avatarSize / 2.0 {
transition.updateCornerRadius(node: self.avatarListNode.listContainerNode.controlsClippingNode, cornerRadius: avatarSize / 2.0)
transition.updateCornerRadius(node: self.avatarListNode.listContainerNode, cornerRadius: avatarSize / 2.0, completion: { [weak self] _ in
guard let strongSelf = self else {
return
}

View File

@ -633,7 +633,13 @@ private func settingsItems(data: PeerInfoScreenData?, context: AccountContext, p
items[section] = []
}
items[.edit]!.append(PeerInfoScreenActionItem(id: 0, text: presentationData.strings.Settings_SetProfilePhotoOrVideo, icon: UIImage(bundleImageName: "Settings/SetAvatar"), action: {
let setPhotoTitle: String
if let peer = data.peer, !peer.profileImageRepresentations.isEmpty {
setPhotoTitle = presentationData.strings.Settings_SetNewProfilePhotoOrVideo
} else {
setPhotoTitle = presentationData.strings.Settings_SetProfilePhotoOrVideo
}
items[.edit]!.append(PeerInfoScreenActionItem(id: 0, text: setPhotoTitle, icon: UIImage(bundleImageName: "Settings/SetAvatar"), action: {
interaction.openSettings(.avatar)
}))
if let peer = data.peer, peer.addressName == nil {
@ -641,10 +647,6 @@ private func settingsItems(data: PeerInfoScreenData?, context: AccountContext, p
interaction.openSettings(.username)
}))
}
// items[.edit]!.append(PeerInfoScreenActionItem(id: 2, text: presentationData.strings.Settings_EditAccount, icon: UIImage(bundleImageName: "Settings/EditAccount"), action: {
// interaction.openSettings(.edit)
// }))
if let settings = data.globalSettings {
if settings.suggestPhoneNumberConfirmation, let peer = data.peer as? TelegramUser {