mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
PiP improvements
This commit is contained in:
parent
682ae8a967
commit
3f9322d105
@ -29,6 +29,7 @@ public protocol MediaManager: class {
|
||||
func filteredPlaylistState(accountId: AccountRecordId, playlistId: SharedMediaPlaylistId, itemId: SharedMediaPlaylistItemId, type: MediaManagerPlayerType) -> Signal<SharedMediaPlayerItemPlaybackState?, NoError>
|
||||
|
||||
func setOverlayVideoNode(_ node: OverlayMediaItemNode?)
|
||||
func hasOverlayVideoNode(_ node: OverlayMediaItemNode) -> Bool
|
||||
|
||||
func audioRecorder(beginWithTone: Bool, applicationBindings: TelegramApplicationBindings, beganWithTone: @escaping (Bool) -> Void) -> Signal<ManagedAudioRecorder?, NoError>
|
||||
}
|
||||
|
@ -21,6 +21,9 @@ open class OverlayMediaItemNode: ASDisplayNode {
|
||||
|
||||
open var unminimize: (() -> Void)?
|
||||
|
||||
public var manualExpandEmbed: (() -> Void)?
|
||||
public var customUnembedWhenPortrait: ((OverlayMediaItemNode) -> Bool)?
|
||||
|
||||
open var group: OverlayMediaItemNodeGroup? {
|
||||
return nil
|
||||
}
|
||||
|
@ -228,6 +228,7 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
|
||||
let velocity = recognizer.velocity(in: self.view).x
|
||||
|
||||
if velocity > 1000 || navigationTransitionCoordinator.progress > 0.2 {
|
||||
self.state.top?.value.viewWillLeaveNavigation()
|
||||
navigationTransitionCoordinator.animateCompletion(velocity, completion: { [weak self] in
|
||||
guard let strongSelf = self, let _ = strongSelf.state.layout, let _ = strongSelf.state.transition, let top = strongSelf.state.top else {
|
||||
return
|
||||
@ -399,6 +400,7 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
|
||||
})
|
||||
}
|
||||
|
||||
fromValue.value.viewWillLeaveNavigation()
|
||||
fromValue.value.viewWillDisappear(true)
|
||||
toValue.value.viewWillAppear(true)
|
||||
toValue.value.setIgnoreAppearanceMethodInvocations(true)
|
||||
@ -464,6 +466,7 @@ final class NavigationContainer: ASDisplayNode, UIGestureRecognizerDelegate {
|
||||
})
|
||||
} else {
|
||||
if let fromValue = fromValue {
|
||||
fromValue.value.viewWillLeaveNavigation()
|
||||
fromValue.value.viewWillDisappear(false)
|
||||
fromValue.value.setIgnoreAppearanceMethodInvocations(true)
|
||||
fromValue.value.displayNode.removeFromSupernode()
|
||||
|
@ -117,6 +117,7 @@ open class NavigationBar: ASDisplayNode {
|
||||
|
||||
public var userInfo: Any?
|
||||
public var makeCustomTransitionNode: ((NavigationBar, Bool) -> CustomNavigationTransitionNode?)?
|
||||
public var allowsCustomTransition: (() -> Bool)?
|
||||
|
||||
private var collapsed: Bool {
|
||||
get {
|
||||
|
@ -561,6 +561,9 @@ public enum TabBarItemContextActionType {
|
||||
super.viewDidDisappear(animated)
|
||||
}
|
||||
|
||||
open func viewWillLeaveNavigation() {
|
||||
}
|
||||
|
||||
open override func viewDidAppear(_ animated: Bool) {
|
||||
self.activeInputView = nil
|
||||
|
||||
|
@ -290,6 +290,8 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
|
||||
var playbackCompleted: (() -> Void)?
|
||||
|
||||
private var customUnembedWhenPortrait: ((OverlayMediaItemNode) -> Bool)?
|
||||
|
||||
init(context: AccountContext, presentationData: PresentationData, performAction: @escaping (GalleryControllerInteractionTapAction) -> Void, openActionOptions: @escaping (GalleryControllerInteractionTapAction) -> Void, present: @escaping (ViewController, Any?) -> Void) {
|
||||
self.context = context
|
||||
self.presentationData = presentationData
|
||||
@ -423,6 +425,10 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
}
|
||||
|
||||
override func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
if let _ = self.customUnembedWhenPortrait, layout.size.width < layout.size.height {
|
||||
self.expandIntoCustomPiP()
|
||||
}
|
||||
|
||||
super.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition)
|
||||
|
||||
var dismiss = false
|
||||
@ -888,6 +894,11 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
}
|
||||
|
||||
if let node = node.0 as? OverlayMediaItemNode {
|
||||
self.customUnembedWhenPortrait = node.customUnembedWhenPortrait
|
||||
node.customUnembedWhenPortrait = nil
|
||||
}
|
||||
|
||||
if let node = node.0 as? OverlayMediaItemNode, self.context.sharedContext.mediaManager.hasOverlayVideoNode(node) {
|
||||
var transformedFrame = node.view.convert(node.view.bounds, to: videoNode.view)
|
||||
let transformedSuperFrame = node.view.convert(node.view.bounds, to: videoNode.view.superview)
|
||||
|
||||
@ -960,10 +971,12 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
surfaceCopyView.layer.animate(from: NSValue(caTransform3D: CATransform3DIdentity), to: NSValue(caTransform3D: CATransform3DMakeScale(scale.width, scale.height, 1.0)), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25, removeOnCompletion: false)
|
||||
}
|
||||
|
||||
videoNode.allowsGroupOpacity = true
|
||||
videoNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1, completion: { [weak videoNode] _ in
|
||||
videoNode?.allowsGroupOpacity = false
|
||||
})
|
||||
if surfaceCopyView.superview != nil {
|
||||
videoNode.allowsGroupOpacity = true
|
||||
videoNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1, completion: { [weak videoNode] _ in
|
||||
videoNode?.allowsGroupOpacity = false
|
||||
})
|
||||
}
|
||||
videoNode.layer.animatePosition(from: CGPoint(x: transformedSuperFrame.midX, y: transformedSuperFrame.midY), to: videoNode.layer.position, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
|
||||
transformedFrame.origin = CGPoint()
|
||||
@ -1272,9 +1285,9 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
}
|
||||
}
|
||||
|
||||
@objc func pictureInPictureButtonPressed() {
|
||||
if let item = self.item, let videoNode = self.videoNode {
|
||||
|
||||
private func expandIntoCustomPiP() {
|
||||
if let item = self.item, let videoNode = self.videoNode, let customUnembedWhenPortrait = customUnembedWhenPortrait {
|
||||
self.customUnembedWhenPortrait = nil
|
||||
videoNode.setContinuePlayingWithoutSoundOnLostAudioSession(false)
|
||||
|
||||
let context = self.context
|
||||
@ -1306,9 +1319,77 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
if let overlayNode = overlayNode, let overlaySupernode = overlayNode.supernode {
|
||||
return GalleryTransitionArguments(transitionNode: (overlayNode, overlayNode.bounds, { [weak overlayNode] in
|
||||
return (overlayNode?.view.snapshotContentTree(), nil)
|
||||
}), addToTransitionSurface: { [weak overlaySupernode, weak overlayNode] view in
|
||||
overlaySupernode?.view.addSubview(view)
|
||||
overlayNode?.canAttachContent = false
|
||||
}), addToTransitionSurface: { [weak context, weak overlaySupernode, weak overlayNode] view in
|
||||
guard let context = context, let overlayNode = overlayNode else {
|
||||
return
|
||||
}
|
||||
if context.sharedContext.mediaManager.hasOverlayVideoNode(overlayNode) {
|
||||
overlaySupernode?.view.addSubview(view)
|
||||
}
|
||||
overlayNode.canAttachContent = false
|
||||
})
|
||||
} else if let info = context.sharedContext.mediaManager.galleryHiddenMediaManager.findTarget(messageId: id, media: media) {
|
||||
return GalleryTransitionArguments(transitionNode: (info.1, info.1.bounds, {
|
||||
return info.2()
|
||||
}), addToTransitionSurface: info.0)
|
||||
}
|
||||
return nil
|
||||
}))
|
||||
case .webPage:
|
||||
break
|
||||
}
|
||||
}
|
||||
if customUnembedWhenPortrait(overlayNode) {
|
||||
self.beginCustomDismiss()
|
||||
self.statusNode.isHidden = true
|
||||
self.animateOut(toOverlay: overlayNode, completion: { [weak self] in
|
||||
self?.completeCustomDismiss()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc func pictureInPictureButtonPressed() {
|
||||
if let item = self.item, let videoNode = self.videoNode {
|
||||
videoNode.setContinuePlayingWithoutSoundOnLostAudioSession(false)
|
||||
|
||||
let context = self.context
|
||||
let baseNavigationController = self.baseNavigationController()
|
||||
let mediaManager = self.context.sharedContext.mediaManager
|
||||
var expandImpl: (() -> Void)?
|
||||
let overlayNode = OverlayUniversalVideoNode(postbox: self.context.account.postbox, audioSession: context.sharedContext.mediaManager.audioSession, manager: context.sharedContext.mediaManager.universalVideoManager, content: item.content, expand: {
|
||||
expandImpl?()
|
||||
}, close: { [weak mediaManager] in
|
||||
mediaManager?.setOverlayVideoNode(nil)
|
||||
})
|
||||
expandImpl = { [weak overlayNode] in
|
||||
guard let contentInfo = item.contentInfo, let overlayNode = overlayNode else {
|
||||
return
|
||||
}
|
||||
|
||||
switch contentInfo {
|
||||
case let .message(message):
|
||||
let gallery = GalleryController(context: context, source: .peerMessagesAtId(message.id), replaceRootController: { controller, ready in
|
||||
if let baseNavigationController = baseNavigationController {
|
||||
baseNavigationController.replaceTopController(controller, animated: false, ready: ready)
|
||||
}
|
||||
}, baseNavigationController: baseNavigationController)
|
||||
gallery.temporaryDoNotWaitForReady = true
|
||||
|
||||
baseNavigationController?.view.endEditing(true)
|
||||
|
||||
(baseNavigationController?.topViewController as? ViewController)?.present(gallery, in: .window(.root), with: GalleryControllerPresentationArguments(transitionArguments: { [weak overlayNode] id, media in
|
||||
if let overlayNode = overlayNode, let overlaySupernode = overlayNode.supernode {
|
||||
return GalleryTransitionArguments(transitionNode: (overlayNode, overlayNode.bounds, { [weak overlayNode] in
|
||||
return (overlayNode?.view.snapshotContentTree(), nil)
|
||||
}), addToTransitionSurface: { [weak context, weak overlaySupernode, weak overlayNode] view in
|
||||
guard let context = context, let overlayNode = overlayNode else {
|
||||
return
|
||||
}
|
||||
if context.sharedContext.mediaManager.hasOverlayVideoNode(overlayNode) {
|
||||
overlaySupernode?.view.addSubview(view)
|
||||
}
|
||||
overlayNode.canAttachContent = false
|
||||
})
|
||||
} else if let info = context.sharedContext.mediaManager.galleryHiddenMediaManager.findTarget(messageId: id, media: media) {
|
||||
return GalleryTransitionArguments(transitionNode: (info.1, info.1.bounds, {
|
||||
|
@ -45,9 +45,9 @@ public extension TabBarControllerTheme {
|
||||
}
|
||||
|
||||
public extension NavigationBarTheme {
|
||||
convenience init(rootControllerTheme: PresentationTheme, hideBackground: Bool = false) {
|
||||
convenience init(rootControllerTheme: PresentationTheme, hideBackground: Bool = false, hideBadge: Bool = false) {
|
||||
let theme = rootControllerTheme.rootController.navigationBar
|
||||
self.init(buttonColor: theme.buttonColor, disabledButtonColor: theme.disabledButtonColor, primaryTextColor: theme.primaryTextColor, backgroundColor: hideBackground ? .clear : theme.backgroundColor, separatorColor: hideBackground ? .clear : theme.separatorColor, badgeBackgroundColor: theme.badgeBackgroundColor, badgeStrokeColor: hideBackground ? .clear : theme.badgeStrokeColor, badgeTextColor: theme.badgeTextColor)
|
||||
self.init(buttonColor: theme.buttonColor, disabledButtonColor: theme.disabledButtonColor, primaryTextColor: theme.primaryTextColor, backgroundColor: hideBackground ? .clear : theme.backgroundColor, separatorColor: hideBackground ? .clear : theme.separatorColor, badgeBackgroundColor: hideBadge ? .clear : theme.badgeBackgroundColor, badgeStrokeColor: hideBadge ? .clear : theme.badgeStrokeColor, badgeTextColor: hideBadge ? .clear : theme.badgeTextColor)
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,8 +62,8 @@ public extension NavigationBarPresentationData {
|
||||
self.init(theme: NavigationBarTheme(rootControllerTheme: presentationData.theme), strings: NavigationBarStrings(presentationStrings: presentationData.strings))
|
||||
}
|
||||
|
||||
convenience init(presentationData: PresentationData, hideBackground: Bool) {
|
||||
self.init(theme: NavigationBarTheme(rootControllerTheme: presentationData.theme, hideBackground: hideBackground), strings: NavigationBarStrings(presentationStrings: presentationData.strings))
|
||||
convenience init(presentationData: PresentationData, hideBackground: Bool, hideBadge: Bool) {
|
||||
self.init(theme: NavigationBarTheme(rootControllerTheme: presentationData.theme, hideBackground: hideBackground, hideBadge: hideBadge), strings: NavigationBarStrings(presentationStrings: presentationData.strings))
|
||||
}
|
||||
|
||||
convenience init(presentationTheme: PresentationTheme, presentationStrings: PresentationStrings) {
|
||||
|
@ -322,6 +322,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
private let peekTimerDisposable = MetaDisposable()
|
||||
|
||||
private var hasEmbeddedTitleContent = false
|
||||
private var isEmbeddedTitleContentHidden = false
|
||||
|
||||
public override var customData: Any? {
|
||||
return self.chatLocation
|
||||
@ -375,7 +376,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
case .inline:
|
||||
navigationBarPresentationData = nil
|
||||
default:
|
||||
navigationBarPresentationData = NavigationBarPresentationData(presentationData: self.presentationData, hideBackground: true)
|
||||
navigationBarPresentationData = NavigationBarPresentationData(presentationData: self.presentationData, hideBackground: true, hideBadge: false)
|
||||
}
|
||||
super.init(context: context, navigationBarPresentationData: navigationBarPresentationData, mediaAccessoryPanelVisibility: mediaAccessoryPanelVisibility, locationBroadcastPanelSource: locationBroadcastPanelSource)
|
||||
|
||||
@ -2059,6 +2060,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
if case let .peer(peerId) = chatLocation, peerId != context.account.peerId, subject != .scheduledMessages {
|
||||
self.navigationBar?.userInfo = PeerInfoNavigationSourceTag(peerId: peerId)
|
||||
}
|
||||
self.navigationBar?.allowsCustomTransition = { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return false
|
||||
}
|
||||
return !strongSelf.chatDisplayNode.hasEmbeddedTitleContent
|
||||
}
|
||||
|
||||
self.chatTitleView = ChatTitleView(account: self.context.account, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder)
|
||||
self.navigationItem.titleView = self.chatTitleView
|
||||
@ -2799,9 +2806,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
let navigationBarTheme: NavigationBarTheme
|
||||
|
||||
if self.hasEmbeddedTitleContent {
|
||||
navigationBarTheme = NavigationBarTheme(rootControllerTheme: defaultDarkPresentationTheme, hideBackground: true)
|
||||
navigationBarTheme = NavigationBarTheme(rootControllerTheme: defaultDarkPresentationTheme, hideBackground: true, hideBadge: true)
|
||||
} else {
|
||||
navigationBarTheme = NavigationBarTheme(rootControllerTheme: self.presentationData.theme, hideBackground: true)
|
||||
navigationBarTheme = NavigationBarTheme(rootControllerTheme: self.presentationData.theme, hideBackground: true, hideBadge: false)
|
||||
}
|
||||
|
||||
self.navigationBar?.updatePresentationData(NavigationBarPresentationData(theme: navigationBarTheme, strings: NavigationBarStrings(presentationStrings: self.presentationData.strings)))
|
||||
@ -4750,10 +4757,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}))
|
||||
}
|
||||
|
||||
self.chatDisplayNode.updateHasEmbeddedTitleContent = { [weak self] hasEmbeddedTitleContent in
|
||||
self.chatDisplayNode.updateHasEmbeddedTitleContent = { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
let hasEmbeddedTitleContent = strongSelf.chatDisplayNode.hasEmbeddedTitleContent
|
||||
let isEmbeddedTitleContentHidden = strongSelf.chatDisplayNode.isEmbeddedTitleContentHidden
|
||||
|
||||
if strongSelf.hasEmbeddedTitleContent != hasEmbeddedTitleContent {
|
||||
strongSelf.hasEmbeddedTitleContent = hasEmbeddedTitleContent
|
||||
|
||||
@ -4774,6 +4785,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
strongSelf.updateNavigationBarPresentation()
|
||||
}
|
||||
|
||||
if strongSelf.isEmbeddedTitleContentHidden != isEmbeddedTitleContentHidden {
|
||||
strongSelf.isEmbeddedTitleContentHidden = isEmbeddedTitleContentHidden
|
||||
|
||||
if let navigationBar = strongSelf.navigationBar {
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut)
|
||||
transition.updateAlpha(node: navigationBar, alpha: isEmbeddedTitleContentHidden ? 0.0 : 1.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.interfaceInteraction = interfaceInteraction
|
||||
@ -5168,6 +5188,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
})
|
||||
}
|
||||
|
||||
override public func viewWillLeaveNavigation() {
|
||||
self.chatDisplayNode.willNavigateAway()
|
||||
}
|
||||
|
||||
override public func inFocusUpdated(isInFocus: Bool) {
|
||||
self.chatDisplayNode.inFocusUpdated(isInFocus: isInFocus)
|
||||
}
|
||||
|
@ -69,6 +69,9 @@ private final class ChatEmbeddedTitleContentNode: ASDisplayNode {
|
||||
private let context: AccountContext
|
||||
private let backgroundNode: ASDisplayNode
|
||||
private let videoNode: OverlayUniversalVideoNode
|
||||
private let disableInternalAnimationIn: Bool
|
||||
private let isUIHiddenUpdated: () -> Void
|
||||
private let unembedWhenPortrait: (OverlayMediaItemNode) -> Bool
|
||||
|
||||
private var validLayout: (CGSize, CGFloat, CGFloat)?
|
||||
|
||||
@ -78,9 +81,14 @@ private final class ChatEmbeddedTitleContentNode: ASDisplayNode {
|
||||
private(set) var interactiveExtension: CGFloat = 0.0
|
||||
private var freezeInteractiveExtension = false
|
||||
|
||||
init(context: AccountContext, videoNode: OverlayUniversalVideoNode, interactiveExtensionUpdated: @escaping (ContainedViewLayoutTransition) -> Void, dismissed: @escaping () -> Void) {
|
||||
private(set) var isUIHidden: Bool = false
|
||||
|
||||
init(context: AccountContext, videoNode: OverlayUniversalVideoNode, disableInternalAnimationIn: Bool, interactiveExtensionUpdated: @escaping (ContainedViewLayoutTransition) -> Void, dismissed: @escaping () -> Void, isUIHiddenUpdated: @escaping () -> Void, unembedWhenPortrait: @escaping (OverlayMediaItemNode) -> Bool) {
|
||||
self.dismissed = dismissed
|
||||
self.interactiveExtensionUpdated = interactiveExtensionUpdated
|
||||
self.isUIHiddenUpdated = isUIHiddenUpdated
|
||||
self.unembedWhenPortrait = unembedWhenPortrait
|
||||
self.disableInternalAnimationIn = disableInternalAnimationIn
|
||||
|
||||
self.context = context
|
||||
|
||||
@ -96,6 +104,14 @@ private final class ChatEmbeddedTitleContentNode: ASDisplayNode {
|
||||
self.addSubnode(self.backgroundNode)
|
||||
|
||||
self.view.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:))))
|
||||
|
||||
self.videoNode.controlsAreShowingUpdated = { [weak self] value in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.isUIHidden = !value
|
||||
strongSelf.isUIHiddenUpdated()
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func panGesture(_ recognizer: UIPanGestureRecognizer) {
|
||||
@ -116,7 +132,7 @@ private final class ChatEmbeddedTitleContentNode: ASDisplayNode {
|
||||
|
||||
if translation.y > 80.0 {
|
||||
self.freezeInteractiveExtension = true
|
||||
self.videoNode.customExpand?()
|
||||
self.expandIntoPiP()
|
||||
} else {
|
||||
self.interactiveExtension = max(0.0, offset)
|
||||
self.interactiveExtensionUpdated(.immediate)
|
||||
@ -146,72 +162,63 @@ private final class ChatEmbeddedTitleContentNode: ASDisplayNode {
|
||||
let sourceFrame = self.videoNode.view.convert(self.videoNode.bounds, to: transitionSurface.view)
|
||||
let targetFrame = self.view.convert(videoFrame, to: transitionSurface.view)
|
||||
|
||||
self.context.sharedContext.mediaManager.setOverlayVideoNode(nil)
|
||||
transitionSurface.addSubnode(self.videoNode)
|
||||
|
||||
let navigationBarCopy = navigationBar?.view.snapshotView(afterScreenUpdates: true)
|
||||
let navigationBarContainer = UIView()
|
||||
navigationBarContainer.frame = targetFrame
|
||||
navigationBarContainer.clipsToBounds = true
|
||||
transitionSurface.view.addSubview(navigationBarContainer)
|
||||
|
||||
navigationBarContainer.layer.animateFrame(from: sourceFrame, to: targetFrame, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
|
||||
if let navigationBar = navigationBar, let navigationBarCopy = navigationBarCopy {
|
||||
let navigationFrame = navigationBar.view.convert(navigationBar.bounds, to: transitionSurface.view)
|
||||
let navigationSourceFrame = navigationFrame.offsetBy(dx: -sourceFrame.minX, dy: -sourceFrame.minY)
|
||||
let navigationTargetFrame = navigationFrame.offsetBy(dx: -targetFrame.minX, dy: -targetFrame.minY)
|
||||
navigationBarCopy.frame = navigationTargetFrame
|
||||
navigationBarContainer.addSubview(navigationBarCopy)
|
||||
var navigationBarCopy: UIView?
|
||||
var navigationBarContainer: UIView?
|
||||
var nodeTransition = transition
|
||||
if self.disableInternalAnimationIn {
|
||||
nodeTransition = .immediate
|
||||
} else {
|
||||
self.context.sharedContext.mediaManager.setOverlayVideoNode(nil)
|
||||
transitionSurface.addSubnode(self.videoNode)
|
||||
|
||||
navigationBarCopy.layer.animateFrame(from: navigationSourceFrame, to: navigationTargetFrame, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
navigationBarCopy.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
navigationBarCopy = navigationBar?.view.snapshotView(afterScreenUpdates: true)
|
||||
let navigationBarContainerValue = UIView()
|
||||
navigationBarContainer = navigationBarContainerValue
|
||||
navigationBarContainerValue.frame = targetFrame
|
||||
navigationBarContainerValue.clipsToBounds = true
|
||||
transitionSurface.view.addSubview(navigationBarContainerValue)
|
||||
}
|
||||
|
||||
self.videoNode.updateRoundCorners(false, transition: .animated(duration: 0.25, curve: .spring))
|
||||
self.videoNode.showControls()
|
||||
if !self.disableInternalAnimationIn {
|
||||
navigationBarContainer?.layer.animateFrame(from: sourceFrame, to: targetFrame, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
}
|
||||
|
||||
self.videoNode.updateLayout(targetFrame.size, transition: .animated(duration: 0.25, curve: .spring))
|
||||
self.videoNode.frame = targetFrame
|
||||
self.videoNode.layer.animateFrame(from: sourceFrame, to: targetFrame, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, completion: { [weak self] _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
navigationBarContainer.removeFromSuperview()
|
||||
strongSelf.addSubnode(strongSelf.videoNode)
|
||||
if let (size, topInset, interactiveExtension) = strongSelf.validLayout {
|
||||
strongSelf.updateLayout(size: size, topInset: topInset, interactiveExtension: interactiveExtension, transition: .immediate, transitionSurface: nil, navigationBar: nil)
|
||||
}
|
||||
})
|
||||
self.backgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
|
||||
self.videoNode.customExpand = { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .spring)
|
||||
|
||||
strongSelf.videoNode.customExpand = nil
|
||||
strongSelf.videoNode.customClose = nil
|
||||
|
||||
let previousFrame = strongSelf.videoNode.frame
|
||||
strongSelf.context.sharedContext.mediaManager.setOverlayVideoNode(strongSelf.videoNode)
|
||||
strongSelf.videoNode.updateRoundCorners(true, transition: transition)
|
||||
|
||||
if let targetSuperview = strongSelf.videoNode.view.superview {
|
||||
let sourceFrame = strongSelf.view.convert(previousFrame, to: targetSuperview)
|
||||
let targetFrame = strongSelf.videoNode.frame
|
||||
strongSelf.videoNode.frame = sourceFrame
|
||||
strongSelf.videoNode.updateLayout(sourceFrame.size, transition: .immediate)
|
||||
if !self.disableInternalAnimationIn {
|
||||
if let navigationBar = navigationBar, let navigationBarCopy = navigationBarCopy {
|
||||
let navigationFrame = navigationBar.view.convert(navigationBar.bounds, to: transitionSurface.view)
|
||||
let navigationSourceFrame = navigationFrame.offsetBy(dx: -sourceFrame.minX, dy: -sourceFrame.minY)
|
||||
let navigationTargetFrame = navigationFrame.offsetBy(dx: -targetFrame.minX, dy: -targetFrame.minY)
|
||||
navigationBarCopy.frame = navigationTargetFrame
|
||||
navigationBarContainer?.addSubview(navigationBarCopy)
|
||||
|
||||
transition.updateFrame(node: strongSelf.videoNode, frame: targetFrame)
|
||||
strongSelf.videoNode.updateLayout(targetFrame.size, transition: transition)
|
||||
navigationBarCopy.layer.animateFrame(from: navigationSourceFrame, to: navigationTargetFrame, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
navigationBarCopy.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
}
|
||||
|
||||
strongSelf.dismissed()
|
||||
}
|
||||
|
||||
self.videoNode.updateRoundCorners(false, transition: nodeTransition)
|
||||
if !self.disableInternalAnimationIn {
|
||||
self.videoNode.showControls()
|
||||
}
|
||||
|
||||
self.videoNode.updateLayout(targetFrame.size, transition: nodeTransition)
|
||||
self.videoNode.frame = targetFrame
|
||||
if self.disableInternalAnimationIn {
|
||||
self.addSubnode(self.videoNode)
|
||||
} else {
|
||||
self.videoNode.layer.animateFrame(from: sourceFrame, to: targetFrame, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, completion: { [weak self] _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
navigationBarContainer?.removeFromSuperview()
|
||||
strongSelf.addSubnode(strongSelf.videoNode)
|
||||
if let (size, topInset, interactiveExtension) = strongSelf.validLayout {
|
||||
strongSelf.updateLayout(size: size, topInset: topInset, interactiveExtension: interactiveExtension, transition: .immediate, transitionSurface: nil, navigationBar: nil)
|
||||
}
|
||||
})
|
||||
self.backgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
|
||||
self.videoNode.customClose = { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
@ -227,6 +234,39 @@ private final class ChatEmbeddedTitleContentNode: ASDisplayNode {
|
||||
transition.updateFrame(node: self.videoNode, frame: videoFrame)
|
||||
}
|
||||
}
|
||||
|
||||
func expand(intoLandscape: Bool) {
|
||||
if intoLandscape {
|
||||
let unembedWhenPortrait = self.unembedWhenPortrait
|
||||
self.videoNode.customUnembedWhenPortrait = { videoNode in
|
||||
unembedWhenPortrait(videoNode)
|
||||
}
|
||||
}
|
||||
self.videoNode.expand()
|
||||
}
|
||||
|
||||
func expandIntoPiP() {
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .spring)
|
||||
|
||||
self.videoNode.customExpand = nil
|
||||
self.videoNode.customClose = nil
|
||||
|
||||
let previousFrame = self.videoNode.frame
|
||||
self.context.sharedContext.mediaManager.setOverlayVideoNode(self.videoNode)
|
||||
self.videoNode.updateRoundCorners(true, transition: transition)
|
||||
|
||||
if let targetSuperview = self.videoNode.view.superview {
|
||||
let sourceFrame = self.view.convert(previousFrame, to: targetSuperview)
|
||||
let targetFrame = self.videoNode.frame
|
||||
self.videoNode.frame = sourceFrame
|
||||
self.videoNode.updateLayout(sourceFrame.size, transition: .immediate)
|
||||
|
||||
transition.updateFrame(node: self.videoNode, frame: targetFrame)
|
||||
self.videoNode.updateLayout(targetFrame.size, transition: transition)
|
||||
}
|
||||
|
||||
self.dismissed()
|
||||
}
|
||||
}
|
||||
|
||||
enum ChatEmbeddedTitlePeekContent: Equatable {
|
||||
@ -387,6 +427,9 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
private var embeddedTitlePeekContent: ChatEmbeddedTitlePeekContent = .none
|
||||
private var embeddedTitleContentNode: ChatEmbeddedTitleContentNode?
|
||||
private var dismissedEmbeddedTitleContentNode: ChatEmbeddedTitleContentNode?
|
||||
var hasEmbeddedTitleContent: Bool {
|
||||
return self.embeddedTitleContentNode != nil
|
||||
}
|
||||
|
||||
init(context: AccountContext, chatLocation: ChatLocation, subject: ChatControllerSubject?, controllerInteraction: ChatControllerInteraction, chatPresentationInterfaceState: ChatPresentationInterfaceState, automaticMediaDownloadSettings: MediaAutoDownloadSettings, navigationBar: NavigationBar?, controller: ChatControllerImpl?) {
|
||||
self.context = context
|
||||
@ -892,6 +935,15 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
|
||||
let statusBarHeight = layout.insets(options: [.statusBar]).top
|
||||
|
||||
if let embeddedTitleContentNode = self.embeddedTitleContentNode, embeddedTitleContentNode.supernode != nil {
|
||||
if layout.size.width > layout.size.height {
|
||||
self.embeddedTitleContentNode = nil
|
||||
self.dismissedEmbeddedTitleContentNode = embeddedTitleContentNode
|
||||
embeddedTitleContentNode.expand(intoLandscape: true)
|
||||
self.updateHasEmbeddedTitleContent?()
|
||||
}
|
||||
}
|
||||
|
||||
if let embeddedTitleContentNode = self.embeddedTitleContentNode {
|
||||
let embeddedSize = CGSize(width: layout.size.width, height: min(400.0, embeddedTitleContentNode.calculateHeight(width: layout.size.width)) + statusBarHeight + embeddedTitleContentNode.interactiveExtension)
|
||||
if embeddedTitleContentNode.supernode == nil {
|
||||
@ -2648,7 +2700,15 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
var updateHasEmbeddedTitleContent: ((Bool) -> Void)?
|
||||
var isEmbeddedTitleContentHidden: Bool {
|
||||
if let embeddedTitleContentNode = self.embeddedTitleContentNode {
|
||||
return embeddedTitleContentNode.isUIHidden
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
var updateHasEmbeddedTitleContent: (() -> Void)?
|
||||
|
||||
func acceptEmbeddedTitlePeekContent(content: NavigationControllerDropContent) -> Bool {
|
||||
guard let (_, navigationHeight) = self.validLayout else {
|
||||
@ -2658,7 +2718,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
return false
|
||||
}
|
||||
if let item = content.item as? VideoNavigationControllerDropContentItem, let itemNode = item.itemNode as? OverlayUniversalVideoNode {
|
||||
let embeddedTitleContentNode = ChatEmbeddedTitleContentNode(context: self.context, videoNode: itemNode, interactiveExtensionUpdated: { [weak self] transition in
|
||||
let embeddedTitleContentNode = ChatEmbeddedTitleContentNode(context: self.context, videoNode: itemNode, disableInternalAnimationIn: false, interactiveExtensionUpdated: { [weak self] transition in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
@ -2671,12 +2731,20 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
strongSelf.embeddedTitleContentNode = nil
|
||||
strongSelf.dismissedEmbeddedTitleContentNode = embeddedTitleContentNode
|
||||
strongSelf.requestLayout(.animated(duration: 0.25, curve: .spring))
|
||||
strongSelf.updateHasEmbeddedTitleContent?(false)
|
||||
strongSelf.updateHasEmbeddedTitleContent?()
|
||||
}
|
||||
}, isUIHiddenUpdated: { [weak self] in
|
||||
self?.updateHasEmbeddedTitleContent?()
|
||||
}, unembedWhenPortrait: { [weak self] itemNode in
|
||||
guard let strongSelf = self, let itemNode = itemNode as? OverlayUniversalVideoNode else {
|
||||
return false
|
||||
}
|
||||
strongSelf.unembedWhenPortrait(contentNode: itemNode)
|
||||
return true
|
||||
})
|
||||
self.embeddedTitleContentNode = embeddedTitleContentNode
|
||||
self.embeddedTitlePeekContent = .none
|
||||
self.updateHasEmbeddedTitleContent?(true)
|
||||
self.updateHasEmbeddedTitleContent?()
|
||||
DispatchQueue.main.async {
|
||||
self.requestLayout(.animated(duration: 0.25, curve: .spring))
|
||||
}
|
||||
@ -2685,4 +2753,46 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private func unembedWhenPortrait(contentNode: OverlayUniversalVideoNode) {
|
||||
let embeddedTitleContentNode = ChatEmbeddedTitleContentNode(context: self.context, videoNode: contentNode, disableInternalAnimationIn: true, interactiveExtensionUpdated: { [weak self] transition in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.requestLayout(transition)
|
||||
}, dismissed: { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if let embeddedTitleContentNode = strongSelf.embeddedTitleContentNode {
|
||||
strongSelf.embeddedTitleContentNode = nil
|
||||
strongSelf.dismissedEmbeddedTitleContentNode = embeddedTitleContentNode
|
||||
strongSelf.requestLayout(.animated(duration: 0.25, curve: .spring))
|
||||
strongSelf.updateHasEmbeddedTitleContent?()
|
||||
}
|
||||
}, isUIHiddenUpdated: { [weak self] in
|
||||
self?.updateHasEmbeddedTitleContent?()
|
||||
}, unembedWhenPortrait: { [weak self] itemNode in
|
||||
guard let strongSelf = self, let itemNode = itemNode as? OverlayUniversalVideoNode else {
|
||||
return false
|
||||
}
|
||||
strongSelf.unembedWhenPortrait(contentNode: itemNode)
|
||||
return true
|
||||
})
|
||||
|
||||
self.embeddedTitleContentNode = embeddedTitleContentNode
|
||||
self.embeddedTitlePeekContent = .none
|
||||
self.updateHasEmbeddedTitleContent?()
|
||||
self.requestLayout(.immediate)
|
||||
}
|
||||
|
||||
func willNavigateAway() {
|
||||
if let embeddedTitleContentNode = self.embeddedTitleContentNode {
|
||||
self.embeddedTitleContentNode = nil
|
||||
self.dismissedEmbeddedTitleContentNode = embeddedTitleContentNode
|
||||
embeddedTitleContentNode.expandIntoPiP()
|
||||
self.requestLayout(.animated(duration: 0.25, curve: .spring))
|
||||
self.updateHasEmbeddedTitleContent?()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -616,4 +616,8 @@ public final class MediaManagerImpl: NSObject, MediaManager {
|
||||
self.overlayMediaManager.controller?.addNode(node, customTransition: true)
|
||||
}
|
||||
}
|
||||
|
||||
public func hasOverlayVideoNode(_ node: OverlayMediaItemNode) -> Bool {
|
||||
return self.currentOverlayVideoNode === node
|
||||
}
|
||||
}
|
||||
|
@ -4345,6 +4345,9 @@ public final class PeerInfoScreen: ViewController {
|
||||
if other.contentNode != nil {
|
||||
return nil
|
||||
}
|
||||
if let allowsCustomTransition = other.allowsCustomTransition, !allowsCustomTransition() {
|
||||
return nil
|
||||
}
|
||||
if let tag = other.userInfo as? PeerInfoNavigationSourceTag, tag.peerId == peerId {
|
||||
return PeerInfoNavigationTransitionNode(screenNode: strongSelf.controllerNode, presentationData: strongSelf.presentationData, headerNode: strongSelf.controllerNode.headerNode)
|
||||
}
|
||||
|
@ -287,10 +287,20 @@ private final class NativeVideoContentNode: ASDisplayNode, UniversalVideoContent
|
||||
applyLayout()
|
||||
}
|
||||
|
||||
self.imageNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||
self.playerNode.frame = CGRect(origin: CGPoint(), size: size).insetBy(dx: -1.0, dy: -1.0)
|
||||
transition.updateFrame(node: self.imageNode, frame: CGRect(origin: CGPoint(), size: size))
|
||||
let fromFrame = self.playerNode.frame
|
||||
let toFrame = CGRect(origin: CGPoint(), size: size).insetBy(dx: -1.0, dy: -1.0)
|
||||
if case let .animated(duration, curve) = transition, fromFrame != toFrame, !fromFrame.width.isZero, !fromFrame.height.isZero, !toFrame.width.isZero, !toFrame.height.isZero {
|
||||
self.playerNode.frame = toFrame
|
||||
transition.animatePosition(node: self.playerNode, from: CGPoint(x: fromFrame.center.x - toFrame.center.x, y: fromFrame.center.y - toFrame.center.y))
|
||||
|
||||
let transform = CATransform3DScale(CATransform3DIdentity, fromFrame.width / toFrame.width, fromFrame.height / toFrame.height, 1.0)
|
||||
self.playerNode.layer.animate(from: NSValue(caTransform3D: transform), to: NSValue(caTransform3D: CATransform3DIdentity), keyPath: "transform", timingFunction: curve.timingFunction, duration: duration)
|
||||
} else {
|
||||
transition.updateFrame(node: self.playerNode, frame: toFrame)
|
||||
}
|
||||
if let thumbnailNode = self.thumbnailNode {
|
||||
thumbnailNode.frame = CGRect(origin: CGPoint(), size: size).insetBy(dx: -1.0, dy: -1.0)
|
||||
transition.updateFrame(node: thumbnailNode, frame: CGRect(origin: CGPoint(), size: size).insetBy(dx: -1.0, dy: -1.0))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -33,17 +33,19 @@ public final class OverlayUniversalVideoNode: OverlayMediaItemNode {
|
||||
private let defaultExpand: () -> Void
|
||||
public var customExpand: (() -> Void)?
|
||||
public var customClose: (() -> Void)?
|
||||
public var controlsAreShowingUpdated: ((Bool) -> Void)?
|
||||
|
||||
public init(postbox: Postbox, audioSession: ManagedAudioSession, manager: UniversalVideoManager, content: UniversalVideoContent, expand: @escaping () -> Void, close: @escaping () -> Void) {
|
||||
self.content = content
|
||||
self.defaultExpand = expand
|
||||
|
||||
var expandImpl: (() -> Void)?
|
||||
var controlsAreShowingUpdatedImpl: ((Bool) -> Void)?
|
||||
|
||||
var unminimizeImpl: (() -> Void)?
|
||||
var togglePlayPauseImpl: (() -> Void)?
|
||||
var closeImpl: (() -> Void)?
|
||||
let decoration = OverlayVideoDecoration(unminimize: {
|
||||
let decoration = OverlayVideoDecoration(contentDimensions: content.dimensions, unminimize: {
|
||||
unminimizeImpl?()
|
||||
}, togglePlayPause: {
|
||||
togglePlayPauseImpl?()
|
||||
@ -51,6 +53,8 @@ public final class OverlayUniversalVideoNode: OverlayMediaItemNode {
|
||||
expandImpl?()
|
||||
}, close: {
|
||||
closeImpl?()
|
||||
}, controlsAreShowingUpdated: { value in
|
||||
controlsAreShowingUpdatedImpl?(value)
|
||||
})
|
||||
self.videoNode = UniversalVideoNode(postbox: postbox, audioSession: audioSession, manager: manager, decoration: decoration, content: content, priority: .overlay)
|
||||
self.decoration = decoration
|
||||
@ -58,14 +62,7 @@ public final class OverlayUniversalVideoNode: OverlayMediaItemNode {
|
||||
super.init()
|
||||
|
||||
expandImpl = { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if let customExpand = strongSelf.customExpand {
|
||||
customExpand()
|
||||
} else {
|
||||
strongSelf.defaultExpand()
|
||||
}
|
||||
self?.expand()
|
||||
}
|
||||
|
||||
unminimizeImpl = { [weak self] in
|
||||
@ -90,6 +87,10 @@ public final class OverlayUniversalVideoNode: OverlayMediaItemNode {
|
||||
strongSelf.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false)
|
||||
}
|
||||
}
|
||||
|
||||
controlsAreShowingUpdatedImpl = { [weak self] value in
|
||||
self?.controlsAreShowingUpdated?(value)
|
||||
}
|
||||
|
||||
self.clipsToBounds = true
|
||||
self.cornerRadius = 4.0
|
||||
@ -104,7 +105,7 @@ public final class OverlayUniversalVideoNode: OverlayMediaItemNode {
|
||||
if previous != value {
|
||||
if !value {
|
||||
strongSelf.dismiss()
|
||||
close()
|
||||
closeImpl?()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -155,4 +156,12 @@ public final class OverlayUniversalVideoNode: OverlayMediaItemNode {
|
||||
public func showControls() {
|
||||
self.decoration.showControls()
|
||||
}
|
||||
|
||||
public func expand() {
|
||||
if let customExpand = self.customExpand {
|
||||
customExpand()
|
||||
} else {
|
||||
self.defaultExpand()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,11 +26,14 @@ private func setupArrowFrame(size: CGSize, edge: OverlayMediaItemMinimizationEdg
|
||||
private let backgroundImage = UIImage(bundleImageName: "Chat/Message/OverlayPlainVideoShadow")?.precomposed().resizableImage(withCapInsets: UIEdgeInsets(top: 22.0, left: 25.0, bottom: 26.0, right: 25.0), resizingMode: .stretch)
|
||||
|
||||
final class OverlayVideoDecoration: UniversalVideoDecoration {
|
||||
private let contentDimensions: CGSize
|
||||
|
||||
let backgroundNode: ASDisplayNode?
|
||||
let contentContainerNode: ASDisplayNode
|
||||
let foregroundNode: ASDisplayNode?
|
||||
|
||||
private let unminimize: () -> Void
|
||||
private let controlsAreShowingUpdated: (Bool) -> Void
|
||||
|
||||
private let shadowNode: ASImageNode
|
||||
private let foregroundContainerNode: ASDisplayNode
|
||||
@ -46,8 +49,11 @@ final class OverlayVideoDecoration: UniversalVideoDecoration {
|
||||
|
||||
private var validLayoutSize: CGSize?
|
||||
|
||||
init(unminimize: @escaping () -> Void, togglePlayPause: @escaping () -> Void, expand: @escaping () -> Void, close: @escaping () -> Void) {
|
||||
init(contentDimensions: CGSize, unminimize: @escaping () -> Void, togglePlayPause: @escaping () -> Void, expand: @escaping () -> Void, close: @escaping () -> Void, controlsAreShowingUpdated: @escaping (Bool) -> Void) {
|
||||
self.contentDimensions = contentDimensions
|
||||
|
||||
self.unminimize = unminimize
|
||||
self.controlsAreShowingUpdated = controlsAreShowingUpdated
|
||||
|
||||
self.shadowNode = ASImageNode()
|
||||
self.shadowNode.image = backgroundImage
|
||||
@ -78,6 +84,14 @@ final class OverlayVideoDecoration: UniversalVideoDecoration {
|
||||
self.statusDisposable.dispose()
|
||||
}
|
||||
|
||||
private func frameForContent(size: CGSize) -> CGRect {
|
||||
if !self.contentDimensions.width.isZero && !self.contentDimensions.height.isZero {
|
||||
let fittedSize = self.contentDimensions.aspectFittedWithOverflow(size, leeway: 10.0)
|
||||
return CGRect(origin: CGPoint(x: floor(size.width - fittedSize.width) / 2.0, y: floor(size.height - fittedSize.height) / 2.0), size: fittedSize)
|
||||
}
|
||||
return CGRect(origin: CGPoint(), size: size)
|
||||
}
|
||||
|
||||
func updateContentNode(_ contentNode: (UniversalVideoContentNode & ASDisplayNode)?) {
|
||||
if self.contentNode !== contentNode {
|
||||
let previous = self.contentNode
|
||||
@ -93,7 +107,7 @@ final class OverlayVideoDecoration: UniversalVideoDecoration {
|
||||
if contentNode.supernode !== self.contentContainerNode {
|
||||
self.contentContainerNode.addSubnode(contentNode)
|
||||
if let validLayoutSize = self.validLayoutSize {
|
||||
contentNode.frame = CGRect(origin: CGPoint(), size: validLayoutSize)
|
||||
contentNode.frame = self.frameForContent(size: validLayoutSize)
|
||||
contentNode.updateLayout(size: validLayoutSize, transition: .immediate)
|
||||
}
|
||||
}
|
||||
@ -107,6 +121,8 @@ final class OverlayVideoDecoration: UniversalVideoDecoration {
|
||||
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
|
||||
self.validLayoutSize = size
|
||||
|
||||
let contentFrame = self.frameForContent(size: size)
|
||||
|
||||
let shadowInsets = UIEdgeInsets(top: 2.0, left: 3.0, bottom: 4.0, right: 3.0)
|
||||
transition.updateFrame(node: self.shadowNode, frame: CGRect(origin: CGPoint(x: -shadowInsets.left, y: -shadowInsets.top), size: CGSize(width: size.width + shadowInsets.left + shadowInsets.right, height: size.height + shadowInsets.top + shadowInsets.bottom)))
|
||||
|
||||
@ -129,8 +145,8 @@ final class OverlayVideoDecoration: UniversalVideoDecoration {
|
||||
transition.updateFrame(node: self.statusNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - progressSize.width) / 2.0), y: floorToScreenPixels((size.height - progressSize.height) / 2.0)), size: progressSize))
|
||||
|
||||
if let contentNode = self.contentNode {
|
||||
transition.updateFrame(node: contentNode, frame: CGRect(origin: CGPoint(), size: size))
|
||||
contentNode.updateLayout(size: size, transition: transition)
|
||||
transition.updateFrame(node: contentNode, frame: contentFrame)
|
||||
contentNode.updateLayout(size: contentFrame.size, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
@ -141,9 +157,11 @@ final class OverlayVideoDecoration: UniversalVideoDecoration {
|
||||
if self.controlsNode.alpha.isZero {
|
||||
self.controlsNode.alpha = 1.0
|
||||
self.controlsNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
self.controlsAreShowingUpdated(true)
|
||||
} else {
|
||||
self.controlsNode.alpha = 0.0
|
||||
self.controlsNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3)
|
||||
self.controlsAreShowingUpdated(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -152,6 +170,7 @@ final class OverlayVideoDecoration: UniversalVideoDecoration {
|
||||
if self.controlsNode.alpha.isZero {
|
||||
self.controlsNode.alpha = 1.0
|
||||
self.controlsNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
self.controlsAreShowingUpdated(true)
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user