mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Video avatar fixes
This commit is contained in:
parent
65c6e51e2c
commit
63a32bcbd8
@ -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
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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 = { }
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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(^{
|
||||
|
@ -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];
|
||||
|
@ -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];
|
||||
|
@ -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;
|
||||
|
@ -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];
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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];
|
||||
|
||||
if (_entitiesContainerView == nil) {
|
||||
_entitiesContainerView = [[TGPhotoEntitiesContainerView alloc] init];
|
||||
_entitiesContainerView.clipsToBounds = true;
|
||||
_entitiesContainerView.stickersContext = _stickersContext;
|
||||
}
|
||||
_entitiesContainerView.entitySelected = ^(TGPhotoPaintEntityView *sender)
|
||||
{
|
||||
__strong TGPhotoPaintController *strongSelf = weakSelf;
|
||||
|
@ -666,7 +666,9 @@ 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));
|
||||
|
@ -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)
|
||||
|
@ -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] {
|
||||
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user