This commit is contained in:
Peter 2019-09-04 00:00:49 +04:00
parent c31e0372f8
commit c9aa7d70d6
12 changed files with 589 additions and 120 deletions

View File

@ -63,9 +63,20 @@ static int64_t makePeerId(int32_t namespace, int32_t value) {
return self;
}
- (void)completeWithContent:(UNNotificationContent * _Nonnull)content {
/*NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"org.telegram.Telegram-iOS.background"];
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
NSURLSessionDownloadTask *task = [session downloadTaskWithURL:[NSURL URLWithString:@"https://telegram.org"]];
[task resume];*/
if (_contentHandler) {
_contentHandler(content);
}
}
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
if (_rootPath == nil) {
contentHandler(request.content);
[self completeWithContent:request.content];
return;
}
@ -289,8 +300,8 @@ static int64_t makePeerId(int32_t namespace, int32_t value) {
_bestAttemptContent.attachments = @[attachment];
}
}
if (_contentHandler && _bestAttemptContent != nil) {
_contentHandler(_bestAttemptContent);
if (_bestAttemptContent != nil) {
[self completeWithContent:_bestAttemptContent];
}
} else {
BuildConfig *buildConfig = [[BuildConfig alloc] initWithBaseAppBundleId:_baseAppBundleId];
@ -322,21 +333,21 @@ static int64_t makePeerId(int32_t namespace, int32_t value) {
}
}
if (strongSelf->_contentHandler && strongSelf->_bestAttemptContent != nil) {
strongSelf->_contentHandler(strongSelf->_bestAttemptContent);
if (strongSelf->_bestAttemptContent != nil) {
[strongSelf completeWithContent:strongSelf->_bestAttemptContent];
}
}
});
});
}
} else {
if (_contentHandler && _bestAttemptContent != nil) {
_contentHandler(_bestAttemptContent);
if (_bestAttemptContent != nil) {
[self completeWithContent:_bestAttemptContent];
}
}
} else {
if (_contentHandler && _bestAttemptContent != nil) {
_contentHandler(_bestAttemptContent);
if (_bestAttemptContent != nil) {
[self completeWithContent:_bestAttemptContent];
}
}
}
@ -349,7 +360,7 @@ static int64_t makePeerId(int32_t namespace, int32_t value) {
if (_contentHandler) {
if(_bestAttemptContent) {
_contentHandler(_bestAttemptContent);
[self completeWithContent:_bestAttemptContent];
_bestAttemptContent = nil;
}
_contentHandler = nil;

View File

@ -15,6 +15,7 @@ import TelegramNotices
import SearchUI
import DeleteChatPeerActionSheetItem
import LanguageSuggestionUI
import ContextUI
public func useSpecialTabBarIcons() -> Bool {
return (Date(timeIntervalSince1970: 1545642000)...Date(timeIntervalSince1970: 1546387200)).contains(Date())
@ -64,6 +65,27 @@ private func fixListNodeScrolling(_ listNode: ListView, searchNode: NavigationBa
return false
}
private final class ContextControllerContentSourceImpl: ContextControllerContentSource {
let controller: ViewController
weak var sourceNode: ASDisplayNode?
init(controller: ViewController, sourceNode: ASDisplayNode?) {
self.controller = controller
self.sourceNode = sourceNode
}
func transitionInfo() -> ContextControllerTakeControllerInfo? {
let sourceNode = self.sourceNode
return ContextControllerTakeControllerInfo(contentAreaInScreenSpace: CGRect(origin: CGPoint(), size: CGSize(width: 10.0, height: 10.0)), sourceNode: { [weak sourceNode] in
if let sourceNode = sourceNode {
return (sourceNode, sourceNode.bounds)
} else {
return nil
}
})
}
}
public class ChatListControllerImpl: TelegramBaseController, ChatListController, UIViewControllerPreviewingDelegate {
private var validLayout: ContainerViewLayout?
@ -356,6 +378,19 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
preconditionFailure("debug tap")
}
}
self.customPresentPreviewingController = { [weak self] controller, sourceNode in
guard let strongSelf = self else {
return nil
}
var items: [ContextMenuItem] = []
for i in 0 ..< 5 {
items.append(.action(ContextMenuActionItem(text: "Item \(i)", icon: { _ in nil }, action: { controller, f in
f(.default)
})))
}
return ContextController(account: strongSelf.context.account, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, source: .controller(ContextControllerContentSourceImpl(controller: controller, sourceNode: sourceNode)), items: items, reactionItems: [])
}
}
required public init(coder aDecoder: NSCoder) {
@ -1068,6 +1103,13 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
return true
})
}
if #available(iOSApplicationExtension 9.0, iOS 9.0, *) {
if !self.didSetup3dTouch {
self.didSetup3dTouch = true
self.registerForPreviewingNonNative(with: self, sourceView: self.view, theme: PeekControllerTheme(presentationTheme: self.presentationData.theme))
}
}
}
override public func viewWillDisappear(_ animated: Bool) {

View File

@ -9,7 +9,7 @@ final class ContextContentContainerNode: ASDisplayNode {
super.init()
}
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
func updateLayout(size: CGSize, scaledSize: CGSize, transition: ContainedViewLayoutTransition) {
guard let contentNode = self.contentNode else {
return
}
@ -17,6 +17,9 @@ final class ContextContentContainerNode: ASDisplayNode {
case .extracted:
break
case let .controller(controller):
transition.updatePosition(node: controller, position: CGPoint(x: scaledSize.width / 2.0, y: scaledSize.height / 2.0))
transition.updateBounds(node: controller, bounds: CGRect(origin: CGPoint(), size: size))
transition.updateTransformScale(node: controller, scale: scaledSize.width / size.width)
controller.updateLayout(size: size, transition: transition)
}
}

View File

@ -38,7 +38,8 @@ final class ContextControllerContentNode: ASDisplayNode {
}
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
controller.containerLayoutUpdated(ContainerViewLayout(size: size, metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact), deviceMetrics: .iPhoneX, intrinsicInsets: UIEdgeInsets(), safeInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: false), transition: transition)
transition.updateFrame(node: self.controller.displayNode, frame: CGRect(origin: CGPoint(), size: size))
self.controller.containerLayoutUpdated(ContainerViewLayout(size: size, metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact), deviceMetrics: .iPhoneX, intrinsicInsets: UIEdgeInsets(), safeInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: false), transition: transition)
}
}

View File

@ -339,7 +339,25 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
self.clippingNode.layer.animateBoundsOriginYAdditive(from: updatedContentAreaInScreenSpace.minY, to: 0.0, duration: 0.18 * animationDurationFactor, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue)
}
case let .controller(source):
break
let contentParentNode = ContextControllerContentNode(controller: source.controller)
self.contentContainerNode.contentNode = .controller(contentParentNode)
self.contentContainerNode.clipsToBounds = true
self.contentContainerNode.cornerRadius = 14.0
self.contentContainerNode.addSubnode(contentParentNode)
let transitionInfo = source.transitionInfo()
if let transitionInfo = transitionInfo, let (sourceNode, sourceNodeRect) = transitionInfo.sourceNode() {
let projectedFrame = convertFrame(sourceNodeRect, from: sourceNode.view, to: self.view)
self.originalProjectedContentViewFrame = (projectedFrame, projectedFrame)
var updatedContentAreaInScreenSpace = transitionInfo.contentAreaInScreenSpace
updatedContentAreaInScreenSpace.origin.x = 0.0
updatedContentAreaInScreenSpace.size.width = self.bounds.width
self.contentAreaInScreenSpace = updatedContentAreaInScreenSpace
//self.clippingNode.layer.animateFrame(from: updatedContentAreaInScreenSpace, to: self.clippingNode.frame, duration: 0.18 * animationDurationFactor, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue)
//self.clippingNode.layer.animateBoundsOriginYAdditive(from: updatedContentAreaInScreenSpace.minY, to: 0.0, duration: 0.18 * animationDurationFactor, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue)
}
}
if let validLayout = self.validLayout {
@ -375,14 +393,15 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
self?.didCompleteAnimationIn = true
})
}
self.actionsContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2 * animationDurationFactor)
let springDuration: Double = 0.42 * animationDurationFactor
let springDamping: CGFloat = 104.0
self.actionsContainerNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: springDuration, initialVelocity: 0.0, damping: springDamping)
if let contentNode = self.contentContainerNode.contentNode {
switch contentNode {
case let .extracted(extracted):
self.actionsContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2 * animationDurationFactor)
self.actionsContainerNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: springDuration, initialVelocity: 0.0, damping: springDamping)
if let originalProjectedContentViewFrame = self.originalProjectedContentViewFrame {
let contentParentNode = extracted
let localSourceFrame = self.view.convert(originalProjectedContentViewFrame.1, to: self.scrollNode.view)
@ -396,8 +415,23 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
self.contentContainerNode.layer.animateSpring(from: NSValue(cgPoint: contentContainerOffset), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: springDuration, initialVelocity: 0.0, damping: springDamping, additive: true)
contentParentNode.applyAbsoluteOffsetSpring?(-contentContainerOffset.y, springDuration, springDamping)
}
case let .controller(controller):
break
case .controller:
self.actionsContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2 * animationDurationFactor)
self.actionsContainerNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: springDuration, initialVelocity: 0.0, damping: springDamping)
self.contentContainerNode.allowsGroupOpacity = true
self.contentContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2 * animationDurationFactor, completion: { [weak self] _ in
self?.contentContainerNode.allowsGroupOpacity = false
})
self.contentContainerNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: springDuration, initialVelocity: 0.0, damping: springDamping)
if let originalProjectedContentViewFrame = self.originalProjectedContentViewFrame {
let actionsSideInset: CGFloat = 11.0
let localSourceFrame = self.view.convert(CGRect(origin: CGPoint(x: actionsSideInset, y: originalProjectedContentViewFrame.1.minY), size: CGSize(width: actionsSideInset, height: originalProjectedContentViewFrame.1.height)), to: self.scrollNode.view)
self.actionsContainerNode.layer.animateSpring(from: NSValue(cgPoint: CGPoint(x: localSourceFrame.center.x - self.actionsContainerNode.position.x, y: localSourceFrame.center.y - self.actionsContainerNode.position.y)), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: springDuration, initialVelocity: 0.0, damping: springDamping, additive: true)
let contentContainerOffset = CGPoint(x: localSourceFrame.center.x - self.contentContainerNode.frame.center.x, y: localSourceFrame.center.y - self.contentContainerNode.frame.center.y)
self.contentContainerNode.layer.animateSpring(from: NSValue(cgPoint: contentContainerOffset), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: springDuration, initialVelocity: 0.0, damping: springDamping, additive: true)
}
}
}
}
@ -410,6 +444,10 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
switch self.source {
case let .extracted(source):
guard let maybeContentNode = self.contentContainerNode.contentNode, case let .extracted(contentParentNode) = maybeContentNode else {
return
}
let putBackInfo = source.putBack()
if putBackInfo == nil {
@ -438,7 +476,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
var completedContentNode = false
var completedActionsNode = false
if let putBackInfo = putBackInfo, let contentParentNode = self.contentParentNode, let parentSupernode = contentParentNode.supernode {
if let putBackInfo = putBackInfo, let parentSupernode = contentParentNode.supernode {
self.originalProjectedContentViewFrame = (convertFrame(contentParentNode.frame, from: parentSupernode.view, to: self.view), convertFrame(contentParentNode.contentRect, from: contentParentNode.view, to: self.view))
var updatedContentAreaInScreenSpace = putBackInfo.contentAreaInScreenSpace
@ -449,9 +487,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
self.clippingNode.layer.animateBoundsOriginYAdditive(from: 0.0, to: updatedContentAreaInScreenSpace.minY, duration: transitionDuration * animationDurationFactor, timingFunction: transitionCurve.timingFunction, removeOnCompletion: false)
}
let contentParentNode = self.contentParentNode
contentParentNode?.willUpdateIsExtractedToContextPreview?(false)
contentParentNode.willUpdateIsExtractedToContextPreview?(false)
let intermediateCompletion: () -> Void = { [weak contentParentNode] in
if completedEffect && completedContentNode && completedActionsNode {
@ -518,7 +554,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
animateOutToItem = false
}
if animateOutToItem, let originalProjectedContentViewFrame = self.originalProjectedContentViewFrame, let contentParentNode = self.contentParentNode {
if animateOutToItem, let originalProjectedContentViewFrame = self.originalProjectedContentViewFrame {
let localSourceFrame = self.view.convert(originalProjectedContentViewFrame.1, to: self.scrollNode.view)
self.actionsContainerNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: localSourceFrame.center.x - self.actionsContainerNode.position.x, y: localSourceFrame.center.y - self.actionsContainerNode.position.y), duration: transitionDuration * animationDurationFactor, timingFunction: transitionCurve.timingFunction, removeOnCompletion: false, additive: true)
let contentContainerOffset = CGPoint(x: localSourceFrame.center.x - self.contentContainerNode.frame.center.x - contentParentNode.contentRect.minX, y: localSourceFrame.center.y - self.contentContainerNode.frame.center.y - contentParentNode.contentRect.minY)
@ -532,7 +568,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
if let reactionContextNode = self.reactionContextNode {
reactionContextNode.animateOut(to: CGRect(origin: CGPoint(x: originalProjectedContentViewFrame.1.minX, y: originalProjectedContentViewFrame.1.minY), size: contentParentNode.contentRect.size), animatingOutToReaction: self.reactionContextNodeIsAnimatingOut)
}
} else if let contentParentNode = self.contentParentNode {
} else {
if let snapshotView = contentParentNode.contentNode.view.snapshotContentTree() {
self.contentContainerNode.view.addSubview(snapshotView)
}
@ -553,7 +589,137 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
}
}
case let .controller(source):
break
guard let maybeContentNode = self.contentContainerNode.contentNode, case let .controller(controller) = maybeContentNode else {
return
}
let transitionInfo = source.transitionInfo()
if transitionInfo == nil {
result = .dismissWithoutContent
}
switch result {
case let .custom(value):
switch value {
case let .animated(duration, curve):
transitionDuration = duration
transitionCurve = curve
default:
break
}
default:
break
}
self.isUserInteractionEnabled = false
self.isAnimatingOut = true
self.scrollNode.view.setContentOffset(self.scrollNode.view.contentOffset, animated: false)
var completedEffect = false
var completedContentNode = false
var completedActionsNode = false
if let transitionInfo = transitionInfo, let (sourceNode, sourceNodeRect) = transitionInfo.sourceNode() {
let projectedFrame = convertFrame(sourceNodeRect, from: sourceNode.view, to: self.view)
self.originalProjectedContentViewFrame = (projectedFrame, projectedFrame)
var updatedContentAreaInScreenSpace = transitionInfo.contentAreaInScreenSpace
updatedContentAreaInScreenSpace.origin.x = 0.0
updatedContentAreaInScreenSpace.size.width = self.bounds.width
}
let intermediateCompletion: () -> Void = {
if completedEffect && completedContentNode && completedActionsNode {
switch result {
case .default, .custom:
break
case .dismissWithoutContent:
break
}
completion()
}
}
if #available(iOS 10.0, *) {
if let propertyAnimator = self.propertyAnimator {
let propertyAnimator = propertyAnimator as? UIViewPropertyAnimator
propertyAnimator?.stopAnimation(true)
}
self.propertyAnimator = UIViewPropertyAnimator(duration: transitionDuration, curve: .easeInOut, animations: { [weak self] in
self?.effectView.effect = nil
})
}
if let _ = self.propertyAnimator {
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
self.displayLinkAnimator = DisplayLinkAnimator(duration: 0.2 * animationDurationFactor, from: 0.0, to: 0.999, update: { [weak self] value in
(self?.propertyAnimator as? UIViewPropertyAnimator)?.fractionComplete = value
}, completion: {
completedEffect = true
intermediateCompletion()
})
}
self.effectView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.05 * animationDurationFactor, delay: 0.15, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false)
} else {
UIView.animate(withDuration: 0.21 * animationDurationFactor, animations: {
if #available(iOS 9.0, *) {
self.effectView.effect = nil
} else {
self.effectView.alpha = 0.0
}
}, completion: { _ in
completedEffect = true
intermediateCompletion()
})
}
self.dimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: transitionDuration * animationDurationFactor, removeOnCompletion: false)
self.actionsContainerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2 * animationDurationFactor, removeOnCompletion: false, completion: { _ in
completedActionsNode = true
intermediateCompletion()
})
self.contentContainerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2 * animationDurationFactor, removeOnCompletion: false, completion: { _ in
})
self.actionsContainerNode.layer.animateScale(from: 1.0, to: 0.1, duration: transitionDuration * animationDurationFactor, timingFunction: transitionCurve.timingFunction, removeOnCompletion: false)
self.contentContainerNode.layer.animateScale(from: 1.0, to: 0.01, duration: transitionDuration * animationDurationFactor, timingFunction: transitionCurve.timingFunction, removeOnCompletion: false)
let animateOutToItem: Bool
switch result {
case .default, .custom:
animateOutToItem = true
case .dismissWithoutContent:
animateOutToItem = false
}
if animateOutToItem, let originalProjectedContentViewFrame = self.originalProjectedContentViewFrame {
let actionsSideInset: CGFloat = 11.0
let localSourceFrame = self.view.convert(CGRect(origin: CGPoint(x: actionsSideInset, y: originalProjectedContentViewFrame.1.minY), size: CGSize(width: actionsSideInset, height: originalProjectedContentViewFrame.1.height)), to: self.scrollNode.view)
self.actionsContainerNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: localSourceFrame.center.x - self.actionsContainerNode.position.x, y: localSourceFrame.center.y - self.actionsContainerNode.position.y), duration: transitionDuration * animationDurationFactor, timingFunction: transitionCurve.timingFunction, removeOnCompletion: false, additive: true)
let contentContainerOffset = CGPoint(x: localSourceFrame.center.x - self.contentContainerNode.frame.center.x, y: localSourceFrame.center.y - self.contentContainerNode.frame.center.y)
self.contentContainerNode.layer.animatePosition(from: CGPoint(), to: contentContainerOffset, duration: transitionDuration * animationDurationFactor, timingFunction: transitionCurve.timingFunction, removeOnCompletion: false, additive: true, completion: { _ in
completedContentNode = true
intermediateCompletion()
})
} else {
if let snapshotView = controller.view.snapshotContentTree() {
self.contentContainerNode.view.addSubview(snapshotView)
}
self.contentContainerNode.allowsGroupOpacity = true
self.contentContainerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: transitionDuration * animationDurationFactor, removeOnCompletion: false, completion: { _ in
completedContentNode = true
intermediateCompletion()
})
if let reactionContextNode = self.reactionContextNode {
reactionContextNode.animateOut(to: nil, animatingOutToReaction: self.reactionContextNodeIsAnimatingOut)
}
}
}
}
@ -646,55 +812,115 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
}
let actionsBottomInset: CGFloat = 11.0
if let originalProjectedContentViewFrame = self.originalProjectedContentViewFrame, let contentParentNode = self.contentParentNode {
let isInitialLayout = self.actionsContainerNode.frame.size.width.isZero
let previousContainerFrame = self.view.convert(self.contentContainerNode.frame, from: self.scrollNode.view)
let actionsSize = self.actionsContainerNode.updateLayout(constrainedWidth: layout.size.width - actionsSideInset * 2.0, transition: actionsContainerTransition)
let contentSize = originalProjectedContentViewFrame.1.size
self.contentContainerNode.updateLayout(size: contentSize, transition: transition)
let maximumActionsFrameOrigin = max(60.0, layout.size.height - layout.intrinsicInsets.bottom - actionsBottomInset - actionsSize.height)
var originalActionsFrame = CGRect(origin: CGPoint(x: max(actionsSideInset, min(layout.size.width - actionsSize.width - actionsSideInset, originalProjectedContentViewFrame.1.minX)), y: min(originalProjectedContentViewFrame.1.maxY + contentActionsSpacing, maximumActionsFrameOrigin)), size: actionsSize)
var originalContentFrame = CGRect(origin: CGPoint(x: originalProjectedContentViewFrame.1.minX, y: originalActionsFrame.minY - contentActionsSpacing - originalProjectedContentViewFrame.1.size.height), size: originalProjectedContentViewFrame.1.size)
let topEdge = max(contentTopInset, self.contentAreaInScreenSpace?.minY ?? 0.0)
if originalContentFrame.minY < topEdge {
let requiredOffset = topEdge - originalContentFrame.minY
let availableOffset = max(0.0, layout.size.height - layout.intrinsicInsets.bottom - actionsBottomInset - originalActionsFrame.maxY)
let offset = min(requiredOffset, availableOffset)
originalActionsFrame = originalActionsFrame.offsetBy(dx: 0.0, dy: offset)
originalContentFrame = originalContentFrame.offsetBy(dx: 0.0, dy: offset)
}
let contentHeight = max(layout.size.height, max(layout.size.height, originalActionsFrame.maxY + actionsBottomInset) - originalContentFrame.minY + contentTopInset)
let scrollContentSize = CGSize(width: layout.size.width, height: contentHeight)
if self.scrollNode.view.contentSize != scrollContentSize {
self.scrollNode.view.contentSize = scrollContentSize
}
let overflowOffset = min(0.0, originalContentFrame.minY - contentTopInset)
let contentContainerFrame = originalContentFrame.offsetBy(dx: -contentParentNode.contentRect.minX, dy: -overflowOffset - contentParentNode.contentRect.minY)
transition.updateFrame(node: self.contentContainerNode, frame: contentContainerFrame)
actionsContainerTransition.updateFrame(node: self.actionsContainerNode, frame: originalActionsFrame.offsetBy(dx: 0.0, dy: -overflowOffset))
if isInitialLayout {
self.scrollNode.view.contentOffset = CGPoint(x: 0.0, y: -overflowOffset)
let currentContainerFrame = self.view.convert(self.contentContainerNode.frame, from: self.scrollNode.view)
if overflowOffset < 0.0 {
transition.animateOffsetAdditive(node: self.scrollNode, offset: currentContainerFrame.minY - previousContainerFrame.minY)
if let contentNode = self.contentContainerNode.contentNode {
switch contentNode {
case let .extracted(contentParentNode):
if let originalProjectedContentViewFrame = self.originalProjectedContentViewFrame {
let isInitialLayout = self.actionsContainerNode.frame.size.width.isZero
let previousContainerFrame = self.view.convert(self.contentContainerNode.frame, from: self.scrollNode.view)
let actionsSize = self.actionsContainerNode.updateLayout(constrainedWidth: layout.size.width - actionsSideInset * 2.0, transition: actionsContainerTransition)
let contentSize = originalProjectedContentViewFrame.1.size
self.contentContainerNode.updateLayout(size: contentSize, scaledSize: contentSize, transition: transition)
let maximumActionsFrameOrigin = max(60.0, layout.size.height - layout.intrinsicInsets.bottom - actionsBottomInset - actionsSize.height)
var originalActionsFrame = CGRect(origin: CGPoint(x: max(actionsSideInset, min(layout.size.width - actionsSize.width - actionsSideInset, originalProjectedContentViewFrame.1.minX)), y: min(originalProjectedContentViewFrame.1.maxY + contentActionsSpacing, maximumActionsFrameOrigin)), size: actionsSize)
var originalContentFrame = CGRect(origin: CGPoint(x: originalProjectedContentViewFrame.1.minX, y: originalActionsFrame.minY - contentActionsSpacing - originalProjectedContentViewFrame.1.size.height), size: originalProjectedContentViewFrame.1.size)
let topEdge = max(contentTopInset, self.contentAreaInScreenSpace?.minY ?? 0.0)
if originalContentFrame.minY < topEdge {
let requiredOffset = topEdge - originalContentFrame.minY
let availableOffset = max(0.0, layout.size.height - layout.intrinsicInsets.bottom - actionsBottomInset - originalActionsFrame.maxY)
let offset = min(requiredOffset, availableOffset)
originalActionsFrame = originalActionsFrame.offsetBy(dx: 0.0, dy: offset)
originalContentFrame = originalContentFrame.offsetBy(dx: 0.0, dy: offset)
}
let contentHeight = max(layout.size.height, max(layout.size.height, originalActionsFrame.maxY + actionsBottomInset) - originalContentFrame.minY + contentTopInset)
let scrollContentSize = CGSize(width: layout.size.width, height: contentHeight)
if self.scrollNode.view.contentSize != scrollContentSize {
self.scrollNode.view.contentSize = scrollContentSize
}
let overflowOffset = min(0.0, originalContentFrame.minY - contentTopInset)
let contentContainerFrame = originalContentFrame.offsetBy(dx: -contentParentNode.contentRect.minX, dy: -overflowOffset - contentParentNode.contentRect.minY)
transition.updateFrame(node: self.contentContainerNode, frame: contentContainerFrame)
actionsContainerTransition.updateFrame(node: self.actionsContainerNode, frame: originalActionsFrame.offsetBy(dx: 0.0, dy: -overflowOffset))
if isInitialLayout {
self.scrollNode.view.contentOffset = CGPoint(x: 0.0, y: -overflowOffset)
let currentContainerFrame = self.view.convert(self.contentContainerNode.frame, from: self.scrollNode.view)
if overflowOffset < 0.0 {
transition.animateOffsetAdditive(node: self.scrollNode, offset: currentContainerFrame.minY - previousContainerFrame.minY)
}
}
let absoluteContentRect = contentContainerFrame.offsetBy(dx: 0.0, dy: -self.scrollNode.view.contentOffset.y)
contentParentNode.updateAbsoluteRect?(absoluteContentRect, layout.size)
if let reactionContextNode = self.reactionContextNode {
let insets = layout.insets(options: [.statusBar])
transition.updateFrame(node: reactionContextNode, frame: CGRect(origin: CGPoint(), size: layout.size))
reactionContextNode.updateLayout(size: layout.size, insets: insets, anchorRect: CGRect(origin: CGPoint(x: absoluteContentRect.minX + contentParentNode.contentRect.minX, y: absoluteContentRect.minY + contentParentNode.contentRect.minY), size: contentParentNode.contentRect.size), transition: transition)
}
}
case let .controller(contentParentNode):
let topEdge = max(contentTopInset, self.contentAreaInScreenSpace?.minY ?? 0.0)
contentParentNode.updateLayout(size: layout.size, transition: transition)
let isInitialLayout = self.actionsContainerNode.frame.size.width.isZero
let previousContainerFrame = self.view.convert(self.contentContainerNode.frame, from: self.scrollNode.view)
let actionsSize = self.actionsContainerNode.updateLayout(constrainedWidth: layout.size.width - actionsSideInset * 2.0, transition: actionsContainerTransition)
let contentScale = (layout.size.width - actionsSideInset * 2.0) / layout.size.width
let proposedContentHeight = layout.size.height - topEdge - contentActionsSpacing - actionsSize.height - layout.intrinsicInsets.bottom - actionsBottomInset
let contentUnscaledSize = CGSize(width: layout.size.width, height: max(400.0, proposedContentHeight))
let contentSize = CGSize(width: floor(contentUnscaledSize.width * contentScale), height: floor(contentUnscaledSize.height * contentScale))
self.contentContainerNode.updateLayout(size: contentUnscaledSize, scaledSize: contentSize, transition: transition)
let maximumActionsFrameOrigin = max(60.0, layout.size.height - layout.intrinsicInsets.bottom - actionsBottomInset - actionsSize.height)
var originalActionsFrame = CGRect(origin: CGPoint(x: actionsSideInset, y: maximumActionsFrameOrigin), size: actionsSize)
var originalContentFrame = CGRect(origin: CGPoint(x: actionsSideInset, y: originalActionsFrame.minY - contentActionsSpacing - contentSize.height), size: contentSize)
if originalContentFrame.minY < topEdge {
let requiredOffset = topEdge - originalContentFrame.minY
let availableOffset = max(0.0, layout.size.height - layout.intrinsicInsets.bottom - actionsBottomInset - originalActionsFrame.maxY)
let offset = min(requiredOffset, availableOffset)
originalActionsFrame = originalActionsFrame.offsetBy(dx: 0.0, dy: offset)
originalContentFrame = originalContentFrame.offsetBy(dx: 0.0, dy: offset)
}
let contentHeight = max(layout.size.height, max(layout.size.height, originalActionsFrame.maxY + actionsBottomInset) - originalContentFrame.minY + contentTopInset)
let scrollContentSize = CGSize(width: layout.size.width, height: contentHeight)
if self.scrollNode.view.contentSize != scrollContentSize {
self.scrollNode.view.contentSize = scrollContentSize
}
let overflowOffset = min(0.0, originalContentFrame.minY - contentTopInset)
let contentContainerFrame = originalContentFrame
transition.updateFrame(node: self.contentContainerNode, frame: contentContainerFrame.offsetBy(dx: 0.0, dy: -overflowOffset))
actionsContainerTransition.updateFrame(node: self.actionsContainerNode, frame: originalActionsFrame.offsetBy(dx: 0.0, dy: -overflowOffset))
if isInitialLayout {
self.scrollNode.view.contentOffset = CGPoint(x: 0.0, y: -overflowOffset)
let currentContainerFrame = self.view.convert(self.contentContainerNode.frame, from: self.scrollNode.view)
if overflowOffset < 0.0 {
transition.animateOffsetAdditive(node: self.scrollNode, offset: currentContainerFrame.minY - previousContainerFrame.minY)
}
}
let absoluteContentRect = contentContainerFrame.offsetBy(dx: 0.0, dy: -self.scrollNode.view.contentOffset.y)
if let reactionContextNode = self.reactionContextNode {
let insets = layout.insets(options: [.statusBar])
transition.updateFrame(node: reactionContextNode, frame: CGRect(origin: CGPoint(), size: layout.size))
reactionContextNode.updateLayout(size: layout.size, insets: insets, anchorRect: CGRect(origin: CGPoint(x: absoluteContentRect.minX, y: absoluteContentRect.minY), size: contentSize), transition: transition)
}
}
let absoluteContentRect = contentContainerFrame.offsetBy(dx: 0.0, dy: -self.scrollNode.view.contentOffset.y)
contentParentNode.updateAbsoluteRect?(absoluteContentRect, layout.size)
if let reactionContextNode = self.reactionContextNode {
let insets = layout.insets(options: [.statusBar])
transition.updateFrame(node: reactionContextNode, frame: CGRect(origin: CGPoint(), size: layout.size))
reactionContextNode.updateLayout(size: layout.size, insets: insets, anchorRect: CGRect(origin: CGPoint(x: absoluteContentRect.minX + contentParentNode.contentRect.minX, y: absoluteContentRect.minY + contentParentNode.contentRect.minY), size: contentParentNode.contentRect.size), transition: transition)
}
}
@ -718,7 +944,10 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if let contentParentNode = self.contentParentNode, let layout = self.validLayout {
guard let layout = self.validLayout else {
return
}
if let maybeContentNode = self.contentContainerNode.contentNode, case let .extracted(contentParentNode) = maybeContentNode {
let contentContainerFrame = self.contentContainerNode.frame
contentParentNode.updateAbsoluteRect?(contentContainerFrame.offsetBy(dx: 0.0, dy: -self.scrollNode.view.contentOffset.y), layout.size)
}
@ -734,14 +963,19 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
}
}
let mappedPoint = self.view.convert(point, to: self.scrollNode.view)
if let contentParentNode = self.contentParentNode {
let contentPoint = self.view.convert(point, to: contentParentNode.contentNode.view)
if let result = contentParentNode.contentNode.hitTest(contentPoint, with: event) {
if result is TextSelectionNodeView {
return result
} else if contentParentNode.contentRect.contains(contentPoint) {
return contentParentNode.contentNode.view
if let maybeContentNode = self.contentContainerNode.contentNode {
switch maybeContentNode {
case let .extracted(contentParentNode):
let contentPoint = self.view.convert(point, to: contentParentNode.contentNode.view)
if let result = contentParentNode.contentNode.hitTest(contentPoint, with: event) {
if result is TextSelectionNodeView {
return result
} else if contentParentNode.contentRect.contains(contentPoint) {
return contentParentNode.contentNode.view
}
}
case let .controller(controller):
break
}
}
@ -765,9 +999,11 @@ public final class ContextControllerTakeViewInfo {
public final class ContextControllerTakeControllerInfo {
public let contentAreaInScreenSpace: CGRect
public let sourceNode: () -> (ASDisplayNode, CGRect)?
public init(contentAreaInScreenSpace: CGRect) {
public init(contentAreaInScreenSpace: CGRect, sourceNode: @escaping () -> (ASDisplayNode, CGRect)?) {
self.contentAreaInScreenSpace = contentAreaInScreenSpace
self.sourceNode = sourceNode
}
}

View File

@ -18,7 +18,7 @@ private func traceDeceleratingScrollView(_ view: UIView, at point: CGPoint) -> B
public final class PeekControllerGestureRecognizer: UIPanGestureRecognizer {
private let contentAtPoint: (CGPoint) -> Signal<(ASDisplayNode, PeekControllerContent)?, NoError>?
private let present: (PeekControllerContent, ASDisplayNode) -> PeekController?
private let present: (PeekControllerContent, ASDisplayNode) -> ViewController?
private let updateContent: (PeekControllerContent?) -> Void
private let activateBySingleTap: Bool
@ -36,7 +36,7 @@ public final class PeekControllerGestureRecognizer: UIPanGestureRecognizer {
private var menuActivation: PeerkControllerMenuActivation?
private weak var presentedController: PeekController?
public init(contentAtPoint: @escaping (CGPoint) -> Signal<(ASDisplayNode, PeekControllerContent)?, NoError>?, present: @escaping (PeekControllerContent, ASDisplayNode) -> PeekController?, updateContent: @escaping (PeekControllerContent?) -> Void = { _ in }, activateBySingleTap: Bool = false) {
public init(contentAtPoint: @escaping (CGPoint) -> Signal<(ASDisplayNode, PeekControllerContent)?, NoError>?, present: @escaping (PeekControllerContent, ASDisplayNode) -> ViewController?, updateContent: @escaping (PeekControllerContent?) -> Void = { _ in }, activateBySingleTap: Bool = false) {
self.contentAtPoint = contentAtPoint
self.present = present
self.updateContent = updateContent
@ -259,29 +259,35 @@ public final class PeekControllerGestureRecognizer: UIPanGestureRecognizer {
}
} else {
if let presentedController = strongSelf.present(content, sourceNode) {
if forceActivate {
strongSelf.candidateContent = nil
if case .press = content.menuActivation() {
(presentedController.displayNode as? PeekControllerNode)?.activateMenu()
}
} else {
strongSelf.candidateContent = (sourceNode, content)
strongSelf.menuActivation = content.menuActivation()
strongSelf.presentedController = presentedController
strongSelf.state = .began
switch content.menuActivation() {
case .drag:
break
case .press:
if #available(iOSApplicationExtension 9.0, *) {
if presentedController.traitCollection.forceTouchCapability != .available {
if let presentedController = presentedController as? PeekController {
if forceActivate {
strongSelf.candidateContent = nil
if case .press = content.menuActivation() {
(presentedController.displayNode as? PeekControllerNode)?.activateMenu()
}
} else {
strongSelf.candidateContent = (sourceNode, content)
strongSelf.menuActivation = content.menuActivation()
strongSelf.presentedController = presentedController
strongSelf.state = .began
switch content.menuActivation() {
case .drag:
break
case .press:
if #available(iOSApplicationExtension 9.0, *) {
if presentedController.traitCollection.forceTouchCapability != .available {
strongSelf.startPressTimer()
}
} else {
strongSelf.startPressTimer()
}
} else {
strongSelf.startPressTimer()
}
}
}
} else {
if strongSelf.state != .ended {
strongSelf.state = .ended
}
}
}

View File

@ -224,6 +224,8 @@ open class ViewControllerPresentationArguments {
public var scrollToTopWithTabBar: (() -> Void)?
public var longTapWithTabBar: (() -> Void)?
public var customPresentPreviewingController: ((ViewController, ASDisplayNode) -> ViewController?)?
open func updateNavigationCustomData(_ data: Any?, progress: CGFloat, transition: ContainedViewLayoutTransition) {
}
@ -491,12 +493,14 @@ open class ViewControllerPresentationArguments {
@available(iOSApplicationExtension 9.0, iOS 9.0, *)
open func registerForPreviewing(with delegate: UIViewControllerPreviewingDelegate, sourceView: UIView, theme: PeekControllerTheme, onlyNative: Bool) {
if self.traitCollection.forceTouchCapability == .available {
if false, self.traitCollection.forceTouchCapability == .available {
let _ = super.registerForPreviewing(with: delegate, sourceView: sourceView)
} else if !onlyNative {
if self.previewingContext == nil {
let previewingContext = SimulatedViewControllerPreviewing(theme: theme, delegate: delegate, sourceView: sourceView, node: self.displayNode, present: { [weak self] c, a in
self?.presentInGlobalOverlay(c, with: a)
}, customPresent: { [weak self] c, n in
return self?.customPresentPreviewingController?(c, n)
})
self.previewingContext = previewingContext
}
@ -505,10 +509,12 @@ open class ViewControllerPresentationArguments {
@available(iOSApplicationExtension 9.0, iOS 9.0, *)
public func registerForPreviewingNonNative(with delegate: UIViewControllerPreviewingDelegate, sourceView: UIView, theme: PeekControllerTheme) {
if self.traitCollection.forceTouchCapability != .available {
if true || self.traitCollection.forceTouchCapability != .available {
if self.previewingContext == nil {
let previewingContext = SimulatedViewControllerPreviewing(theme: theme, delegate: delegate, sourceView: sourceView, node: self.displayNode, present: { [weak self] c, a in
self?.presentInGlobalOverlay(c, with: a)
}, customPresent: { [weak self] c, n in
return self?.customPresentPreviewingController?(c, n)
})
self.previewingContext = previewingContext
}

View File

@ -5,7 +5,7 @@ import SwiftSignalKit
@available(iOSApplicationExtension 9.0, iOS 9.0, *)
private final class ViewControllerPeekContent: PeekControllerContent {
private let controller: ViewController
let controller: ViewController
private let menu: [PeekControllerMenuItem]
init(controller: ViewController) {
@ -101,7 +101,7 @@ final class SimulatedViewControllerPreviewing: NSObject, UIViewControllerPreview
var sourceRect: CGRect = CGRect()
init(theme: PeekControllerTheme, delegate: UIViewControllerPreviewingDelegate, sourceView: UIView, node: ASDisplayNode, present: @escaping (ViewController, Any?) -> Void) {
init(theme: PeekControllerTheme, delegate: UIViewControllerPreviewingDelegate, sourceView: UIView, node: ASDisplayNode, present: @escaping (ViewController, Any?) -> Void, customPresent: ((ViewController, ASDisplayNode) -> ViewController?)?) {
self.delegateImpl = delegate
self.sourceView = sourceView
self.node = node
@ -109,11 +109,16 @@ final class SimulatedViewControllerPreviewing: NSObject, UIViewControllerPreview
self.recognizer = PeekControllerGestureRecognizer(contentAtPoint: { point in
return contentAtPointImpl?(point)
}, present: { content, sourceNode in
let controller = PeekController(theme: theme, content: content, sourceNode: {
return sourceNode
})
present(controller, nil)
return controller
if let content = content as? ViewControllerPeekContent, let controller = customPresent?(content.controller, sourceNode) {
present(controller, nil)
return controller
} else {
let controller = PeekController(theme: theme, content: content, sourceNode: {
return sourceNode
})
present(controller, nil)
return controller
}
})
node.view.addGestureRecognizer(self.recognizer)

View File

@ -1277,6 +1277,14 @@ final class SharedApplicationContext {
self.isInForegroundPromise.set(true)
self.isActiveValue = true
self.isActivePromise.set(true)
let configuration = URLSessionConfiguration.background(withIdentifier: "org.telegram.Telegram-iOS.background")
let session = URLSession(configuration: configuration)
if #available(iOS 9.0, *) {
session.getAllTasks(completionHandler: { tasks in
print(tasks)
})
}
}
func applicationWillTerminate(_ application: UIApplication) {
@ -1741,6 +1749,11 @@ final class SharedApplicationContext {
})
}
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
Logger.shared.log("App \(self.episodeId)", "handleEventsForBackgroundURLSession \(identifier)")
completionHandler()
}
override var next: UIResponder? {
if let context = self.contextValue, let controller = context.context.keyShortcutsController {
return controller

View File

@ -563,7 +563,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if Namespaces.Message.allScheduled.contains(message.id.namespace) {
reactionItems = []
}
let controller = ContextController(account: strongSelf.context.account, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, source: ChatMessageContextExtractedContentSource(chatNode: strongSelf.chatDisplayNode, message: message), items: actions, reactionItems: reactionItems, recognizer: recognizer)
let controller = ContextController(account: strongSelf.context.account, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, source: .extracted(ChatMessageContextExtractedContentSource(chatNode: strongSelf.chatDisplayNode, message: message)), items: actions, reactionItems: reactionItems, recognizer: recognizer)
strongSelf.currentContextController = controller
controller.reactionSelected = { [weak controller] value in
guard let strongSelf = self, let message = updatedMessages.first else {
@ -1392,7 +1392,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
})))
let controller = ContextController(account: strongSelf.context.account, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, source: ChatMessageContextExtractedContentSource(chatNode: strongSelf.chatDisplayNode, message: message), items: actions, reactionItems: [], recognizer: nil)
let controller = ContextController(account: strongSelf.context.account, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, source: .extracted(ChatMessageContextExtractedContentSource(chatNode: strongSelf.chatDisplayNode, message: message)), items: actions, reactionItems: [], recognizer: nil)
strongSelf.currentContextController = controller
strongSelf.window?.presentInGlobalOverlay(controller)
})
@ -1432,7 +1432,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
f(.dismissWithoutContent)
})))
let controller = ContextController(account: strongSelf.context.account, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, source: ChatMessageContextExtractedContentSource(chatNode: strongSelf.chatDisplayNode, message: message), items: actions, reactionItems: [], recognizer: nil)
let controller = ContextController(account: strongSelf.context.account, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, source: .extracted(ChatMessageContextExtractedContentSource(chatNode: strongSelf.chatDisplayNode, message: message)), items: actions, reactionItems: [], recognizer: nil)
strongSelf.currentContextController = controller
strongSelf.window?.presentInGlobalOverlay(controller)
})

View File

@ -832,7 +832,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
}
strongSelf.fetchDisposable.set(visibilityAwareFetchSignal.start())
}
} else if case .prefetch = automaticDownload, message.id.namespace != Namespaces.Message.SecretIncoming && message.id.namespace != Namespaces.Message.Local {
} else if case .prefetch = automaticDownload, message.id.namespace != Namespaces.Message.SecretIncoming /*&& message.id.namespace != Namespaces.Message.Local*/ {
if let file = media as? TelegramMediaFile {
let fetchSignal = preloadVideoResource(postbox: context.account.postbox, resourceReference: AnyMediaReference.message(message: MessageReference(message), media: file).resourceReference(file.resource), duration: 4.0)
let visibilityAwareFetchSignal = strongSelf.visibilityPromise.get()

View File

@ -73,6 +73,151 @@ struct VideoConversionConfiguration {
}
}
private final class FetchVideoLibraryMediaResourceItem {
let priority: MediaBoxFetchPriority
let signal: Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError>
let next: (MediaResourceDataFetchResult) -> Void
let error: (MediaResourceDataFetchError) -> Void
let completion: () -> Void
var isActive: Bool = false
var disposable: Disposable?
init(priority: MediaBoxFetchPriority, signal: Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError>, next: @escaping (MediaResourceDataFetchResult) -> Void, error: @escaping (MediaResourceDataFetchError) -> Void, completion: @escaping () -> Void) {
self.priority = priority
self.signal = signal
self.next = next
self.error = error
self.completion = completion
}
deinit {
self.disposable?.dispose()
}
}
private let fetchVideoLimit: Int = 2
private final class FetchVideoLibraryMediaResourceContextImpl {
private let queue: Queue
var items: [FetchVideoLibraryMediaResourceItem] = []
init(queue: Queue) {
self.queue = queue
}
func add(priority: MediaBoxFetchPriority, signal: Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError>, next: @escaping (MediaResourceDataFetchResult) -> Void, error: @escaping (MediaResourceDataFetchError) -> Void, completion: @escaping () -> Void) -> Disposable {
let queue = self.queue
let item = FetchVideoLibraryMediaResourceItem(priority: priority, signal: signal, next: next, error: error, completion: completion)
self.items.append(item)
self.update()
return ActionDisposable { [weak self, weak item] in
queue.async {
guard let strongSelf = self, let item = item else {
return
}
for i in 0 ..< strongSelf.items.count {
if strongSelf.items[i] === item {
strongSelf.items[i].disposable?.dispose()
strongSelf.items.remove(at: i)
strongSelf.update()
break
}
}
}
}
}
func update() {
let queue = self.queue
var activeCount = 0
for item in self.items {
if item.isActive {
activeCount += 1
}
}
while activeCount < fetchVideoLimit {
var maxPriorityIndex: Int?
for i in 0 ..< self.items.count {
if !self.items[i].isActive {
if let maxPriorityIndexValue = maxPriorityIndex {
if self.items[i].priority.rawValue > self.items[maxPriorityIndexValue].priority.rawValue {
maxPriorityIndex = i
}
} else {
maxPriorityIndex = i
}
}
}
if let maxPriorityIndex = maxPriorityIndex {
let item = self.items[maxPriorityIndex]
item.isActive = true
activeCount += 1
assert(item.disposable == nil)
item.disposable = self.items[maxPriorityIndex].signal.start(next: { [weak item] value in
queue.async {
item?.next(value)
}
}, error: { [weak item] value in
queue.async {
item?.error(value)
}
}, completed: { [weak self, weak item] in
queue.async {
guard let strongSelf = self, let item = item else {
return
}
for i in 0 ..< strongSelf.items.count {
if strongSelf.items[i] === item {
strongSelf.items.remove(at: i)
item.completion()
strongSelf.update()
break
}
}
}
})
} else {
break
}
}
}
}
private final class FetchVideoLibraryMediaResourceContext {
private let queue = Queue()
private let impl: QueueLocalObject<FetchVideoLibraryMediaResourceContextImpl>
init() {
let queue = self.queue
self.impl = QueueLocalObject(queue: queue, generate: {
return FetchVideoLibraryMediaResourceContextImpl(queue: queue)
})
}
func wrap(priority: MediaBoxFetchPriority, signal: Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError>) -> Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError> {
return Signal { subscriber in
let disposable = MetaDisposable()
self.impl.with { impl in
disposable.set(impl.add(priority: priority, signal: signal, next: { value in
subscriber.putNext(value)
}, error: { error in
subscriber.putError(error)
}, completion: {
subscriber.putCompletion()
}))
}
return disposable
}
}
}
private let throttlingContext = FetchVideoLibraryMediaResourceContext()
public func fetchVideoLibraryMediaResource(postbox: Postbox, resource: VideoLibraryMediaResource) -> Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError> {
return postbox.preferencesView(keys: [PreferencesKeys.appConfiguration])
|> take(1)
@ -82,9 +227,8 @@ public func fetchVideoLibraryMediaResource(postbox: Postbox, resource: VideoLibr
|> introduceError(MediaResourceDataFetchError.self)
|> mapToSignal { appConfiguration -> Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError> in
let config = VideoConversionConfiguration.with(appConfiguration: appConfiguration)
return Signal { subscriber in
let signal = Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError> { subscriber in
subscriber.putNext(.reset)
let fetchResult = PHAsset.fetchAssets(withLocalIdentifiers: [resource.localIdentifier], options: nil)
var requestId: PHImageRequestID?
let disposable = MetaDisposable()
@ -189,6 +333,7 @@ public func fetchVideoLibraryMediaResource(postbox: Postbox, resource: VideoLibr
disposable.dispose()
}
}
return throttlingContext.wrap(priority: .default, signal: signal)
}
}
@ -201,7 +346,7 @@ func fetchLocalFileVideoMediaResource(postbox: Postbox, resource: LocalFileVideo
|> introduceError(MediaResourceDataFetchError.self)
|> mapToSignal { appConfiguration -> Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError> in
let config = VideoConversionConfiguration.with(appConfiguration: appConfiguration)
return Signal { subscriber in
let signal = Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError> { subscriber in
subscriber.putNext(.reset)
let avAsset = AVURLAsset(url: URL(fileURLWithPath: resource.path))
@ -267,6 +412,7 @@ func fetchLocalFileVideoMediaResource(postbox: Postbox, resource: LocalFileVideo
disposable.dispose()
}
}
return throttlingContext.wrap(priority: .default, signal: signal)
}
}