diff --git a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift index 74b24054eb..576abff2ec 100644 --- a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift +++ b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift @@ -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 } diff --git a/submodules/GalleryUI/Sources/ChatVideoGalleryItemScrubberView.swift b/submodules/GalleryUI/Sources/ChatVideoGalleryItemScrubberView.swift index 57ff37afdb..c2c25e09e7 100644 --- a/submodules/GalleryUI/Sources/ChatVideoGalleryItemScrubberView.swift +++ b/submodules/GalleryUI/Sources/ChatVideoGalleryItemScrubberView.swift @@ -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?) { let mappedStatus: Signal? if let status = status { diff --git a/submodules/GalleryUI/Sources/GalleryControllerNode.swift b/submodules/GalleryUI/Sources/GalleryControllerNode.swift index 580fecfb06..5d0e45f4df 100644 --- a/submodules/GalleryUI/Sources/GalleryControllerNode.swift +++ b/submodules/GalleryUI/Sources/GalleryControllerNode.swift @@ -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) + } } } diff --git a/submodules/GalleryUI/Sources/GalleryFooterContentNode.swift b/submodules/GalleryUI/Sources/GalleryFooterContentNode.swift index 52e03d82ca..0c85e4e792 100644 --- a/submodules/GalleryUI/Sources/GalleryFooterContentNode.swift +++ b/submodules/GalleryUI/Sources/GalleryFooterContentNode.swift @@ -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 } diff --git a/submodules/GalleryUI/Sources/GalleryFooterNode.swift b/submodules/GalleryUI/Sources/GalleryFooterNode.swift index 027ea8a682..b407ee98fd 100644 --- a/submodules/GalleryUI/Sources/GalleryFooterNode.swift +++ b/submodules/GalleryUI/Sources/GalleryFooterNode.swift @@ -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) diff --git a/submodules/GalleryUI/Sources/GalleryItemNode.swift b/submodules/GalleryUI/Sources/GalleryItemNode.swift index cf2acfdc51..800c2fa36c 100644 --- a/submodules/GalleryUI/Sources/GalleryItemNode.swift +++ b/submodules/GalleryUI/Sources/GalleryItemNode.swift @@ -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 = { } diff --git a/submodules/GalleryUI/Sources/GalleryPagerNode.swift b/submodules/GalleryUI/Sources/GalleryPagerNode.swift index c87abff163..b51506c3e2 100644 --- a/submodules/GalleryUI/Sources/GalleryPagerNode.swift +++ b/submodules/GalleryUI/Sources/GalleryPagerNode.swift @@ -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 diff --git a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift index 819e30f4ed..c91fabfb92 100644 --- a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift @@ -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) } } } diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoAvatarCropView.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoAvatarCropView.h index fd0ee10637..2510d6f309 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoAvatarCropView.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoAvatarCropView.h @@ -1,6 +1,7 @@ #import @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; diff --git a/submodules/LegacyComponents/Sources/GPUImageToneCurveFilter.h b/submodules/LegacyComponents/Sources/GPUImageToneCurveFilter.h index ff4ae92e7e..25fdb55e8c 100755 --- a/submodules/LegacyComponents/Sources/GPUImageToneCurveFilter.h +++ b/submodules/LegacyComponents/Sources/GPUImageToneCurveFilter.h @@ -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; diff --git a/submodules/LegacyComponents/Sources/GPUImageToneCurveFilter.m b/submodules/LegacyComponents/Sources/GPUImageToneCurveFilter.m index 711fef560a..9a0c901c35 100644 --- a/submodules/LegacyComponents/Sources/GPUImageToneCurveFilter.m +++ b/submodules/LegacyComponents/Sources/GPUImageToneCurveFilter.m @@ -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)context photoEditor:(PGPhotoEditor *)photoEditor previewView:(TGPhotoEditorPreviewView *)previewView; diff --git a/submodules/LegacyComponents/Sources/TGPhotoAvatarPreviewController.m b/submodules/LegacyComponents/Sources/TGPhotoAvatarPreviewController.m index 83f5edd1b5..76dbafa2f5 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoAvatarPreviewController.m +++ b/submodules/LegacyComponents/Sources/TGPhotoAvatarPreviewController.m @@ -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]; diff --git a/submodules/LegacyComponents/Sources/TGPhotoEditorController.m b/submodules/LegacyComponents/Sources/TGPhotoEditorController.m index e4b65d1d0c..3c2cb8164e 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoEditorController.m +++ b/submodules/LegacyComponents/Sources/TGPhotoEditorController.m @@ -29,6 +29,7 @@ #import "TGPhotoToolbarView.h" #import "TGPhotoEditorPreviewView.h" +#import "TGPhotoEntitiesContainerView.h" #import @@ -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) { diff --git a/submodules/LegacyComponents/Sources/TGPhotoPaintController.h b/submodules/LegacyComponents/Sources/TGPhotoPaintController.h index 1065af1daf..7cc96e30a0 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoPaintController.h +++ b/submodules/LegacyComponents/Sources/TGPhotoPaintController.h @@ -11,7 +11,7 @@ @property (nonatomic, strong) id stickersContext; -- (instancetype)initWithContext:(id)context photoEditor:(PGPhotoEditor *)photoEditor previewView:(TGPhotoEditorPreviewView *)previewView; +- (instancetype)initWithContext:(id)context photoEditor:(PGPhotoEditor *)photoEditor previewView:(TGPhotoEditorPreviewView *)previewView entitiesView:(TGPhotoEntitiesContainerView *)entitiesView; - (TGPaintingData *)paintingData; diff --git a/submodules/LegacyComponents/Sources/TGPhotoPaintController.m b/submodules/LegacyComponents/Sources/TGPhotoPaintController.m index fbfe87db23..1d50a2a8cd 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoPaintController.m +++ b/submodules/LegacyComponents/Sources/TGPhotoPaintController.m @@ -139,7 +139,7 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; @implementation TGPhotoPaintController -- (instancetype)initWithContext:(id)context photoEditor:(PGPhotoEditor *)photoEditor previewView:(TGPhotoEditorPreviewView *)previewView +- (instancetype)initWithContext:(id)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; diff --git a/submodules/LegacyComponents/Sources/TGPhotoTextEntityView.m b/submodules/LegacyComponents/Sources/TGPhotoTextEntityView.m index c65161751b..9460d11871 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoTextEntityView.m +++ b/submodules/LegacyComponents/Sources/TGPhotoTextEntityView.m @@ -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]; diff --git a/submodules/LegacyComponents/Sources/YUGPUImageHighPassSkinSmoothingFilter.m b/submodules/LegacyComponents/Sources/YUGPUImageHighPassSkinSmoothingFilter.m index 5ec3b56de5..683c2c9ade 100644 --- a/submodules/LegacyComponents/Sources/YUGPUImageHighPassSkinSmoothingFilter.m +++ b/submodules/LegacyComponents/Sources/YUGPUImageHighPassSkinSmoothingFilter.m @@ -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 = @[ diff --git a/submodules/MediaPlayer/Sources/MediaPlayerScrubbingNode.swift b/submodules/MediaPlayer/Sources/MediaPlayerScrubbingNode.swift index 3769f545e4..d398f5ef36 100644 --- a/submodules/MediaPlayer/Sources/MediaPlayerScrubbingNode.swift +++ b/submodules/MediaPlayer/Sources/MediaPlayerScrubbingNode.swift @@ -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) diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift index 96626880ae..0a33ca38a6 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift @@ -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() 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 } diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index 1f930e4d78..6b2a138d98 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -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 {