mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
74aff6fb6d
@ -380,6 +380,7 @@ official_apple_pay_merchants = [
|
|||||||
"merchant.smartglocal.prod.telegramios",
|
"merchant.smartglocal.prod.telegramios",
|
||||||
"merchant.smartglocal.test.telegramios",
|
"merchant.smartglocal.test.telegramios",
|
||||||
"merchant.yoomoney.test.telegramios",
|
"merchant.yoomoney.test.telegramios",
|
||||||
|
"merchant.org.telegram.Best2pay.test",
|
||||||
]
|
]
|
||||||
|
|
||||||
official_bundle_ids = [
|
official_bundle_ids = [
|
||||||
|
@ -220,6 +220,8 @@
|
|||||||
"PUSH_REMINDER_TITLE" = "🗓 Reminder";
|
"PUSH_REMINDER_TITLE" = "🗓 Reminder";
|
||||||
"PUSH_SENDER_YOU" = "📅 You";
|
"PUSH_SENDER_YOU" = "📅 You";
|
||||||
|
|
||||||
|
"PUSH_CHAT_REQ_JOINED" = "%2$@|%1$@ was accepted into the group";
|
||||||
|
|
||||||
"LOCAL_MESSAGE_FWDS" = "%1$@ forwarded you %2$d messages";
|
"LOCAL_MESSAGE_FWDS" = "%1$@ forwarded you %2$d messages";
|
||||||
"LOCAL_CHANNEL_MESSAGE_FWDS" = "%1$@ posted %2$d forwarded messages";
|
"LOCAL_CHANNEL_MESSAGE_FWDS" = "%1$@ posted %2$d forwarded messages";
|
||||||
"LOCAL_CHAT_MESSAGE_FWDS" = "%1$@ forwarded %2$d messages";
|
"LOCAL_CHAT_MESSAGE_FWDS" = "%1$@ forwarded %2$d messages";
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import UIKit
|
import UIKit
|
||||||
import AsyncDisplayKit
|
import AsyncDisplayKit
|
||||||
|
import AVKit
|
||||||
|
|
||||||
public struct OverlayMediaItemNodeGroup: Hashable, RawRepresentable {
|
public struct OverlayMediaItemNodeGroup: Hashable, RawRepresentable {
|
||||||
public var rawValue: Int32
|
public var rawValue: Int32
|
||||||
@ -61,4 +62,9 @@ open class OverlayMediaItemNode: ASDisplayNode {
|
|||||||
open func performCustomTransitionOut() -> Bool {
|
open func performCustomTransitionOut() -> Bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension 15.0, iOS 15.0, *)
|
||||||
|
open func makeNativeContentSource() -> AVPictureInPictureController.ContentSource? {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import UIKit
|
import UIKit
|
||||||
import Display
|
import Display
|
||||||
|
import AVFoundation
|
||||||
|
import AsyncDisplayKit
|
||||||
|
|
||||||
public final class OverlayMediaControllerEmbeddingItem {
|
public final class OverlayMediaControllerEmbeddingItem {
|
||||||
public let position: CGPoint
|
public let position: CGPoint
|
||||||
@ -15,6 +17,10 @@ public final class OverlayMediaControllerEmbeddingItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public protocol PictureInPictureContent: AnyObject {
|
||||||
|
var videoNode: ASDisplayNode { get }
|
||||||
|
}
|
||||||
|
|
||||||
public protocol OverlayMediaController: AnyObject {
|
public protocol OverlayMediaController: AnyObject {
|
||||||
var updatePossibleEmbeddingItem: ((OverlayMediaControllerEmbeddingItem?) -> Void)? { get set }
|
var updatePossibleEmbeddingItem: ((OverlayMediaControllerEmbeddingItem?) -> Void)? { get set }
|
||||||
var embedPossibleEmbeddingItem: ((OverlayMediaControllerEmbeddingItem) -> Bool)? { get set }
|
var embedPossibleEmbeddingItem: ((OverlayMediaControllerEmbeddingItem) -> Bool)? { get set }
|
||||||
@ -22,6 +28,10 @@ public protocol OverlayMediaController: AnyObject {
|
|||||||
var hasNodes: Bool { get }
|
var hasNodes: Bool { get }
|
||||||
func addNode(_ node: OverlayMediaItemNode, customTransition: Bool)
|
func addNode(_ node: OverlayMediaItemNode, customTransition: Bool)
|
||||||
func removeNode(_ node: OverlayMediaItemNode, customTransition: Bool)
|
func removeNode(_ node: OverlayMediaItemNode, customTransition: Bool)
|
||||||
|
|
||||||
|
func setPictureInPictureContent(content: PictureInPictureContent, absoluteRect: CGRect)
|
||||||
|
func setPictureInPictureContentHidden(content: PictureInPictureContent, isHidden value: Bool)
|
||||||
|
func removePictureInPictureContent(content: PictureInPictureContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class OverlayMediaManager {
|
public final class OverlayMediaManager {
|
||||||
|
@ -7,6 +7,7 @@ import TelegramCore
|
|||||||
import Display
|
import Display
|
||||||
import TelegramAudio
|
import TelegramAudio
|
||||||
import UniversalMediaPlayer
|
import UniversalMediaPlayer
|
||||||
|
import AVFoundation
|
||||||
|
|
||||||
public protocol UniversalVideoContentNode: AnyObject {
|
public protocol UniversalVideoContentNode: AnyObject {
|
||||||
var ready: Signal<Void, NoError> { get }
|
var ready: Signal<Void, NoError> { get }
|
||||||
@ -29,6 +30,7 @@ public protocol UniversalVideoContentNode: AnyObject {
|
|||||||
func removePlaybackCompleted(_ index: Int)
|
func removePlaybackCompleted(_ index: Int)
|
||||||
func fetchControl(_ control: UniversalVideoNodeFetchControl)
|
func fetchControl(_ control: UniversalVideoNodeFetchControl)
|
||||||
func notifyPlaybackControlsHidden(_ hidden: Bool)
|
func notifyPlaybackControlsHidden(_ hidden: Bool)
|
||||||
|
func setCanPlaybackWithoutHierarchy(_ canPlaybackWithoutHierarchy: Bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
public protocol UniversalVideoContent {
|
public protocol UniversalVideoContent {
|
||||||
@ -332,4 +334,36 @@ public final class UniversalVideoNode: ASDisplayNode {
|
|||||||
self.decoration.tap()
|
self.decoration.tap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func getVideoLayer() -> AVSampleBufferDisplayLayer? {
|
||||||
|
guard let contentNode = self.contentNode else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func findVideoLayer(layer: CALayer) -> AVSampleBufferDisplayLayer? {
|
||||||
|
if let layer = layer as? AVSampleBufferDisplayLayer {
|
||||||
|
return layer
|
||||||
|
}
|
||||||
|
|
||||||
|
if let sublayers = layer.sublayers {
|
||||||
|
for sublayer in sublayers {
|
||||||
|
if let result = findVideoLayer(layer: sublayer) {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return findVideoLayer(layer: contentNode.layer)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func setCanPlaybackWithoutHierarchy(_ canPlaybackWithoutHierarchy: Bool) {
|
||||||
|
self.manager.withUniversalVideoContent(id: self.content.id, { contentNode in
|
||||||
|
if let contentNode = contentNode {
|
||||||
|
contentNode.setCanPlaybackWithoutHierarchy(canPlaybackWithoutHierarchy)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -385,6 +385,8 @@ public class GalleryController: ViewController, StandalonePresentableController
|
|||||||
private var screenCaptureEventsDisposable: Disposable?
|
private var screenCaptureEventsDisposable: Disposable?
|
||||||
|
|
||||||
public var centralItemUpdated: ((MessageId) -> Void)?
|
public var centralItemUpdated: ((MessageId) -> Void)?
|
||||||
|
public var onDidAppear: (() -> Void)?
|
||||||
|
public var useSimpleAnimation: Bool = false
|
||||||
|
|
||||||
private var initialOrientation: UIInterfaceOrientation?
|
private var initialOrientation: UIInterfaceOrientation?
|
||||||
|
|
||||||
@ -1037,11 +1039,11 @@ public class GalleryController: ViewController, StandalonePresentableController
|
|||||||
self?.presentingViewController?.dismiss(animated: false, completion: nil)
|
self?.presentingViewController?.dismiss(animated: false, completion: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.galleryNode.beginCustomDismiss = { [weak self] in
|
self.galleryNode.beginCustomDismiss = { [weak self] simpleAnimation in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf._hiddenMedia.set(.single(nil))
|
strongSelf._hiddenMedia.set(.single(nil))
|
||||||
|
|
||||||
let animatedOutNode = true
|
let animatedOutNode = !simpleAnimation
|
||||||
|
|
||||||
strongSelf.galleryNode.animateOut(animateContent: animatedOutNode, completion: {
|
strongSelf.galleryNode.animateOut(animateContent: animatedOutNode, completion: {
|
||||||
})
|
})
|
||||||
@ -1296,13 +1298,15 @@ public class GalleryController: ViewController, StandalonePresentableController
|
|||||||
}
|
}
|
||||||
centralItemNode.activateAsInitial()
|
centralItemNode.activateAsInitial()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.onDidAppear?()
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.isPresentedInPreviewingContext() {
|
if !self.isPresentedInPreviewingContext() {
|
||||||
self.galleryNode.setControlsHidden(self.landscape, animated: false)
|
self.galleryNode.setControlsHidden(self.landscape, animated: false)
|
||||||
if let presentationArguments = self.presentationArguments as? GalleryControllerPresentationArguments {
|
if let presentationArguments = self.presentationArguments as? GalleryControllerPresentationArguments {
|
||||||
if presentationArguments.animated {
|
if presentationArguments.animated {
|
||||||
self.galleryNode.animateIn(animateContent: !nodeAnimatesItself)
|
self.galleryNode.animateIn(animateContent: !nodeAnimatesItself && !self.useSimpleAnimation, useSimpleAnimation: self.useSimpleAnimation)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ open class GalleryControllerNode: ASDisplayNode, UIScrollViewDelegate, UIGesture
|
|||||||
public var scrollView: UIScrollView
|
public var scrollView: UIScrollView
|
||||||
public var pager: GalleryPagerNode
|
public var pager: GalleryPagerNode
|
||||||
|
|
||||||
public var beginCustomDismiss: () -> Void = { }
|
public var beginCustomDismiss: (Bool) -> Void = { _ in }
|
||||||
public var completeCustomDismiss: () -> Void = { }
|
public var completeCustomDismiss: () -> Void = { }
|
||||||
public var baseNavigationController: () -> NavigationController? = { return nil }
|
public var baseNavigationController: () -> NavigationController? = { return nil }
|
||||||
public var galleryController: () -> ViewController? = { return nil }
|
public var galleryController: () -> ViewController? = { return nil }
|
||||||
@ -106,9 +106,9 @@ open class GalleryControllerNode: ASDisplayNode, UIScrollViewDelegate, UIGesture
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.pager.beginCustomDismiss = { [weak self] in
|
self.pager.beginCustomDismiss = { [weak self] simpleAnimation in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.beginCustomDismiss()
|
strongSelf.beginCustomDismiss(simpleAnimation)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -327,14 +327,16 @@ open class GalleryControllerNode: ASDisplayNode, UIScrollViewDelegate, UIGesture
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open func animateIn(animateContent: Bool) {
|
open func animateIn(animateContent: Bool, useSimpleAnimation: Bool) {
|
||||||
|
let duration: Double = animateContent ? 0.2 : 0.3
|
||||||
|
|
||||||
self.backgroundNode.backgroundColor = self.backgroundNode.backgroundColor?.withAlphaComponent(0.0)
|
self.backgroundNode.backgroundColor = self.backgroundNode.backgroundColor?.withAlphaComponent(0.0)
|
||||||
self.statusBar?.alpha = 0.0
|
self.statusBar?.alpha = 0.0
|
||||||
self.navigationBar?.alpha = 0.0
|
self.navigationBar?.alpha = 0.0
|
||||||
self.footerNode.alpha = 0.0
|
self.footerNode.alpha = 0.0
|
||||||
self.currentThumbnailContainerNode?.alpha = 0.0
|
self.currentThumbnailContainerNode?.alpha = 0.0
|
||||||
|
|
||||||
UIView.animate(withDuration: 0.2, animations: {
|
UIView.animate(withDuration: duration, animations: {
|
||||||
self.backgroundNode.backgroundColor = self.backgroundNode.backgroundColor?.withAlphaComponent(1.0)
|
self.backgroundNode.backgroundColor = self.backgroundNode.backgroundColor?.withAlphaComponent(1.0)
|
||||||
if !self.areControlsHidden {
|
if !self.areControlsHidden {
|
||||||
self.statusBar?.alpha = 1.0
|
self.statusBar?.alpha = 1.0
|
||||||
@ -346,6 +348,8 @@ open class GalleryControllerNode: ASDisplayNode, UIScrollViewDelegate, UIGesture
|
|||||||
|
|
||||||
if animateContent {
|
if animateContent {
|
||||||
self.scrollView.layer.animateBounds(from: self.scrollView.layer.bounds.offsetBy(dx: 0.0, dy: -self.scrollView.layer.bounds.size.height), to: self.scrollView.layer.bounds, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring)
|
self.scrollView.layer.animateBounds(from: self.scrollView.layer.bounds.offsetBy(dx: 0.0, dy: -self.scrollView.layer.bounds.size.height), to: self.scrollView.layer.bounds, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring)
|
||||||
|
} else if useSimpleAnimation {
|
||||||
|
self.scrollView.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -384,6 +388,11 @@ open class GalleryControllerNode: ASDisplayNode, UIScrollViewDelegate, UIGesture
|
|||||||
contentAnimationCompleted = true
|
contentAnimationCompleted = true
|
||||||
intermediateCompletion()
|
intermediateCompletion()
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
self.scrollView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in
|
||||||
|
contentAnimationCompleted = true
|
||||||
|
intermediateCompletion()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ open class GalleryItemNode: ASDisplayNode {
|
|||||||
public var updateControlsVisibility: (Bool) -> Void = { _ in }
|
public var updateControlsVisibility: (Bool) -> Void = { _ in }
|
||||||
public var updateOrientation: (UIInterfaceOrientation) -> Void = { _ in }
|
public var updateOrientation: (UIInterfaceOrientation) -> Void = { _ in }
|
||||||
public var dismiss: () -> Void = { }
|
public var dismiss: () -> Void = { }
|
||||||
public var beginCustomDismiss: () -> Void = { }
|
public var beginCustomDismiss: (Bool) -> Void = { _ in }
|
||||||
public var completeCustomDismiss: () -> Void = { }
|
public var completeCustomDismiss: () -> Void = { }
|
||||||
public var baseNavigationController: () -> NavigationController? = { return nil }
|
public var baseNavigationController: () -> NavigationController? = { return nil }
|
||||||
public var galleryController: () -> ViewController? = { return nil }
|
public var galleryController: () -> ViewController? = { return nil }
|
||||||
|
@ -109,7 +109,7 @@ public final class GalleryPagerNode: ASDisplayNode, UIScrollViewDelegate, UIGest
|
|||||||
public var updateControlsVisibility: (Bool) -> Void = { _ in }
|
public var updateControlsVisibility: (Bool) -> Void = { _ in }
|
||||||
public var updateOrientation: (UIInterfaceOrientation) -> Void = { _ in }
|
public var updateOrientation: (UIInterfaceOrientation) -> Void = { _ in }
|
||||||
public var dismiss: () -> Void = { }
|
public var dismiss: () -> Void = { }
|
||||||
public var beginCustomDismiss: () -> Void = { }
|
public var beginCustomDismiss: (Bool) -> Void = { _ in }
|
||||||
public var completeCustomDismiss: () -> Void = { }
|
public var completeCustomDismiss: () -> Void = { }
|
||||||
public var baseNavigationController: () -> NavigationController? = { return nil }
|
public var baseNavigationController: () -> NavigationController? = { return nil }
|
||||||
public var galleryController: () -> ViewController? = { return nil }
|
public var galleryController: () -> ViewController? = { return nil }
|
||||||
|
@ -20,6 +20,7 @@ import SaveToCameraRoll
|
|||||||
import UndoUI
|
import UndoUI
|
||||||
import TelegramUIPreferences
|
import TelegramUIPreferences
|
||||||
import OpenInExternalAppUI
|
import OpenInExternalAppUI
|
||||||
|
import AVKit
|
||||||
|
|
||||||
public enum UniversalVideoGalleryItemContentInfo {
|
public enum UniversalVideoGalleryItemContentInfo {
|
||||||
case message(Message)
|
case message(Message)
|
||||||
@ -463,6 +464,200 @@ private final class MoreHeaderButton: HighlightableButtonNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(iOS 15.0, *)
|
||||||
|
private final class PictureInPictureContentImpl: NSObject, PictureInPictureContent, AVPictureInPictureControllerDelegate {
|
||||||
|
private final class PlaybackDelegate: NSObject, AVPictureInPictureSampleBufferPlaybackDelegate {
|
||||||
|
private let node: UniversalVideoNode
|
||||||
|
private var statusDisposable: Disposable?
|
||||||
|
private var status: MediaPlayerStatus?
|
||||||
|
weak var pictureInPictureController: AVPictureInPictureController?
|
||||||
|
|
||||||
|
init(node: UniversalVideoNode) {
|
||||||
|
self.node = node
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
self.statusDisposable = (self.node.status
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] status in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.status = status
|
||||||
|
strongSelf.pictureInPictureController?.invalidatePlaybackState()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.statusDisposable?.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
public func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, setPlaying playing: Bool) {
|
||||||
|
self.node.togglePlayPause()
|
||||||
|
}
|
||||||
|
|
||||||
|
public func pictureInPictureControllerTimeRangeForPlayback(_ pictureInPictureController: AVPictureInPictureController) -> CMTimeRange {
|
||||||
|
guard let status = self.status else {
|
||||||
|
return CMTimeRange(start: CMTime(seconds: 0.0, preferredTimescale: CMTimeScale(30.0)), duration: CMTime(seconds: 0.0, preferredTimescale: CMTimeScale(30.0)))
|
||||||
|
}
|
||||||
|
return CMTimeRange(start: CMTime(seconds: 0.0, preferredTimescale: CMTimeScale(30.0)), duration: CMTime(seconds: status.duration, preferredTimescale: CMTimeScale(30.0)))
|
||||||
|
}
|
||||||
|
|
||||||
|
public func pictureInPictureControllerIsPlaybackPaused(_ pictureInPictureController: AVPictureInPictureController) -> Bool {
|
||||||
|
guard let status = self.status else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
switch status.status {
|
||||||
|
case .playing:
|
||||||
|
return false
|
||||||
|
case .buffering, .paused:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, didTransitionToRenderSize newRenderSize: CMVideoDimensions) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, skipByInterval skipInterval: CMTime, completion completionHandler: @escaping () -> Void) {
|
||||||
|
completionHandler()
|
||||||
|
}
|
||||||
|
|
||||||
|
public func pictureInPictureControllerShouldProhibitBackgroundAudioPlayback(_ pictureInPictureController: AVPictureInPictureController) -> Bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private weak var overlayController: OverlayMediaController?
|
||||||
|
private weak var mediaManager: MediaManager?
|
||||||
|
private var pictureInPictureController: AVPictureInPictureController?
|
||||||
|
private var contentDelegate: PlaybackDelegate?
|
||||||
|
private let node: UniversalVideoNode
|
||||||
|
private let willBegin: (PictureInPictureContentImpl) -> Void
|
||||||
|
private let didEnd: (PictureInPictureContentImpl) -> Void
|
||||||
|
private let expand: (@escaping () -> Void) -> Void
|
||||||
|
private var pictureInPictureTimer: SwiftSignalKit.Timer?
|
||||||
|
private var didExpand: Bool = false
|
||||||
|
|
||||||
|
private var hiddenMediaManagerIndex: Int?
|
||||||
|
|
||||||
|
init(overlayController: OverlayMediaController, mediaManager: MediaManager, accountId: AccountRecordId, hiddenMedia: (MessageId, Media)?, videoNode: UniversalVideoNode, willBegin: @escaping (PictureInPictureContentImpl) -> Void, didEnd: @escaping (PictureInPictureContentImpl) -> Void, expand: @escaping (@escaping () -> Void) -> Void) {
|
||||||
|
self.overlayController = overlayController
|
||||||
|
self.mediaManager = mediaManager
|
||||||
|
self.node = videoNode
|
||||||
|
self.willBegin = willBegin
|
||||||
|
self.didEnd = didEnd
|
||||||
|
self.expand = expand
|
||||||
|
|
||||||
|
self.node.setCanPlaybackWithoutHierarchy(true)
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
let contentDelegate = PlaybackDelegate(node: self.node)
|
||||||
|
self.contentDelegate = contentDelegate
|
||||||
|
|
||||||
|
let pictureInPictureController = AVPictureInPictureController(contentSource: AVPictureInPictureController.ContentSource(sampleBufferDisplayLayer: videoNode.getVideoLayer()!, playbackDelegate: contentDelegate))
|
||||||
|
self.pictureInPictureController = pictureInPictureController
|
||||||
|
contentDelegate.pictureInPictureController = pictureInPictureController
|
||||||
|
|
||||||
|
pictureInPictureController.canStartPictureInPictureAutomaticallyFromInline = false
|
||||||
|
pictureInPictureController.requiresLinearPlayback = true
|
||||||
|
pictureInPictureController.delegate = self
|
||||||
|
self.pictureInPictureController = pictureInPictureController
|
||||||
|
let timer = SwiftSignalKit.Timer(timeout: 0.005, repeat: true, completion: { [weak self] in
|
||||||
|
guard let strongSelf = self, let pictureInPictureController = strongSelf.pictureInPictureController else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if pictureInPictureController.isPictureInPicturePossible {
|
||||||
|
strongSelf.pictureInPictureTimer?.invalidate()
|
||||||
|
strongSelf.pictureInPictureTimer = nil
|
||||||
|
|
||||||
|
pictureInPictureController.startPictureInPicture()
|
||||||
|
}
|
||||||
|
}, queue: .mainQueue())
|
||||||
|
self.pictureInPictureTimer = timer
|
||||||
|
timer.start()
|
||||||
|
|
||||||
|
if let hiddenMedia = hiddenMedia {
|
||||||
|
self.hiddenMediaManagerIndex = mediaManager.galleryHiddenMediaManager.addSource(Signal<(MessageId, Media)?, NoError>.single(hiddenMedia)
|
||||||
|
|> map { messageIdAndMedia in
|
||||||
|
if let (messageId, media) = messageIdAndMedia {
|
||||||
|
return .chat(accountId, messageId, media)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.pictureInPictureTimer?.invalidate()
|
||||||
|
self.node.setCanPlaybackWithoutHierarchy(false)
|
||||||
|
|
||||||
|
if let hiddenMediaManagerIndex = self.hiddenMediaManagerIndex, let mediaManager = self.mediaManager {
|
||||||
|
mediaManager.galleryHiddenMediaManager.removeSource(hiddenMediaManagerIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var videoNode: ASDisplayNode {
|
||||||
|
return self.node
|
||||||
|
}
|
||||||
|
|
||||||
|
public func pictureInPictureControllerWillStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
|
||||||
|
Queue.mainQueue().after(0.1, { [weak self] in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.willBegin(strongSelf)
|
||||||
|
|
||||||
|
if let overlayController = strongSelf.overlayController {
|
||||||
|
overlayController.setPictureInPictureContentHidden(content: strongSelf, isHidden: true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public func pictureInPictureControllerDidStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
|
||||||
|
self.didEnd(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, failedToStartPictureInPictureWithError error: Error) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public func pictureInPictureControllerWillStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public func pictureInPictureControllerDidStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
|
||||||
|
guard let overlayController = self.overlayController else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
overlayController.removePictureInPictureContent(content: self)
|
||||||
|
self.node.canAttachContent = false
|
||||||
|
if self.didExpand {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.node.continuePlayingWithoutSound()
|
||||||
|
}
|
||||||
|
|
||||||
|
public func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void) {
|
||||||
|
self.expand { [weak self] in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
strongSelf.didExpand = true
|
||||||
|
|
||||||
|
if let overlayController = strongSelf.overlayController {
|
||||||
|
overlayController.setPictureInPictureContentHidden(content: strongSelf, isHidden: false)
|
||||||
|
strongSelf.node.alpha = 0.02
|
||||||
|
}
|
||||||
|
|
||||||
|
completionHandler(true)
|
||||||
|
|
||||||
|
/*Queue.mainQueue().after(0.2, {
|
||||||
|
self?.node.canAttachContent = false
|
||||||
|
})*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private let presentationData: PresentationData
|
private let presentationData: PresentationData
|
||||||
@ -485,6 +680,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||||||
private var videoNodeUserInteractionEnabled: Bool = false
|
private var videoNodeUserInteractionEnabled: Bool = false
|
||||||
private var videoFramePreview: FramePreview?
|
private var videoFramePreview: FramePreview?
|
||||||
private var pictureInPictureNode: UniversalVideoGalleryItemPictureInPictureNode?
|
private var pictureInPictureNode: UniversalVideoGalleryItemPictureInPictureNode?
|
||||||
|
private var disablePictureInPicturePlaceholder: Bool = false
|
||||||
private let statusButtonNode: HighlightableButtonNode
|
private let statusButtonNode: HighlightableButtonNode
|
||||||
private let statusNode: RadialStatusNode
|
private let statusNode: RadialStatusNode
|
||||||
private var statusNodeShouldBeHidden = true
|
private var statusNodeShouldBeHidden = true
|
||||||
@ -532,6 +728,8 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||||||
|
|
||||||
private var customUnembedWhenPortrait: ((OverlayMediaItemNode) -> Bool)?
|
private var customUnembedWhenPortrait: ((OverlayMediaItemNode) -> Bool)?
|
||||||
|
|
||||||
|
private var pictureInPictureContent: AnyObject?
|
||||||
|
|
||||||
init(context: AccountContext, presentationData: PresentationData, performAction: @escaping (GalleryControllerInteractionTapAction) -> Void, openActionOptions: @escaping (GalleryControllerInteractionTapAction, Message) -> Void, present: @escaping (ViewController, Any?) -> Void) {
|
init(context: AccountContext, presentationData: PresentationData, performAction: @escaping (GalleryControllerInteractionTapAction) -> Void, openActionOptions: @escaping (GalleryControllerInteractionTapAction, Message) -> Void, present: @escaping (ViewController, Any?) -> Void) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
@ -1209,7 +1407,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func updateDisplayPlaceholder(_ displayPlaceholder: Bool) {
|
private func updateDisplayPlaceholder(_ displayPlaceholder: Bool) {
|
||||||
if displayPlaceholder {
|
if displayPlaceholder && !self.disablePictureInPicturePlaceholder {
|
||||||
if self.pictureInPictureNode == nil {
|
if self.pictureInPictureNode == nil {
|
||||||
let pictureInPictureNode = UniversalVideoGalleryItemPictureInPictureNode(strings: self.presentationData.strings)
|
let pictureInPictureNode = UniversalVideoGalleryItemPictureInPictureNode(strings: self.presentationData.strings)
|
||||||
pictureInPictureNode.isUserInteractionEnabled = false
|
pictureInPictureNode.isUserInteractionEnabled = false
|
||||||
@ -1856,7 +2054,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if customUnembedWhenPortrait(overlayNode) {
|
if customUnembedWhenPortrait(overlayNode) {
|
||||||
self.beginCustomDismiss()
|
self.beginCustomDismiss(false)
|
||||||
self.statusNode.isHidden = true
|
self.statusNode.isHidden = true
|
||||||
self.animateOut(toOverlay: overlayNode, completion: { [weak self] in
|
self.animateOut(toOverlay: overlayNode, completion: { [weak self] in
|
||||||
self?.completeCustomDismiss()
|
self?.completeCustomDismiss()
|
||||||
@ -1866,9 +2064,79 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc func pictureInPictureButtonPressed() {
|
@objc func pictureInPictureButtonPressed() {
|
||||||
if let item = self.item, let videoNode = self.videoNode {
|
if let item = self.item, let videoNode = self.videoNode, let overlayController = self.context.sharedContext.mediaManager.overlayMediaManager.controller {
|
||||||
videoNode.setContinuePlayingWithoutSoundOnLostAudioSession(false)
|
videoNode.setContinuePlayingWithoutSoundOnLostAudioSession(false)
|
||||||
|
|
||||||
|
let context = self.context
|
||||||
|
let baseNavigationController = self.baseNavigationController()
|
||||||
|
let playbackRate = self.playbackRate
|
||||||
|
|
||||||
|
if #available(iOSApplicationExtension 15.0, iOS 15.0, *), AVPictureInPictureController.isPictureInPictureSupported() {
|
||||||
|
self.disablePictureInPicturePlaceholder = true
|
||||||
|
|
||||||
|
let overlayVideoNode = UniversalVideoNode(postbox: self.context.account.postbox, audioSession: self.context.sharedContext.mediaManager.audioSession, manager: self.context.sharedContext.mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: item.content, priority: .overlay)
|
||||||
|
let absoluteRect = videoNode.view.convert(videoNode.view.bounds, to: nil)
|
||||||
|
overlayVideoNode.frame = absoluteRect
|
||||||
|
overlayVideoNode.updateLayout(size: absoluteRect.size, transition: .immediate)
|
||||||
|
overlayVideoNode.canAttachContent = true
|
||||||
|
|
||||||
|
var hiddenMedia: (MessageId, Media)? = nil
|
||||||
|
switch item.contentInfo {
|
||||||
|
case let .message(message):
|
||||||
|
for media in message.media {
|
||||||
|
if let media = media as? TelegramMediaImage {
|
||||||
|
hiddenMedia = (message.id, media)
|
||||||
|
} else if let media = media as? TelegramMediaFile, media.isVideo {
|
||||||
|
hiddenMedia = (message.id, media)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
let content = PictureInPictureContentImpl(overlayController: overlayController, mediaManager: self.context.sharedContext.mediaManager, accountId: self.context.account.id, hiddenMedia: hiddenMedia, videoNode: overlayVideoNode, willBegin: { [weak self] content in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.beginCustomDismiss(true)
|
||||||
|
}, didEnd: { [weak self] _ in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.completeCustomDismiss()
|
||||||
|
}, expand: { [weak baseNavigationController] completion in
|
||||||
|
guard let contentInfo = item.contentInfo else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch contentInfo {
|
||||||
|
case let .message(message):
|
||||||
|
let gallery = GalleryController(context: context, source: .peerMessagesAtId(messageId: message.id, chatLocation: .peer(message.id.peerId), chatLocationContextHolder: Atomic<ChatLocationContextHolder?>(value: nil)), playbackRate: playbackRate, replaceRootController: { [weak baseNavigationController] controller, ready in
|
||||||
|
if let baseNavigationController = baseNavigationController {
|
||||||
|
baseNavigationController.replaceTopController(controller, animated: false, ready: ready)
|
||||||
|
}
|
||||||
|
}, baseNavigationController: baseNavigationController)
|
||||||
|
gallery.temporaryDoNotWaitForReady = true
|
||||||
|
gallery.useSimpleAnimation = true
|
||||||
|
|
||||||
|
baseNavigationController?.view.endEditing(true)
|
||||||
|
|
||||||
|
(baseNavigationController?.topViewController as? ViewController)?.present(gallery, in: .window(.root), with: GalleryControllerPresentationArguments(transitionArguments: { id, media in
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
|
||||||
|
gallery.onDidAppear = {
|
||||||
|
completion()
|
||||||
|
}
|
||||||
|
case .webPage:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
self.pictureInPictureContent = content
|
||||||
|
|
||||||
|
self.context.sharedContext.mediaManager.overlayMediaManager.controller?.setPictureInPictureContent(content: content, absoluteRect: absoluteRect)
|
||||||
|
} else {
|
||||||
let context = self.context
|
let context = self.context
|
||||||
let baseNavigationController = self.baseNavigationController()
|
let baseNavigationController = self.baseNavigationController()
|
||||||
let mediaManager = self.context.sharedContext.mediaManager
|
let mediaManager = self.context.sharedContext.mediaManager
|
||||||
@ -1962,7 +2230,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||||||
}
|
}
|
||||||
context.sharedContext.mediaManager.setOverlayVideoNode(overlayNode)
|
context.sharedContext.mediaManager.setOverlayVideoNode(overlayNode)
|
||||||
if overlayNode.supernode != nil {
|
if overlayNode.supernode != nil {
|
||||||
self.beginCustomDismiss()
|
self.beginCustomDismiss(false)
|
||||||
self.statusNode.isHidden = true
|
self.statusNode.isHidden = true
|
||||||
self.animateOut(toOverlay: overlayNode, completion: { [weak self] in
|
self.animateOut(toOverlay: overlayNode, completion: { [weak self] in
|
||||||
self?.completeCustomDismiss()
|
self?.completeCustomDismiss()
|
||||||
@ -1970,6 +2238,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func contentInfo() -> (message: Message, file: TelegramMediaFile?, isWebpage: Bool)? {
|
private func contentInfo() -> (message: Message, file: TelegramMediaFile?, isWebpage: Bool)? {
|
||||||
guard let item = self.item else {
|
guard let item = self.item else {
|
||||||
|
@ -86,8 +86,8 @@ private final class SecretMediaPreviewControllerNode: GalleryControllerNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func animateIn(animateContent: Bool) {
|
override func animateIn(animateContent: Bool, useSimpleAnimation: Bool) {
|
||||||
super.animateIn(animateContent: animateContent)
|
super.animateIn(animateContent: animateContent, useSimpleAnimation: useSimpleAnimation)
|
||||||
|
|
||||||
self.timeoutNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
self.timeoutNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
}
|
}
|
||||||
@ -232,7 +232,7 @@ public final class SecretMediaPreviewController: ViewController {
|
|||||||
self?.presentingViewController?.dismiss(animated: false, completion: nil)
|
self?.presentingViewController?.dismiss(animated: false, completion: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.controllerNode.beginCustomDismiss = { [weak self] in
|
self.controllerNode.beginCustomDismiss = { [weak self] _ in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf._hiddenMedia.set(.single(nil))
|
strongSelf._hiddenMedia.set(.single(nil))
|
||||||
|
|
||||||
@ -369,7 +369,7 @@ public final class SecretMediaPreviewController: ViewController {
|
|||||||
self.controllerNode.setControlsHidden(false, animated: false)
|
self.controllerNode.setControlsHidden(false, animated: false)
|
||||||
if let presentationArguments = self.presentationArguments as? GalleryControllerPresentationArguments {
|
if let presentationArguments = self.presentationArguments as? GalleryControllerPresentationArguments {
|
||||||
if presentationArguments.animated {
|
if presentationArguments.animated {
|
||||||
self.controllerNode.animateIn(animateContent: !nodeAnimatesItself)
|
self.controllerNode.animateIn(animateContent: !nodeAnimatesItself, useSimpleAnimation: false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -454,7 +454,7 @@ public class InstantPageGalleryController: ViewController, StandalonePresentable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.galleryNode.animateIn(animateContent: !nodeAnimatesItself)
|
self.galleryNode.animateIn(animateContent: !nodeAnimatesItself, useSimpleAnimation: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||||
|
@ -61,6 +61,7 @@ private enum PollStatus: CustomStringConvertible {
|
|||||||
|
|
||||||
public final class MediaPlayerNode: ASDisplayNode {
|
public final class MediaPlayerNode: ASDisplayNode {
|
||||||
public var videoInHierarchy: Bool = false
|
public var videoInHierarchy: Bool = false
|
||||||
|
var canPlaybackWithoutHierarchy: Bool = false
|
||||||
public var updateVideoInHierarchy: ((Bool) -> Void)?
|
public var updateVideoInHierarchy: ((Bool) -> Void)?
|
||||||
|
|
||||||
private var videoNode: MediaPlayerNodeDisplayNode
|
private var videoNode: MediaPlayerNodeDisplayNode
|
||||||
@ -117,7 +118,7 @@ public final class MediaPlayerNode: ASDisplayNode {
|
|||||||
videoLayer.setAffineTransform(transform)
|
videoLayer.setAffineTransform(transform)
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.videoInHierarchy {
|
if self.videoInHierarchy || self.canPlaybackWithoutHierarchy {
|
||||||
if requestFrames {
|
if requestFrames {
|
||||||
self.startPolling()
|
self.startPolling()
|
||||||
}
|
}
|
||||||
@ -137,7 +138,7 @@ public final class MediaPlayerNode: ASDisplayNode {
|
|||||||
switch status {
|
switch status {
|
||||||
case let .delay(delay):
|
case let .delay(delay):
|
||||||
strongSelf.timer = SwiftSignalKit.Timer(timeout: delay, repeat: true, completion: {
|
strongSelf.timer = SwiftSignalKit.Timer(timeout: delay, repeat: true, completion: {
|
||||||
if let strongSelf = self, let videoLayer = strongSelf.videoLayer, let (_, requestFrames, _, _) = strongSelf.state, requestFrames, strongSelf.videoInHierarchy {
|
if let strongSelf = self, let videoLayer = strongSelf.videoLayer, let (_, requestFrames, _, _) = strongSelf.state, requestFrames, (strongSelf.videoInHierarchy || strongSelf.canPlaybackWithoutHierarchy) {
|
||||||
if videoLayer.isReadyForMoreMediaData {
|
if videoLayer.isReadyForMoreMediaData {
|
||||||
strongSelf.timer?.invalidate()
|
strongSelf.timer?.invalidate()
|
||||||
strongSelf.timer = nil
|
strongSelf.timer = nil
|
||||||
@ -385,7 +386,7 @@ public final class MediaPlayerNode: ASDisplayNode {
|
|||||||
strongSelf.updateState()
|
strongSelf.updateState()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
strongSelf.updateVideoInHierarchy?(value)
|
strongSelf.updateVideoInHierarchy?(strongSelf.videoInHierarchy || strongSelf.canPlaybackWithoutHierarchy)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.addSubnode(self.videoNode)
|
self.addSubnode(self.videoNode)
|
||||||
@ -458,4 +459,14 @@ public final class MediaPlayerNode: ASDisplayNode {
|
|||||||
public func reset() {
|
public func reset() {
|
||||||
self.videoLayer?.flush()
|
self.videoLayer?.flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func setCanPlaybackWithoutHierarchy(_ canPlaybackWithoutHierarchy: Bool) {
|
||||||
|
if self.canPlaybackWithoutHierarchy != canPlaybackWithoutHierarchy {
|
||||||
|
self.canPlaybackWithoutHierarchy = canPlaybackWithoutHierarchy
|
||||||
|
if canPlaybackWithoutHierarchy {
|
||||||
|
self.updateState()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.updateVideoInHierarchy?(self.videoInHierarchy || self.canPlaybackWithoutHierarchy)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -244,7 +244,7 @@ class SecureIdDocumentGalleryController: ViewController, StandalonePresentableCo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.galleryNode.animateIn(animateContent: !nodeAnimatesItself)
|
self.galleryNode.animateIn(animateContent: !nodeAnimatesItself, useSimpleAnimation: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var firstLayout = true
|
private var firstLayout = true
|
||||||
|
@ -670,7 +670,7 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
|
|||||||
self.galleryNode.setControlsHidden(false, animated: false)
|
self.galleryNode.setControlsHidden(false, animated: false)
|
||||||
if let presentationArguments = self.presentationArguments as? AvatarGalleryControllerPresentationArguments {
|
if let presentationArguments = self.presentationArguments as? AvatarGalleryControllerPresentationArguments {
|
||||||
if presentationArguments.animated {
|
if presentationArguments.animated {
|
||||||
self.galleryNode.animateIn(animateContent: !nodeAnimatesItself)
|
self.galleryNode.animateIn(animateContent: !nodeAnimatesItself, useSimpleAnimation: false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -653,39 +653,66 @@ public final class SparseItemGrid: ASDisplayNode {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func anchorItem(at point: CGPoint) -> Item? {
|
func anchorItem(at point: CGPoint, orLower: Bool = false) -> (Item, Int)? {
|
||||||
guard let items = self.items, !items.items.isEmpty, let layout = self.layout else {
|
guard let items = self.items, !items.items.isEmpty, let layout = self.layout else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if layout.containerLayout.lockScrollingAtTop {
|
if layout.containerLayout.lockScrollingAtTop {
|
||||||
if let item = items.item(at: 0) {
|
if let item = items.item(at: 0) {
|
||||||
return item
|
return (item, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let localPoint = self.scrollView.convert(point, from: self.view)
|
let localPoint = self.scrollView.convert(point, from: self.view)
|
||||||
|
|
||||||
var closestItem: (CGFloat, AnyHashable)?
|
var closestItem: (CGFloat, Int, AnyHashable)?
|
||||||
for (id, visibleItem) in self.visibleItems {
|
for (id, visibleItem) in self.visibleItems {
|
||||||
let itemCenter = visibleItem.frame.center
|
let itemCenter = visibleItem.frame.center
|
||||||
|
if visibleItem.frame.minY >= localPoint.y || visibleItem.frame.maxY < localPoint.y {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
let columnIndex = Int(floor(visibleItem.frame.minX / layout.itemSize.width))
|
||||||
|
let distanceX = itemCenter.x - localPoint.x
|
||||||
|
if orLower {
|
||||||
|
if distanceX > 0.0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let distanceY = itemCenter.y - localPoint.y
|
||||||
|
let distance2 = distanceX * distanceX + distanceY * distanceY
|
||||||
|
|
||||||
|
if let (currentDistance2, _, _) = closestItem {
|
||||||
|
if distance2 < currentDistance2 {
|
||||||
|
closestItem = (distance2, columnIndex, id)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
closestItem = (distance2, columnIndex, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if closestItem == nil {
|
||||||
|
for (id, visibleItem) in self.visibleItems {
|
||||||
|
let itemCenter = visibleItem.frame.center
|
||||||
|
let columnIndex = Int(floor(visibleItem.frame.minX / layout.itemSize.width))
|
||||||
let distanceX = itemCenter.x - localPoint.x
|
let distanceX = itemCenter.x - localPoint.x
|
||||||
let distanceY = itemCenter.y - localPoint.y
|
let distanceY = itemCenter.y - localPoint.y
|
||||||
let distance2 = distanceX * distanceX + distanceY * distanceY
|
let distance2 = distanceX * distanceX + distanceY * distanceY
|
||||||
|
|
||||||
if let (currentDistance2, _) = closestItem {
|
if let (currentDistance2, _, _) = closestItem {
|
||||||
if distance2 < currentDistance2 {
|
if distance2 < currentDistance2 {
|
||||||
closestItem = (distance2, id)
|
closestItem = (distance2, columnIndex, id)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
closestItem = (distance2, id)
|
closestItem = (distance2, columnIndex, id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let (_, id) = closestItem {
|
if let (_, columnIndex, id) = closestItem {
|
||||||
for item in items.items {
|
for item in items.items {
|
||||||
if item.id == id {
|
if item.id == id {
|
||||||
return item
|
return (item, columnIndex)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -1083,6 +1110,7 @@ public final class SparseItemGrid: ASDisplayNode {
|
|||||||
let interactiveState: InteractiveState?
|
let interactiveState: InteractiveState?
|
||||||
let layout: ContainerLayout
|
let layout: ContainerLayout
|
||||||
let anchorItemIndex: Int
|
let anchorItemIndex: Int
|
||||||
|
let transitionAnchorPoint: CGPoint
|
||||||
let fromViewport: Viewport
|
let fromViewport: Viewport
|
||||||
let toViewport: Viewport
|
let toViewport: Viewport
|
||||||
|
|
||||||
@ -1094,10 +1122,11 @@ public final class SparseItemGrid: ASDisplayNode {
|
|||||||
|
|
||||||
let coveringOffsetUpdated: (ContainedViewLayoutTransition) -> Void
|
let coveringOffsetUpdated: (ContainedViewLayoutTransition) -> Void
|
||||||
|
|
||||||
init(interactiveState: InteractiveState?, layout: ContainerLayout, anchorItemIndex: Int, from fromViewport: Viewport, to toViewport: Viewport, coveringOffsetUpdated: @escaping (ContainedViewLayoutTransition) -> Void) {
|
init(interactiveState: InteractiveState?, layout: ContainerLayout, anchorItemIndex: Int, transitionAnchorPoint: CGPoint, from fromViewport: Viewport, to toViewport: Viewport, coveringOffsetUpdated: @escaping (ContainedViewLayoutTransition) -> Void) {
|
||||||
self.interactiveState = interactiveState
|
self.interactiveState = interactiveState
|
||||||
self.layout = layout
|
self.layout = layout
|
||||||
self.anchorItemIndex = anchorItemIndex
|
self.anchorItemIndex = anchorItemIndex
|
||||||
|
self.transitionAnchorPoint = transitionAnchorPoint
|
||||||
self.fromViewport = fromViewport
|
self.fromViewport = fromViewport
|
||||||
self.toViewport = toViewport
|
self.toViewport = toViewport
|
||||||
self.coveringOffsetUpdated = coveringOffsetUpdated
|
self.coveringOffsetUpdated = coveringOffsetUpdated
|
||||||
@ -1119,22 +1148,47 @@ public final class SparseItemGrid: ASDisplayNode {
|
|||||||
let previousProgress = self.currentProgress
|
let previousProgress = self.currentProgress
|
||||||
self.currentProgress = progress
|
self.currentProgress = progress
|
||||||
|
|
||||||
let fixedAnchorPoint = CGPoint(x: toAnchorFrame.midX, y: toAnchorFrame.midY)
|
var fromAnchorPoint = CGPoint()
|
||||||
|
var toAnchorPoint = CGPoint()
|
||||||
|
var fromDeltaOffset = CGPoint()
|
||||||
|
var fromScale: CGFloat = 1.0
|
||||||
|
var toScale: CGFloat = 1.0
|
||||||
|
|
||||||
|
var searchOffset: CGFloat = 0.0
|
||||||
|
|
||||||
|
while true {
|
||||||
|
//let fixedAnchorPoint = CGPoint(x: fromAnchorFrame.midX, y: fromAnchorFrame.midY)
|
||||||
|
var fixedAnchorPoint = self.transitionAnchorPoint
|
||||||
|
if fixedAnchorPoint.x < self.layout.size.width / 2.0 {
|
||||||
|
fixedAnchorPoint.x = 0.0
|
||||||
|
} else {
|
||||||
|
fixedAnchorPoint.x = self.layout.size.width
|
||||||
|
}
|
||||||
|
|
||||||
|
if let fromItem = self.fromViewport.anchorItem(at: fixedAnchorPoint), let fromFrame = self.fromViewport.frameForItem(at: fromItem.0.index) {
|
||||||
|
print("fromColumn: \(fromItem.1)")
|
||||||
|
fromAnchorFrame = fromFrame
|
||||||
|
|
||||||
if let fromItem = self.fromViewport.anchorItem(at: fixedAnchorPoint), let fromFrame = self.fromViewport.frameForItem(at: fromItem.index) {
|
|
||||||
fromAnchorFrame.origin.y = fromFrame.midY
|
fromAnchorFrame.origin.y = fromFrame.midY
|
||||||
fromAnchorFrame.origin.x = fromFrame.midX
|
fromAnchorFrame.origin.x = fromFrame.midX
|
||||||
fromAnchorFrame.size.width = 0.0
|
fromAnchorFrame.size.width = 0.0
|
||||||
|
} else {
|
||||||
|
print("find item1")
|
||||||
}
|
}
|
||||||
|
|
||||||
if let toItem = self.toViewport.anchorItem(at: fixedAnchorPoint), let toFrame = self.toViewport.frameForItem(at: toItem.index) {
|
if let toItem = self.toViewport.anchorItem(at: fixedAnchorPoint.offsetBy(dx: searchOffset, dy: 0.0)), let toFrame = self.toViewport.frameForItem(at: toItem.0.index) {
|
||||||
|
toAnchorFrame = toFrame
|
||||||
|
print("toColumn: \(toItem.1)")
|
||||||
|
|
||||||
toAnchorFrame.origin.y = toFrame.midY
|
toAnchorFrame.origin.y = toFrame.midY
|
||||||
toAnchorFrame.origin.x = toFrame.midX
|
toAnchorFrame.origin.x = toFrame.midX
|
||||||
toAnchorFrame.size.width = 0.0
|
toAnchorFrame.size.width = 0.0
|
||||||
|
} else {
|
||||||
|
print("find item2")
|
||||||
}
|
}
|
||||||
|
|
||||||
let fromAnchorPoint = CGPoint(x: fromAnchorFrame.midX, y: fromAnchorFrame.midY)
|
fromAnchorPoint = CGPoint(x: fromAnchorFrame.midX, y: fromAnchorFrame.midY)
|
||||||
let toAnchorPoint = CGPoint(x: toAnchorFrame.midX, y: toAnchorFrame.midY)
|
toAnchorPoint = CGPoint(x: toAnchorFrame.midX, y: toAnchorFrame.midY)
|
||||||
|
|
||||||
let initialFromViewportScale: CGFloat = 1.0
|
let initialFromViewportScale: CGFloat = 1.0
|
||||||
let targetFromViewportScale: CGFloat = toAnchorFrame.height / fromAnchorFrame.height
|
let targetFromViewportScale: CGFloat = toAnchorFrame.height / fromAnchorFrame.height
|
||||||
@ -1142,12 +1196,27 @@ public final class SparseItemGrid: ASDisplayNode {
|
|||||||
let initialToViewportScale: CGFloat = fromAnchorFrame.height / toAnchorFrame.height
|
let initialToViewportScale: CGFloat = fromAnchorFrame.height / toAnchorFrame.height
|
||||||
let targetToViewportScale: CGFloat = 1.0
|
let targetToViewportScale: CGFloat = 1.0
|
||||||
|
|
||||||
let fromScale = initialFromViewportScale * (1.0 - progress) + targetFromViewportScale * progress
|
fromScale = initialFromViewportScale * (1.0 - progress) + targetFromViewportScale * progress
|
||||||
let toScale = initialToViewportScale * (1.0 - progress) + targetToViewportScale * progress
|
toScale = initialToViewportScale * (1.0 - progress) + targetToViewportScale * progress
|
||||||
|
|
||||||
|
fromDeltaOffset = CGPoint(x: toAnchorPoint.x - fromAnchorPoint.x, y: toAnchorPoint.y - fromAnchorPoint.y)
|
||||||
|
|
||||||
|
if fromDeltaOffset.x > 0.0 && abs(searchOffset) < 1000.0 {
|
||||||
|
searchOffset += -4.0
|
||||||
|
//continue
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
if fromDeltaOffset.x <= 0.0 {
|
||||||
|
print("fail")
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let fromDeltaOffset = CGPoint(x: toAnchorPoint.x - fromAnchorPoint.x, y: toAnchorPoint.y - fromAnchorPoint.y)
|
|
||||||
let toDeltaOffset = CGPoint(x: -fromDeltaOffset.x, y: -fromDeltaOffset.y)
|
let toDeltaOffset = CGPoint(x: -fromDeltaOffset.x, y: -fromDeltaOffset.y)
|
||||||
|
|
||||||
|
print("direction: \(fromDeltaOffset.x < 0.0)")
|
||||||
|
|
||||||
let fromOffset = CGPoint(x: 0.0 * (1.0 - progress) + fromDeltaOffset.x * progress, y: 0.0 * (1.0 - progress) + fromDeltaOffset.y * progress)
|
let fromOffset = CGPoint(x: 0.0 * (1.0 - progress) + fromDeltaOffset.x * progress, y: 0.0 * (1.0 - progress) + fromDeltaOffset.y * progress)
|
||||||
let toOffset = CGPoint(x: toDeltaOffset.x * (1.0 - progress) + 0.0 * progress, y: toDeltaOffset.y * (1.0 - progress) + 0.0 * progress)
|
let toOffset = CGPoint(x: toDeltaOffset.x * (1.0 - progress) + 0.0 * progress, y: toDeltaOffset.y * (1.0 - progress) + 0.0 * progress)
|
||||||
|
|
||||||
@ -1295,18 +1364,18 @@ public final class SparseItemGrid: ASDisplayNode {
|
|||||||
if isZoomingIn {
|
if isZoomingIn {
|
||||||
if progress > 1.0 {
|
if progress > 1.0 {
|
||||||
nextZoomLevel = zoomLevels.increment
|
nextZoomLevel = zoomLevels.increment
|
||||||
nextScale = startScale * 1.5
|
nextScale = startScale * 1.25
|
||||||
} else {
|
} else {
|
||||||
nextZoomLevel = zoomLevels.decrement
|
nextZoomLevel = zoomLevels.decrement
|
||||||
nextScale = startScale * 0.5
|
nextScale = startScale * 0.75
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if progress > 1.0 {
|
if progress > 1.0 {
|
||||||
nextZoomLevel = zoomLevels.decrement
|
nextZoomLevel = zoomLevels.decrement
|
||||||
nextScale = startScale * 0.5
|
nextScale = startScale * 0.75
|
||||||
} else {
|
} else {
|
||||||
nextZoomLevel = zoomLevels.increment
|
nextZoomLevel = zoomLevels.increment
|
||||||
nextScale = startScale * 1.5
|
nextScale = startScale * 1.25
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1314,7 +1383,7 @@ public final class SparseItemGrid: ASDisplayNode {
|
|||||||
|
|
||||||
let nextAnchorItemIndex: Int
|
let nextAnchorItemIndex: Int
|
||||||
if let anchorItem = boundaryViewport.anchorItem(at: anchorLocation) {
|
if let anchorItem = boundaryViewport.anchorItem(at: anchorLocation) {
|
||||||
nextAnchorItemIndex = anchorItem.index
|
nextAnchorItemIndex = anchorItem.0.index
|
||||||
} else {
|
} else {
|
||||||
nextAnchorItemIndex = currentViewportTransition.anchorItemIndex
|
nextAnchorItemIndex = currentViewportTransition.anchorItemIndex
|
||||||
}
|
}
|
||||||
@ -1339,7 +1408,7 @@ public final class SparseItemGrid: ASDisplayNode {
|
|||||||
self.currentViewportTransition?.removeFromSupernode()
|
self.currentViewportTransition?.removeFromSupernode()
|
||||||
|
|
||||||
let nextInteractiveState = ViewportTransition.InteractiveState(anchorLocation: anchorLocation, initialScale: startScale, targetScale: nextScale)
|
let nextInteractiveState = ViewportTransition.InteractiveState(anchorLocation: anchorLocation, initialScale: startScale, targetScale: nextScale)
|
||||||
let currentViewportTransition = ViewportTransition(interactiveState: nextInteractiveState, layout: containerLayout, anchorItemIndex: currentViewportTransition.anchorItemIndex, from: boundaryViewport, to: nextViewport, coveringOffsetUpdated: { [weak self] transition in
|
let currentViewportTransition = ViewportTransition(interactiveState: nextInteractiveState, layout: containerLayout, anchorItemIndex: currentViewportTransition.anchorItemIndex, transitionAnchorPoint: currentViewportTransition.transitionAnchorPoint, from: boundaryViewport, to: nextViewport, coveringOffsetUpdated: { [weak self] transition in
|
||||||
self?.transitionCoveringOffsetUpdated(transition: transition)
|
self?.transitionCoveringOffsetUpdated(transition: transition)
|
||||||
})
|
})
|
||||||
currentViewportTransition.frame = CGRect(origin: CGPoint(), size: containerLayout.size)
|
currentViewportTransition.frame = CGRect(origin: CGPoint(), size: containerLayout.size)
|
||||||
@ -1365,14 +1434,14 @@ public final class SparseItemGrid: ASDisplayNode {
|
|||||||
if let previousViewport = self.currentViewport, let nextZoomLevel = nextZoomLevel {
|
if let previousViewport = self.currentViewport, let nextZoomLevel = nextZoomLevel {
|
||||||
let anchorLocation = recognizer.location(in: self.view)
|
let anchorLocation = recognizer.location(in: self.view)
|
||||||
|
|
||||||
let interactiveState = ViewportTransition.InteractiveState(anchorLocation: anchorLocation, initialScale: 1.0, targetScale: scale > 1.0 ? scale * 1.5 : scale * 0.5)
|
let interactiveState = ViewportTransition.InteractiveState(anchorLocation: anchorLocation, initialScale: 1.0, targetScale: scale > 1.0 ? scale * 1.25 : scale * 0.75)
|
||||||
|
|
||||||
var progress = (scale - interactiveState.initialScale) / (interactiveState.targetScale - interactiveState.initialScale)
|
var progress = (scale - interactiveState.initialScale) / (interactiveState.targetScale - interactiveState.initialScale)
|
||||||
progress = max(0.0, min(1.0, progress))
|
progress = max(0.0, min(1.0, progress))
|
||||||
|
|
||||||
if let anchorItem = previousViewport.anchorItem(at: anchorLocation), let anchorItemFrame = previousViewport.frameForItem(at: anchorItem.index) {
|
if let anchorItem = previousViewport.anchorItem(at: anchorLocation), let anchorItemFrame = previousViewport.frameForItem(at: anchorItem.0.index) {
|
||||||
let restoreScrollPosition: (y: CGFloat, index: Int)? = (anchorItemFrame.minY, anchorItem.index)
|
let restoreScrollPosition: (y: CGFloat, index: Int)? = (anchorItemFrame.minY, anchorItem.0.index)
|
||||||
let anchorItemIndex = anchorItem.index
|
let anchorItemIndex = anchorItem.0.index
|
||||||
|
|
||||||
let nextViewport = Viewport(theme: self.theme, zoomLevel: nextZoomLevel, maybeLoadHoleAnchor: { [weak self] holeAnchor, location in
|
let nextViewport = Viewport(theme: self.theme, zoomLevel: nextZoomLevel, maybeLoadHoleAnchor: { [weak self] holeAnchor, location in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
@ -1386,7 +1455,7 @@ public final class SparseItemGrid: ASDisplayNode {
|
|||||||
nextViewport.frame = CGRect(origin: CGPoint(), size: containerLayout.size)
|
nextViewport.frame = CGRect(origin: CGPoint(), size: containerLayout.size)
|
||||||
nextViewport.update(containerLayout: containerLayout, items: items, restoreScrollPosition: restoreScrollPosition, synchronous: .semi)
|
nextViewport.update(containerLayout: containerLayout, items: items, restoreScrollPosition: restoreScrollPosition, synchronous: .semi)
|
||||||
|
|
||||||
let currentViewportTransition = ViewportTransition(interactiveState: interactiveState, layout: containerLayout, anchorItemIndex: anchorItemIndex, from: previousViewport, to: nextViewport, coveringOffsetUpdated: { [weak self] transition in
|
let currentViewportTransition = ViewportTransition(interactiveState: interactiveState, layout: containerLayout, anchorItemIndex: anchorItemIndex, transitionAnchorPoint: anchorLocation, from: previousViewport, to: nextViewport, coveringOffsetUpdated: { [weak self] transition in
|
||||||
self?.transitionCoveringOffsetUpdated(transition: transition)
|
self?.transitionCoveringOffsetUpdated(transition: transition)
|
||||||
})
|
})
|
||||||
currentViewportTransition.frame = CGRect(origin: CGPoint(), size: containerLayout.size)
|
currentViewportTransition.frame = CGRect(origin: CGPoint(), size: containerLayout.size)
|
||||||
@ -1535,15 +1604,15 @@ public final class SparseItemGrid: ASDisplayNode {
|
|||||||
|
|
||||||
if let containerLayout = self.containerLayout, let items = self.items {
|
if let containerLayout = self.containerLayout, let items = self.items {
|
||||||
let anchorLocation = CGPoint(x: 0.0, y: 10.0)
|
let anchorLocation = CGPoint(x: 0.0, y: 10.0)
|
||||||
if let anchorItem = previousViewport.anchorItem(at: anchorLocation), let anchorItemFrame = previousViewport.frameForItem(at: anchorItem.index) {
|
if let anchorItem = previousViewport.anchorItem(at: anchorLocation), let anchorItemFrame = previousViewport.frameForItem(at: anchorItem.0.index) {
|
||||||
let restoreScrollPosition: (y: CGFloat, index: Int)? = (anchorItemFrame.minY, anchorItem.index)
|
let restoreScrollPosition: (y: CGFloat, index: Int)? = (anchorItemFrame.minY, anchorItem.0.index)
|
||||||
let anchorItemIndex = anchorItem.index
|
let anchorItemIndex = anchorItem.0.index
|
||||||
|
|
||||||
self.scrollingArea.frame = CGRect(origin: CGPoint(), size: containerLayout.size)
|
self.scrollingArea.frame = CGRect(origin: CGPoint(), size: containerLayout.size)
|
||||||
currentViewport.frame = CGRect(origin: CGPoint(), size: containerLayout.size)
|
currentViewport.frame = CGRect(origin: CGPoint(), size: containerLayout.size)
|
||||||
currentViewport.update(containerLayout: containerLayout, items: items, restoreScrollPosition: restoreScrollPosition, synchronous: .semi)
|
currentViewport.update(containerLayout: containerLayout, items: items, restoreScrollPosition: restoreScrollPosition, synchronous: .semi)
|
||||||
|
|
||||||
let currentViewportTransition = ViewportTransition(interactiveState: nil, layout: containerLayout, anchorItemIndex: anchorItemIndex, from: previousViewport, to: currentViewport, coveringOffsetUpdated: { [weak self] transition in
|
let currentViewportTransition = ViewportTransition(interactiveState: nil, layout: containerLayout, anchorItemIndex: anchorItemIndex, transitionAnchorPoint: anchorLocation, from: previousViewport, to: currentViewport, coveringOffsetUpdated: { [weak self] transition in
|
||||||
self?.transitionCoveringOffsetUpdated(transition: transition)
|
self?.transitionCoveringOffsetUpdated(transition: transition)
|
||||||
})
|
})
|
||||||
currentViewportTransition.frame = CGRect(origin: CGPoint(), size: containerLayout.size)
|
currentViewportTransition.frame = CGRect(origin: CGPoint(), size: containerLayout.size)
|
||||||
|
@ -560,6 +560,8 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
|||||||
private var contentInsetAnimator: DisplayLinkAnimator?
|
private var contentInsetAnimator: DisplayLinkAnimator?
|
||||||
|
|
||||||
private let adMessagesContext: AdMessagesHistoryContext?
|
private let adMessagesContext: AdMessagesHistoryContext?
|
||||||
|
private var preloadAdPeerId: PeerId?
|
||||||
|
private let preloadAdPeerDisposable = MetaDisposable()
|
||||||
|
|
||||||
private let clientId: Atomic<Int32>
|
private let clientId: Atomic<Int32>
|
||||||
|
|
||||||
@ -586,7 +588,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
|||||||
|
|
||||||
self.prefetchManager = InChatPrefetchManager(context: context)
|
self.prefetchManager = InChatPrefetchManager(context: context)
|
||||||
|
|
||||||
let adMessages: Signal<[Message], NoError>
|
var adMessages: Signal<[Message], NoError>
|
||||||
if case .bubbles = mode, case let .peer(peerId) = chatLocation, case .none = subject {
|
if case .bubbles = mode, case let .peer(peerId) = chatLocation, case .none = subject {
|
||||||
let adMessagesContext = context.engine.messages.adMessages(peerId: peerId)
|
let adMessagesContext = context.engine.messages.adMessages(peerId: peerId)
|
||||||
self.adMessagesContext = adMessagesContext
|
self.adMessagesContext = adMessagesContext
|
||||||
@ -602,6 +604,29 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
|||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
|
adMessages = adMessages
|
||||||
|
|> afterNext { [weak self] messages in
|
||||||
|
Queue.mainQueue().async {
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var adPeerId: PeerId?
|
||||||
|
adPeerId = nil
|
||||||
|
|
||||||
|
if strongSelf.preloadAdPeerId != adPeerId {
|
||||||
|
strongSelf.preloadAdPeerId = adPeerId
|
||||||
|
if let adPeerId = adPeerId {
|
||||||
|
let combinedDisposable = DisposableSet()
|
||||||
|
strongSelf.preloadAdPeerDisposable.set(combinedDisposable)
|
||||||
|
combinedDisposable.add(strongSelf.context.account.viewTracker.polledChannel(peerId: adPeerId).start())
|
||||||
|
combinedDisposable.add(strongSelf.context.account.addAdditionalPreloadHistoryPeerId(peerId: adPeerId))
|
||||||
|
} else {
|
||||||
|
strongSelf.preloadAdPeerDisposable.set(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.clipsToBounds = false
|
self.clipsToBounds = false
|
||||||
|
|
||||||
self.accessibilityPageScrolledString = { [weak self] row, count in
|
self.accessibilityPageScrolledString = { [weak self] row, count in
|
||||||
@ -1398,6 +1423,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
|||||||
self.interactiveReadActionDisposable?.dispose()
|
self.interactiveReadActionDisposable?.dispose()
|
||||||
self.canReadHistoryDisposable?.dispose()
|
self.canReadHistoryDisposable?.dispose()
|
||||||
self.loadedMessagesFromCachedDataDisposable?.dispose()
|
self.loadedMessagesFromCachedDataDisposable?.dispose()
|
||||||
|
self.preloadAdPeerDisposable.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
public func setLoadStateUpdated(_ f: @escaping (ChatHistoryNodeLoadState, Bool) -> Void) {
|
public func setLoadStateUpdated(_ f: @escaping (ChatHistoryNodeLoadState, Bool) -> Void) {
|
||||||
|
@ -30,10 +30,21 @@ private final class GalleryHiddenMediaTargetHolder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final class GalleryHiddenMediaManagerImpl: GalleryHiddenMediaManager {
|
final class GalleryHiddenMediaManagerImpl: GalleryHiddenMediaManager {
|
||||||
|
private final class SourceContext {
|
||||||
|
let disposable: Disposable
|
||||||
|
var state: (GalleryHiddenMediaId, Int32)? = nil
|
||||||
|
|
||||||
|
init(disposable: Disposable) {
|
||||||
|
self.disposable = disposable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var sources = Bag<Void>()
|
||||||
|
private var sourceContexts: [Int: SourceContext] = [:]
|
||||||
|
|
||||||
private var nextId: Int32 = 0
|
private var nextId: Int32 = 0
|
||||||
private var contexts: [GalleryHiddenMediaId: GalleryHiddenMediaContext] = [:]
|
private var contexts: [GalleryHiddenMediaId: GalleryHiddenMediaContext] = [:]
|
||||||
|
|
||||||
private var sourcesDisposables = Bag<Disposable>()
|
|
||||||
private var subscribers = Bag<(Set<GalleryHiddenMediaId>) -> Void>()
|
private var subscribers = Bag<(Set<GalleryHiddenMediaId>) -> Void>()
|
||||||
|
|
||||||
private var targets: [GalleryHiddenMediaTargetHolder] = []
|
private var targets: [GalleryHiddenMediaTargetHolder] = []
|
||||||
@ -86,8 +97,29 @@ final class GalleryHiddenMediaManagerImpl: GalleryHiddenMediaManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func addSource(_ signal: Signal<GalleryHiddenMediaId?, NoError>) -> Int {
|
func addSource(_ signal: Signal<GalleryHiddenMediaId?, NoError>) -> Int {
|
||||||
var state: (GalleryHiddenMediaId, Int32)?
|
let index = self.sources.add(Void())
|
||||||
let index = self.sourcesDisposables.add((signal |> deliverOnMainQueue).start(next: { [weak self] id in
|
let disposable = MetaDisposable()
|
||||||
|
let context = SourceContext(disposable: disposable)
|
||||||
|
self.sourceContexts[index] = context
|
||||||
|
|
||||||
|
disposable.set((signal |> deliverOnMainQueue).start(next: { [weak self, weak context] id in
|
||||||
|
guard let strongSelf = self, let context = context else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if id != context.state?.0 {
|
||||||
|
if let (previousId, previousIndex) = context.state {
|
||||||
|
strongSelf.removeHiddenMedia(id: previousId, index: previousIndex)
|
||||||
|
context.state = nil
|
||||||
|
}
|
||||||
|
if let id = id {
|
||||||
|
context.state = (id, strongSelf.addHiddenMedia(id: id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
return index
|
||||||
|
|
||||||
|
/*let index = self.sourcesDisposables.add((signal |> deliverOnMainQueue).start(next: { [weak self] id in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
if id != state?.0 {
|
if id != state?.0 {
|
||||||
if let (previousId, previousIndex) = state {
|
if let (previousId, previousIndex) = state {
|
||||||
@ -100,13 +132,17 @@ final class GalleryHiddenMediaManagerImpl: GalleryHiddenMediaManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
return index
|
return index*/
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeSource(_ index: Int) {
|
func removeSource(_ index: Int) {
|
||||||
if let disposable = self.sourcesDisposables.get(index) {
|
self.sources.remove(index)
|
||||||
self.sourcesDisposables.remove(index)
|
|
||||||
disposable.dispose()
|
if let context = self.sourceContexts.removeValue(forKey: index) {
|
||||||
|
context.disposable.dispose()
|
||||||
|
if let (previousId, previousIndex) = context.state {
|
||||||
|
self.removeHiddenMedia(id: previousId, index: previousIndex)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ import AsyncDisplayKit
|
|||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
import Postbox
|
import Postbox
|
||||||
import AccountContext
|
import AccountContext
|
||||||
|
import AVKit
|
||||||
|
|
||||||
public final class OverlayMediaControllerImpl: ViewController, OverlayMediaController {
|
public final class OverlayMediaControllerImpl: ViewController, OverlayMediaController {
|
||||||
private var controllerNode: OverlayMediaControllerNode {
|
private var controllerNode: OverlayMediaControllerNode {
|
||||||
@ -14,6 +15,9 @@ public final class OverlayMediaControllerImpl: ViewController, OverlayMediaContr
|
|||||||
public var updatePossibleEmbeddingItem: ((OverlayMediaControllerEmbeddingItem?) -> Void)?
|
public var updatePossibleEmbeddingItem: ((OverlayMediaControllerEmbeddingItem?) -> Void)?
|
||||||
public var embedPossibleEmbeddingItem: ((OverlayMediaControllerEmbeddingItem) -> Bool)?
|
public var embedPossibleEmbeddingItem: ((OverlayMediaControllerEmbeddingItem) -> Bool)?
|
||||||
|
|
||||||
|
private var pictureInPictureContainer: ASDisplayNode?
|
||||||
|
private var pictureInPictureContent: PictureInPictureContent?
|
||||||
|
|
||||||
public init() {
|
public init() {
|
||||||
super.init(navigationBarPresentationData: nil)
|
super.init(navigationBarPresentationData: nil)
|
||||||
|
|
||||||
@ -45,6 +49,31 @@ public final class OverlayMediaControllerImpl: ViewController, OverlayMediaContr
|
|||||||
self.controllerNode.removeNode(node, customTransition: customTransition)
|
self.controllerNode.removeNode(node, customTransition: customTransition)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func setPictureInPictureContent(content: PictureInPictureContent, absoluteRect: CGRect) {
|
||||||
|
if self.pictureInPictureContainer == nil {
|
||||||
|
let pictureInPictureContainer = ASDisplayNode()
|
||||||
|
pictureInPictureContainer.clipsToBounds = false
|
||||||
|
self.pictureInPictureContainer = pictureInPictureContainer
|
||||||
|
self.controllerNode.addSubnode(pictureInPictureContainer)
|
||||||
|
}
|
||||||
|
self.pictureInPictureContainer?.clipsToBounds = false
|
||||||
|
self.pictureInPictureContent = content
|
||||||
|
self.pictureInPictureContainer?.addSubnode(content.videoNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func setPictureInPictureContentHidden(content: PictureInPictureContent, isHidden value: Bool) {
|
||||||
|
if self.pictureInPictureContent === content {
|
||||||
|
self.pictureInPictureContainer?.clipsToBounds = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func removePictureInPictureContent(content: PictureInPictureContent) {
|
||||||
|
if self.pictureInPictureContent === content {
|
||||||
|
self.pictureInPictureContent = nil
|
||||||
|
content.videoNode.removeFromSupernode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||||
super.containerLayoutUpdated(layout, transition: transition)
|
super.containerLayoutUpdated(layout, transition: transition)
|
||||||
|
|
||||||
|
@ -454,4 +454,8 @@ private final class NativeVideoContentNode: ASDisplayNode, UniversalVideoContent
|
|||||||
|
|
||||||
func notifyPlaybackControlsHidden(_ hidden: Bool) {
|
func notifyPlaybackControlsHidden(_ hidden: Bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setCanPlaybackWithoutHierarchy(_ canPlaybackWithoutHierarchy: Bool) {
|
||||||
|
self.playerNode.setCanPlaybackWithoutHierarchy(canPlaybackWithoutHierarchy)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,8 +7,9 @@ import TelegramCore
|
|||||||
import Postbox
|
import Postbox
|
||||||
import TelegramAudio
|
import TelegramAudio
|
||||||
import AccountContext
|
import AccountContext
|
||||||
|
import AVKit
|
||||||
|
|
||||||
public final class OverlayUniversalVideoNode: OverlayMediaItemNode {
|
public final class OverlayUniversalVideoNode: OverlayMediaItemNode, AVPictureInPictureSampleBufferPlaybackDelegate {
|
||||||
public let content: UniversalVideoContent
|
public let content: UniversalVideoContent
|
||||||
private let videoNode: UniversalVideoNode
|
private let videoNode: UniversalVideoNode
|
||||||
private let decoration: OverlayVideoDecoration
|
private let decoration: OverlayVideoDecoration
|
||||||
@ -179,4 +180,35 @@ public final class OverlayUniversalVideoNode: OverlayMediaItemNode {
|
|||||||
public func controlPlay() {
|
public func controlPlay() {
|
||||||
self.videoNode.play()
|
self.videoNode.play()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(iOSApplicationExtension 15.0, iOS 15.0, *)
|
||||||
|
override public func makeNativeContentSource() -> AVPictureInPictureController.ContentSource? {
|
||||||
|
guard let videoLayer = self.videoNode.getVideoLayer() else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return AVPictureInPictureController.ContentSource(sampleBufferDisplayLayer: videoLayer, playbackDelegate: self)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, setPlaying playing: Bool) {
|
||||||
|
self.controlPlay()
|
||||||
|
}
|
||||||
|
|
||||||
|
public func pictureInPictureControllerTimeRangeForPlayback(_ pictureInPictureController: AVPictureInPictureController) -> CMTimeRange {
|
||||||
|
return CMTimeRange(start: CMTime(seconds: 0.0, preferredTimescale: CMTimeScale(30.0)), duration: CMTime(seconds: 10.0, preferredTimescale: CMTimeScale(30.0)))
|
||||||
|
}
|
||||||
|
|
||||||
|
public func pictureInPictureControllerIsPlaybackPaused(_ pictureInPictureController: AVPictureInPictureController) -> Bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
public func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, didTransitionToRenderSize newRenderSize: CMVideoDimensions) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, skipByInterval skipInterval: CMTime, completion completionHandler: @escaping () -> Void) {
|
||||||
|
completionHandler()
|
||||||
|
}
|
||||||
|
|
||||||
|
public func pictureInPictureControllerShouldProhibitBackgroundAudioPlayback(_ pictureInPictureController: AVPictureInPictureController) -> Bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -450,4 +450,7 @@ private final class PlatformVideoContentNode: ASDisplayNode, UniversalVideoConte
|
|||||||
|
|
||||||
func notifyPlaybackControlsHidden(_ hidden: Bool) {
|
func notifyPlaybackControlsHidden(_ hidden: Bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setCanPlaybackWithoutHierarchy(_ canPlaybackWithoutHierarchy: Bool) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -289,5 +289,8 @@ private final class SystemVideoContentNode: ASDisplayNode, UniversalVideoContent
|
|||||||
|
|
||||||
func notifyPlaybackControlsHidden(_ hidden: Bool) {
|
func notifyPlaybackControlsHidden(_ hidden: Bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setCanPlaybackWithoutHierarchy(_ canPlaybackWithoutHierarchy: Bool) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -224,4 +224,7 @@ final class WebEmbedPlayerNode: ASDisplayNode, WKNavigationDelegate {
|
|||||||
self.webView.isUserInteractionEnabled = !hidden
|
self.webView.isUserInteractionEnabled = !hidden
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setCanPlaybackWithoutHierarchy(_ canPlaybackWithoutHierarchy: Bool) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -188,4 +188,7 @@ final class WebEmbedVideoContentNode: ASDisplayNode, UniversalVideoContentNode {
|
|||||||
func notifyPlaybackControlsHidden(_ hidden: Bool) {
|
func notifyPlaybackControlsHidden(_ hidden: Bool) {
|
||||||
self.playerNode.notifyPlaybackControlsHidden(hidden)
|
self.playerNode.notifyPlaybackControlsHidden(hidden)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setCanPlaybackWithoutHierarchy(_ canPlaybackWithoutHierarchy: Bool) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -333,7 +333,7 @@ class WebSearchGalleryController: ViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.galleryNode.animateIn(animateContent: !nodeAnimatesItself)
|
self.galleryNode.animateIn(animateContent: !nodeAnimatesItself, useSimpleAnimation: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user