From c31e0372f84465801622ba0acef80b6b0ecc2e6a Mon Sep 17 00:00:00 2001 From: Peter <> Date: Tue, 3 Sep 2019 16:29:53 +0400 Subject: [PATCH 1/5] Temp --- .../Sources/ContextContentContainerNode.swift | 9 + .../Sources/ContextContentSourceNode.swift | 29 +- .../ContextUI/Sources/ContextController.swift | 413 ++++++++++-------- .../ChatMessageAnimatedStickerItemNode.swift | 6 +- .../ChatMessageBubbleItemNode.swift | 8 +- ...essageContextControllerContentSource.swift | 2 +- .../ChatMessageInstantVideoItemNode.swift | 6 +- .../TelegramUI/ChatMessageItemView.swift | 2 +- .../ChatMessageStickerItemNode.swift | 6 +- 9 files changed, 273 insertions(+), 208 deletions(-) diff --git a/submodules/ContextUI/Sources/ContextContentContainerNode.swift b/submodules/ContextUI/Sources/ContextContentContainerNode.swift index 014cb82458..4f850c33d4 100644 --- a/submodules/ContextUI/Sources/ContextContentContainerNode.swift +++ b/submodules/ContextUI/Sources/ContextContentContainerNode.swift @@ -10,5 +10,14 @@ final class ContextContentContainerNode: ASDisplayNode { } func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) { + guard let contentNode = self.contentNode else { + return + } + switch contentNode { + case .extracted: + break + case let .controller(controller): + controller.updateLayout(size: size, transition: transition) + } } } diff --git a/submodules/ContextUI/Sources/ContextContentSourceNode.swift b/submodules/ContextUI/Sources/ContextContentSourceNode.swift index a2f91e3cd3..081d528a26 100644 --- a/submodules/ContextUI/Sources/ContextContentSourceNode.swift +++ b/submodules/ContextUI/Sources/ContextContentSourceNode.swift @@ -2,8 +2,8 @@ import Foundation import AsyncDisplayKit import Display -public final class ContextContentContainingNode: ASDisplayNode { - public let contentNode: ContextContentNode +public final class ContextExtractedContentContainingNode: ASDisplayNode { + public let contentNode: ContextExtractedContentNode public var contentRect: CGRect = CGRect() public var isExtractedToContextPreview: Bool = false public var willUpdateIsExtractedToContextPreview: ((Bool) -> Void)? @@ -15,7 +15,7 @@ public final class ContextContentContainingNode: ASDisplayNode { public var updateDistractionFreeMode: ((Bool) -> Void)? public override init() { - self.contentNode = ContextContentNode() + self.contentNode = ContextExtractedContentNode() super.init() @@ -23,5 +23,26 @@ public final class ContextContentContainingNode: ASDisplayNode { } } -public final class ContextContentNode: ASDisplayNode { +public final class ContextExtractedContentNode: ASDisplayNode { +} + +final class ContextControllerContentNode: ASDisplayNode { + let controller: ViewController + + init(controller: ViewController) { + self.controller = controller + + super.init() + + self.addSubnode(controller.displayNode) + } + + 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) + } +} + +enum ContextContentNode { + case extracted(ContextExtractedContentContainingNode) + case controller(ContextControllerContentNode) } diff --git a/submodules/ContextUI/Sources/ContextController.swift b/submodules/ContextUI/Sources/ContextController.swift index 9f27b28a6f..2637c51cd7 100644 --- a/submodules/ContextUI/Sources/ContextController.swift +++ b/submodules/ContextUI/Sources/ContextController.swift @@ -60,7 +60,7 @@ private func convertFrame(_ frame: CGRect, from fromView: UIView, to toView: UIV private final class ContextControllerNode: ViewControllerTracingNode, UIScrollViewDelegate { private var theme: PresentationTheme private var strings: PresentationStrings - private let source: ContextControllerContentSource + private let source: ContextContentSource private var items: [ContextMenuItem] private let beginDismiss: (ContextMenuActionResult) -> Void private let reactionSelected: (String) -> Void @@ -78,7 +78,6 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi private var originalProjectedContentViewFrame: (CGRect, CGRect)? private var contentAreaInScreenSpace: CGRect? - private var contentParentNode: ContextContentContainingNode? private let contentContainerNode: ContextContentContainerNode private var actionsContainerNode: ContextActionsContainerNode private var reactionContextNode: ReactionContextNode? @@ -94,7 +93,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi private var isAnimatingOut = false - init(account: Account, controller: ContextController, theme: PresentationTheme, strings: PresentationStrings, source: ContextControllerContentSource, items: [ContextMenuItem], reactionItems: [ReactionContextItem], beginDismiss: @escaping (ContextMenuActionResult) -> Void, recognizer: TapLongTapOrDoubleTapGestureRecognizer?, reactionSelected: @escaping (String) -> Void) { + init(account: Account, controller: ContextController, theme: PresentationTheme, strings: PresentationStrings, source: ContextContentSource, items: [ContextMenuItem], reactionItems: [ReactionContextItem], beginDismiss: @escaping (ContextMenuActionResult) -> Void, recognizer: TapLongTapOrDoubleTapGestureRecognizer?, reactionSelected: @escaping (String) -> Void) { self.theme = theme self.strings = strings self.source = source @@ -285,58 +284,62 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi func animateIn() { self.hapticFeedback.impact() - let takenViewInfo = self.source.takeView() - - if let takenViewInfo = takenViewInfo, let parentSupernode = takenViewInfo.contentContainingNode.supernode { - self.contentParentNode = takenViewInfo.contentContainingNode - let contentParentNode = takenViewInfo.contentContainingNode - takenViewInfo.contentContainingNode.layoutUpdated = { [weak contentParentNode, weak self] size in - guard let strongSelf = self, let contentParentNode = contentParentNode, let parentSupernode = contentParentNode.supernode else { - return + switch self.source { + case let .extracted(source): + let takenViewInfo = source.takeView() + + if let takenViewInfo = takenViewInfo, let parentSupernode = takenViewInfo.contentContainingNode.supernode { + self.contentContainerNode.contentNode = .extracted(takenViewInfo.contentContainingNode) + let contentParentNode = takenViewInfo.contentContainingNode + takenViewInfo.contentContainingNode.layoutUpdated = { [weak contentParentNode, weak self] size in + guard let strongSelf = self, let contentParentNode = contentParentNode, let parentSupernode = contentParentNode.supernode else { + return + } + if strongSelf.isAnimatingOut { + return + } + strongSelf.originalProjectedContentViewFrame = (convertFrame(contentParentNode.frame, from: parentSupernode.view, to: strongSelf.view), convertFrame(contentParentNode.contentRect, from: contentParentNode.view, to: strongSelf.view)) + if let validLayout = strongSelf.validLayout { + strongSelf.updateLayout(layout: validLayout, transition: .animated(duration: 0.2 * animationDurationFactor, curve: .easeInOut), previousActionsContainerNode: nil) + } } - if strongSelf.isAnimatingOut { - return - } - strongSelf.originalProjectedContentViewFrame = (convertFrame(contentParentNode.frame, from: parentSupernode.view, to: strongSelf.view), convertFrame(contentParentNode.contentRect, from: contentParentNode.view, to: strongSelf.view)) - if let validLayout = strongSelf.validLayout { - strongSelf.updateLayout(layout: validLayout, transition: .animated(duration: 0.2 * animationDurationFactor, curve: .easeInOut), previousActionsContainerNode: nil) - } - } - takenViewInfo.contentContainingNode.updateDistractionFreeMode = { [weak self] value in - guard let strongSelf = self, let reactionContextNode = strongSelf.reactionContextNode else { - return - } - if value { - if !reactionContextNode.alpha.isZero { - reactionContextNode.alpha = 0.0 + takenViewInfo.contentContainingNode.updateDistractionFreeMode = { [weak self] value in + guard let strongSelf = self, let reactionContextNode = strongSelf.reactionContextNode else { + return + } + if value { + if !reactionContextNode.alpha.isZero { + reactionContextNode.alpha = 0.0 + reactionContextNode.allowsGroupOpacity = true + reactionContextNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3 * animationDurationFactor, completion: { [weak reactionContextNode] _ in + reactionContextNode?.allowsGroupOpacity = false + }) + } + } else if reactionContextNode.alpha != 1.0 { + reactionContextNode.alpha = 1.0 reactionContextNode.allowsGroupOpacity = true - reactionContextNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3 * animationDurationFactor, completion: { [weak reactionContextNode] _ in + reactionContextNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3 * animationDurationFactor, completion: { [weak reactionContextNode] _ in reactionContextNode?.allowsGroupOpacity = false }) } - } else if reactionContextNode.alpha != 1.0 { - reactionContextNode.alpha = 1.0 - reactionContextNode.allowsGroupOpacity = true - reactionContextNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3 * animationDurationFactor, completion: { [weak reactionContextNode] _ in - reactionContextNode?.allowsGroupOpacity = false - }) } + + self.contentAreaInScreenSpace = takenViewInfo.contentAreaInScreenSpace + self.contentContainerNode.addSubnode(takenViewInfo.contentContainingNode.contentNode) + takenViewInfo.contentContainingNode.isExtractedToContextPreview = true + takenViewInfo.contentContainingNode.isExtractedToContextPreviewUpdated?(true) + + self.originalProjectedContentViewFrame = (convertFrame(takenViewInfo.contentContainingNode.frame, from: parentSupernode.view, to: self.view), convertFrame(takenViewInfo.contentContainingNode.contentRect, from: takenViewInfo.contentContainingNode.view, to: self.view)) + + var updatedContentAreaInScreenSpace = takenViewInfo.contentAreaInScreenSpace + updatedContentAreaInScreenSpace.origin.x = 0.0 + updatedContentAreaInScreenSpace.size.width = self.bounds.width + + 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) } - self.contentContainerNode.contentNode = takenViewInfo.contentContainingNode.contentNode - - self.contentAreaInScreenSpace = takenViewInfo.contentAreaInScreenSpace - self.contentContainerNode.addSubnode(takenViewInfo.contentContainingNode.contentNode) - takenViewInfo.contentContainingNode.isExtractedToContextPreview = true - takenViewInfo.contentContainingNode.isExtractedToContextPreviewUpdated?(true) - - self.originalProjectedContentViewFrame = (convertFrame(takenViewInfo.contentContainingNode.frame, from: parentSupernode.view, to: self.view), convertFrame(takenViewInfo.contentContainingNode.contentRect, from: takenViewInfo.contentContainingNode.view, to: self.view)) - - var updatedContentAreaInScreenSpace = takenViewInfo.contentAreaInScreenSpace - updatedContentAreaInScreenSpace.origin.x = 0.0 - updatedContentAreaInScreenSpace.size.width = self.bounds.width - - 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) + case let .controller(source): + break } if let validLayout = self.validLayout { @@ -377,17 +380,25 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi 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 originalProjectedContentViewFrame = self.originalProjectedContentViewFrame, let contentParentNode = self.contentParentNode { - let localSourceFrame = self.view.convert(originalProjectedContentViewFrame.1, to: self.scrollNode.view) - - if let reactionContextNode = self.reactionContextNode { - reactionContextNode.animateIn(from: CGRect(origin: CGPoint(x: originalProjectedContentViewFrame.1.minX, y: originalProjectedContentViewFrame.1.minY), size: contentParentNode.contentRect.size)) + if let contentNode = self.contentContainerNode.contentNode { + switch contentNode { + case let .extracted(extracted): + if let originalProjectedContentViewFrame = self.originalProjectedContentViewFrame { + let contentParentNode = extracted + let localSourceFrame = self.view.convert(originalProjectedContentViewFrame.1, to: self.scrollNode.view) + + if let reactionContextNode = self.reactionContextNode { + reactionContextNode.animateIn(from: CGRect(origin: CGPoint(x: originalProjectedContentViewFrame.1.minX, y: originalProjectedContentViewFrame.1.minY), size: contentParentNode.contentRect.size)) + } + + 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 - contentParentNode.contentRect.minX, 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) + contentParentNode.applyAbsoluteOffsetSpring?(-contentContainerOffset.y, springDuration, springDamping) + } + case let .controller(controller): + break } - - 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 - contentParentNode.contentRect.minX, 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) - contentParentNode.applyAbsoluteOffsetSpring?(-contentContainerOffset.y, springDuration, springDamping) } } @@ -396,147 +407,153 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi var transitionCurve: ContainedViewLayoutTransitionCurve = .easeInOut var result = initialResult - let putBackInfo = self.source.putBack() - if putBackInfo == nil { - result = .dismissWithoutContent - } - - switch result { - case let .custom(value): - switch value { - case let .animated(duration, curve): - transitionDuration = duration - transitionCurve = curve + switch self.source { + case let .extracted(source): + let putBackInfo = source.putBack() + + if putBackInfo == nil { + result = .dismissWithoutContent + } + + switch result { + case let .custom(value): + switch value { + case let .animated(duration, curve): + transitionDuration = duration + transitionCurve = curve + default: + break + } 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 putBackInfo = putBackInfo, let contentParentNode = self.contentParentNode, 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 - updatedContentAreaInScreenSpace.origin.x = 0.0 - updatedContentAreaInScreenSpace.size.width = self.bounds.width + self.isUserInteractionEnabled = false + self.isAnimatingOut = true - self.clippingNode.layer.animateFrame(from: self.clippingNode.frame, to: updatedContentAreaInScreenSpace, duration: transitionDuration * animationDurationFactor, timingFunction: transitionCurve.timingFunction, removeOnCompletion: false) - 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) - - let intermediateCompletion: () -> Void = { [weak contentParentNode] in - if completedEffect && completedContentNode && completedActionsNode { - switch result { - case .default, .custom: - if let contentParentNode = contentParentNode { - contentParentNode.addSubnode(contentParentNode.contentNode) - contentParentNode.isExtractedToContextPreview = false - contentParentNode.isExtractedToContextPreviewUpdated?(false) - } - case .dismissWithoutContent: - break - } + self.scrollNode.view.setContentOffset(self.scrollNode.view.contentOffset, animated: false) + + var completedEffect = false + var completedContentNode = false + var completedActionsNode = false + + if let putBackInfo = putBackInfo, let contentParentNode = self.contentParentNode, let parentSupernode = contentParentNode.supernode { + self.originalProjectedContentViewFrame = (convertFrame(contentParentNode.frame, from: parentSupernode.view, to: self.view), convertFrame(contentParentNode.contentRect, from: contentParentNode.view, to: self.view)) - completion() + var updatedContentAreaInScreenSpace = putBackInfo.contentAreaInScreenSpace + updatedContentAreaInScreenSpace.origin.x = 0.0 + updatedContentAreaInScreenSpace.size.width = self.bounds.width + + self.clippingNode.layer.animateFrame(from: self.clippingNode.frame, to: updatedContentAreaInScreenSpace, duration: transitionDuration * animationDurationFactor, timingFunction: transitionCurve.timingFunction, removeOnCompletion: false) + self.clippingNode.layer.animateBoundsOriginYAdditive(from: 0.0, to: updatedContentAreaInScreenSpace.minY, duration: transitionDuration * animationDurationFactor, timingFunction: transitionCurve.timingFunction, removeOnCompletion: false) } - } - - if #available(iOS 10.0, *) { - if let propertyAnimator = self.propertyAnimator { - let propertyAnimator = propertyAnimator as? UIViewPropertyAnimator - propertyAnimator?.stopAnimation(true) + + let contentParentNode = self.contentParentNode + + contentParentNode?.willUpdateIsExtractedToContextPreview?(false) + + let intermediateCompletion: () -> Void = { [weak contentParentNode] in + if completedEffect && completedContentNode && completedActionsNode { + switch result { + case .default, .custom: + if let contentParentNode = contentParentNode { + contentParentNode.addSubnode(contentParentNode.contentNode) + contentParentNode.isExtractedToContextPreview = false + contentParentNode.isExtractedToContextPreviewUpdated?(false) + } + case .dismissWithoutContent: + break + } + + completion() + } } - 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: { + + 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.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 + + 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.actionsContainerNode.layer.animateScale(from: 1.0, to: 0.1, 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 contentParentNode = self.contentParentNode { + 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) + self.contentContainerNode.layer.animatePosition(from: CGPoint(), to: contentContainerOffset, duration: transitionDuration * animationDurationFactor, timingFunction: transitionCurve.timingFunction, removeOnCompletion: false, additive: true, completion: { _ in + completedContentNode = true + intermediateCompletion() + }) + contentParentNode.updateAbsoluteRect?(self.contentContainerNode.frame.offsetBy(dx: 0.0, dy: -self.scrollNode.view.contentOffset.y + contentContainerOffset.y), self.bounds.size) + contentParentNode.applyAbsoluteOffset?(-contentContainerOffset.y, transitionCurve, transitionDuration) + + 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 { + if let snapshotView = contentParentNode.contentNode.view.snapshotContentTree() { + self.contentContainerNode.view.addSubview(snapshotView) + } + + contentParentNode.addSubnode(contentParentNode.contentNode) + contentParentNode.isExtractedToContextPreview = false + contentParentNode.isExtractedToContextPreviewUpdated?(false) + + self.contentContainerNode.allowsGroupOpacity = true + self.contentContainerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: transitionDuration * animationDurationFactor, removeOnCompletion: false, completion: { _ in + completedContentNode = true + intermediateCompletion() + }) + //self.contentContainerNode.layer.animateScale(from: 1.0, to: 0.1, duration: transitionDuration * animationDurationFactor, removeOnCompletion: false) + + if let reactionContextNode = self.reactionContextNode { + reactionContextNode.animateOut(to: nil, animatingOutToReaction: self.reactionContextNodeIsAnimatingOut) } - }, 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.actionsContainerNode.layer.animateScale(from: 1.0, to: 0.1, 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 contentParentNode = self.contentParentNode { - 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) - self.contentContainerNode.layer.animatePosition(from: CGPoint(), to: contentContainerOffset, duration: transitionDuration * animationDurationFactor, timingFunction: transitionCurve.timingFunction, removeOnCompletion: false, additive: true, completion: { _ in - completedContentNode = true - intermediateCompletion() - }) - contentParentNode.updateAbsoluteRect?(self.contentContainerNode.frame.offsetBy(dx: 0.0, dy: -self.scrollNode.view.contentOffset.y + contentContainerOffset.y), self.bounds.size) - contentParentNode.applyAbsoluteOffset?(-contentContainerOffset.y, transitionCurve, transitionDuration) - - 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 { - if let snapshotView = contentParentNode.contentNode.view.snapshotContentTree() { - self.contentContainerNode.view.addSubview(snapshotView) - } - - contentParentNode.addSubnode(contentParentNode.contentNode) - contentParentNode.isExtractedToContextPreview = false - contentParentNode.isExtractedToContextPreviewUpdated?(false) - - self.contentContainerNode.allowsGroupOpacity = true - self.contentContainerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: transitionDuration * animationDurationFactor, removeOnCompletion: false, completion: { _ in - completedContentNode = true - intermediateCompletion() - }) - //self.contentContainerNode.layer.animateScale(from: 1.0, to: 0.1, duration: transitionDuration * animationDurationFactor, removeOnCompletion: false) - - if let reactionContextNode = self.reactionContextNode { - reactionContextNode.animateOut(to: nil, animatingOutToReaction: self.reactionContextNodeIsAnimatingOut) } + case let .controller(source): + break } } @@ -737,15 +754,23 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi } public final class ContextControllerTakeViewInfo { - public let contentContainingNode: ContextContentContainingNode + public let contentContainingNode: ContextExtractedContentContainingNode public let contentAreaInScreenSpace: CGRect - public init(contentContainingNode: ContextContentContainingNode, contentAreaInScreenSpace: CGRect) { + public init(contentContainingNode: ContextExtractedContentContainingNode, contentAreaInScreenSpace: CGRect) { self.contentContainingNode = contentContainingNode self.contentAreaInScreenSpace = contentAreaInScreenSpace } } +public final class ContextControllerTakeControllerInfo { + public let contentAreaInScreenSpace: CGRect + + public init(contentAreaInScreenSpace: CGRect) { + self.contentAreaInScreenSpace = contentAreaInScreenSpace + } +} + public final class ContextControllerPutBackViewInfo { public let contentAreaInScreenSpace: CGRect @@ -754,16 +779,26 @@ public final class ContextControllerPutBackViewInfo { } } -public protocol ContextControllerContentSource: class { +public protocol ContextExtractedContentSource: class { func takeView() -> ContextControllerTakeViewInfo? func putBack() -> ContextControllerPutBackViewInfo? } +public protocol ContextControllerContentSource: class { + var controller: ViewController { get } + func transitionInfo() -> ContextControllerTakeControllerInfo? +} + +public enum ContextContentSource { + case extracted(ContextExtractedContentSource) + case controller(ContextControllerContentSource) +} + public final class ContextController: ViewController { private let account: Account private var theme: PresentationTheme private var strings: PresentationStrings - private let source: ContextControllerContentSource + private let source: ContextContentSource private var items: [ContextMenuItem] private var reactionItems: [ReactionContextItem] @@ -778,7 +813,7 @@ public final class ContextController: ViewController { public var reactionSelected: ((String) -> Void)? - public init(account: Account, theme: PresentationTheme, strings: PresentationStrings, source: ContextControllerContentSource, items: [ContextMenuItem], reactionItems: [ReactionContextItem], recognizer: TapLongTapOrDoubleTapGestureRecognizer? = nil) { + public init(account: Account, theme: PresentationTheme, strings: PresentationStrings, source: ContextContentSource, items: [ContextMenuItem], reactionItems: [ReactionContextItem], recognizer: TapLongTapOrDoubleTapGestureRecognizer? = nil) { self.account = account self.theme = theme self.strings = strings diff --git a/submodules/TelegramUI/TelegramUI/ChatMessageAnimatedStickerItemNode.swift b/submodules/TelegramUI/TelegramUI/ChatMessageAnimatedStickerItemNode.swift index 5ad94121a6..0d88b60bc6 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMessageAnimatedStickerItemNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMessageAnimatedStickerItemNode.swift @@ -107,7 +107,7 @@ private class ChatMessageHeartbeatHaptic { } class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { - private let contextSourceNode: ContextContentContainingNode + private let contextSourceNode: ContextExtractedContentContainingNode let imageNode: TransformImageNode private let animationNode: AnimatedStickerNode private var didSetUpAnimationNode = false @@ -138,7 +138,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { private var currentSwipeToReplyTranslation: CGFloat = 0.0 required init() { - self.contextSourceNode = ContextContentContainingNode() + self.contextSourceNode = ContextExtractedContentContainingNode() self.imageNode = TransformImageNode() self.animationNode = AnimatedStickerNode() self.dateAndStatusNode = ChatMessageDateAndStatusNode() @@ -1063,7 +1063,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } - override func getMessageContextSourceNode() -> ContextContentContainingNode? { + override func getMessageContextSourceNode() -> ContextExtractedContentContainingNode? { return self.contextSourceNode } diff --git a/submodules/TelegramUI/TelegramUI/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/TelegramUI/ChatMessageBubbleItemNode.swift index ce6017c57e..f6a684e6e9 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMessageBubbleItemNode.swift @@ -146,7 +146,7 @@ private enum ContentNodeOperation { } class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode { - private let contextSourceNode: ContextContentContainingNode + private let contextSourceNode: ContextExtractedContentContainingNode private let backgroundWallpaperNode: ChatMessageBubbleBackdrop private let backgroundNode: ChatMessageBackground private var transitionClippingNode: ASDisplayNode? @@ -197,7 +197,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode } required init() { - self.contextSourceNode = ContextContentContainingNode() + self.contextSourceNode = ContextExtractedContentContainingNode() self.backgroundWallpaperNode = ChatMessageBubbleBackdrop() self.backgroundNode = ChatMessageBackground() @@ -276,7 +276,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode if let subnodes = self.subnodes { for node in subnodes { - if let contextNode = node as? ContextContentContainingNode { + if let contextNode = node as? ContextExtractedContentContainingNode { if let contextSubnodes = contextNode.contentNode.subnodes { for contextSubnode in contextSubnodes { if contextSubnode !== self.accessoryItemNode { @@ -2821,7 +2821,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode self.backgroundWallpaperNode.offsetSpring(value: value, duration: duration, damping: damping) } - override func getMessageContextSourceNode() -> ContextContentContainingNode? { + override func getMessageContextSourceNode() -> ContextExtractedContentContainingNode? { return self.contextSourceNode } diff --git a/submodules/TelegramUI/TelegramUI/ChatMessageContextControllerContentSource.swift b/submodules/TelegramUI/TelegramUI/ChatMessageContextControllerContentSource.swift index e7014b23ac..93050d570f 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMessageContextControllerContentSource.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMessageContextControllerContentSource.swift @@ -4,7 +4,7 @@ import Display import ContextUI import Postbox -final class ChatMessageContextControllerContentSource: ContextControllerContentSource { +final class ChatMessageContextExtractedContentSource: ContextExtractedContentSource { private weak var chatNode: ChatControllerNode? private let message: Message diff --git a/submodules/TelegramUI/TelegramUI/ChatMessageInstantVideoItemNode.swift b/submodules/TelegramUI/TelegramUI/ChatMessageInstantVideoItemNode.swift index e1e3ddaed8..f8d31ceec8 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMessageInstantVideoItemNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMessageInstantVideoItemNode.swift @@ -18,7 +18,7 @@ private let inlineBotPrefixFont = Font.regular(14.0) private let inlineBotNameFont = nameFont class ChatMessageInstantVideoItemNode: ChatMessageItemView { - private let contextSourceNode: ContextContentContainingNode + private let contextSourceNode: ContextExtractedContentContainingNode private let interactiveVideoNode: ChatMessageInteractiveInstantVideoNode private var selectionNode: ChatMessageSelectionNode? @@ -54,7 +54,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { } required init() { - self.contextSourceNode = ContextContentContainingNode() + self.contextSourceNode = ContextExtractedContentContainingNode() self.interactiveVideoNode = ChatMessageInteractiveInstantVideoNode() super.init(layerBacked: false) @@ -830,7 +830,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView { return self.interactiveVideoNode.playMediaWithSound() } - override func getMessageContextSourceNode() -> ContextContentContainingNode? { + override func getMessageContextSourceNode() -> ContextExtractedContentContainingNode? { return self.contextSourceNode } diff --git a/submodules/TelegramUI/TelegramUI/ChatMessageItemView.swift b/submodules/TelegramUI/TelegramUI/ChatMessageItemView.swift index 592d3a429e..26bf9a57b6 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMessageItemView.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMessageItemView.swift @@ -679,7 +679,7 @@ public class ChatMessageItemView: ListViewItemNode { return nil } - func getMessageContextSourceNode() -> ContextContentContainingNode? { + func getMessageContextSourceNode() -> ContextExtractedContentContainingNode? { return nil } diff --git a/submodules/TelegramUI/TelegramUI/ChatMessageStickerItemNode.swift b/submodules/TelegramUI/TelegramUI/ChatMessageStickerItemNode.swift index f2ff3a5dcb..6218a3bc30 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMessageStickerItemNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMessageStickerItemNode.swift @@ -16,7 +16,7 @@ private let inlineBotPrefixFont = Font.regular(14.0) private let inlineBotNameFont = nameFont class ChatMessageStickerItemNode: ChatMessageItemView { - private let contextSourceNode: ContextContentContainingNode + private let contextSourceNode: ContextExtractedContentContainingNode let imageNode: TransformImageNode var textNode: TextNode? @@ -42,7 +42,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { private var currentSwipeToReplyTranslation: CGFloat = 0.0 required init() { - self.contextSourceNode = ContextContentContainingNode() + self.contextSourceNode = ContextExtractedContentContainingNode() self.imageNode = TransformImageNode() self.dateAndStatusNode = ChatMessageDateAndStatusNode() @@ -899,7 +899,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } - override func getMessageContextSourceNode() -> ContextContentContainingNode? { + override func getMessageContextSourceNode() -> ContextExtractedContentContainingNode? { return self.contextSourceNode } From c9aa7d70d6a4a9dab0d355d015aba86c309c0306 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Wed, 4 Sep 2019 00:00:49 +0400 Subject: [PATCH 2/5] Temp --- NotificationService/NotificationService.m | 31 +- .../Sources/ChatListController.swift | 42 ++ .../Sources/ContextContentContainerNode.swift | 5 +- .../Sources/ContextContentSourceNode.swift | 3 +- .../ContextUI/Sources/ContextController.swift | 374 ++++++++++++++---- .../PeekControllerGestureRecognizer.swift | 52 +-- .../Display/Display/ViewController.swift | 10 +- .../Display/ViewControllerPreviewing.swift | 19 +- .../TelegramUI/TelegramUI/AppDelegate.swift | 13 + .../TelegramUI/ChatController.swift | 6 +- .../ChatMessageInteractiveMediaNode.swift | 2 +- .../TelegramUI/FetchVideoMediaResource.swift | 152 ++++++- 12 files changed, 589 insertions(+), 120 deletions(-) diff --git a/NotificationService/NotificationService.m b/NotificationService/NotificationService.m index 54ced01308..81ba2f0015 100644 --- a/NotificationService/NotificationService.m +++ b/NotificationService/NotificationService.m @@ -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; diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index c5d4764928..ecdd070956 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -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) { diff --git a/submodules/ContextUI/Sources/ContextContentContainerNode.swift b/submodules/ContextUI/Sources/ContextContentContainerNode.swift index 4f850c33d4..3c4a43ca89 100644 --- a/submodules/ContextUI/Sources/ContextContentContainerNode.swift +++ b/submodules/ContextUI/Sources/ContextContentContainerNode.swift @@ -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) } } diff --git a/submodules/ContextUI/Sources/ContextContentSourceNode.swift b/submodules/ContextUI/Sources/ContextContentSourceNode.swift index 081d528a26..9f8fe315d8 100644 --- a/submodules/ContextUI/Sources/ContextContentSourceNode.swift +++ b/submodules/ContextUI/Sources/ContextContentSourceNode.swift @@ -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) } } diff --git a/submodules/ContextUI/Sources/ContextController.swift b/submodules/ContextUI/Sources/ContextController.swift index 2637c51cd7..9c69570c7a 100644 --- a/submodules/ContextUI/Sources/ContextController.swift +++ b/submodules/ContextUI/Sources/ContextController.swift @@ -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 } } diff --git a/submodules/Display/Display/PeekControllerGestureRecognizer.swift b/submodules/Display/Display/PeekControllerGestureRecognizer.swift index 505c039ee7..7a4d3c3fd8 100644 --- a/submodules/Display/Display/PeekControllerGestureRecognizer.swift +++ b/submodules/Display/Display/PeekControllerGestureRecognizer.swift @@ -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 } } } diff --git a/submodules/Display/Display/ViewController.swift b/submodules/Display/Display/ViewController.swift index bdaa155448..561c7c7538 100644 --- a/submodules/Display/Display/ViewController.swift +++ b/submodules/Display/Display/ViewController.swift @@ -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 } diff --git a/submodules/Display/Display/ViewControllerPreviewing.swift b/submodules/Display/Display/ViewControllerPreviewing.swift index aa5c8e9937..508d0ef8b6 100644 --- a/submodules/Display/Display/ViewControllerPreviewing.swift +++ b/submodules/Display/Display/ViewControllerPreviewing.swift @@ -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) diff --git a/submodules/TelegramUI/TelegramUI/AppDelegate.swift b/submodules/TelegramUI/TelegramUI/AppDelegate.swift index 36e3489bdb..a4f187f557 100644 --- a/submodules/TelegramUI/TelegramUI/AppDelegate.swift +++ b/submodules/TelegramUI/TelegramUI/AppDelegate.swift @@ -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 diff --git a/submodules/TelegramUI/TelegramUI/ChatController.swift b/submodules/TelegramUI/TelegramUI/ChatController.swift index 44a393b825..d2098f5c24 100644 --- a/submodules/TelegramUI/TelegramUI/ChatController.swift +++ b/submodules/TelegramUI/TelegramUI/ChatController.swift @@ -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) }) diff --git a/submodules/TelegramUI/TelegramUI/ChatMessageInteractiveMediaNode.swift b/submodules/TelegramUI/TelegramUI/ChatMessageInteractiveMediaNode.swift index 4d23ae3178..b01bd7c434 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMessageInteractiveMediaNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMessageInteractiveMediaNode.swift @@ -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() diff --git a/submodules/TelegramUI/TelegramUI/FetchVideoMediaResource.swift b/submodules/TelegramUI/TelegramUI/FetchVideoMediaResource.swift index 75f16189fc..7f12ab1f79 100644 --- a/submodules/TelegramUI/TelegramUI/FetchVideoMediaResource.swift +++ b/submodules/TelegramUI/TelegramUI/FetchVideoMediaResource.swift @@ -73,6 +73,151 @@ struct VideoConversionConfiguration { } } +private final class FetchVideoLibraryMediaResourceItem { + let priority: MediaBoxFetchPriority + let signal: Signal + let next: (MediaResourceDataFetchResult) -> Void + let error: (MediaResourceDataFetchError) -> Void + let completion: () -> Void + var isActive: Bool = false + var disposable: Disposable? + + init(priority: MediaBoxFetchPriority, signal: Signal, 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, 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 + + init() { + let queue = self.queue + self.impl = QueueLocalObject(queue: queue, generate: { + return FetchVideoLibraryMediaResourceContextImpl(queue: queue) + }) + } + + func wrap(priority: MediaBoxFetchPriority, signal: Signal) -> Signal { + 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 { 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 in let config = VideoConversionConfiguration.with(appConfiguration: appConfiguration) - return Signal { subscriber in + let signal = Signal { 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 in let config = VideoConversionConfiguration.with(appConfiguration: appConfiguration) - return Signal { subscriber in + let signal = Signal { 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) } } From d36238b687359d407c5a53b35590d3b39d1a4b72 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Sat, 7 Sep 2019 00:03:47 +0400 Subject: [PATCH 3/5] Fix context menu action selection --- submodules/ContextUI/Sources/ContextController.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/submodules/ContextUI/Sources/ContextController.swift b/submodules/ContextUI/Sources/ContextController.swift index 9d4a548041..851071299c 100644 --- a/submodules/ContextUI/Sources/ContextController.swift +++ b/submodules/ContextUI/Sources/ContextController.swift @@ -308,11 +308,11 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi } } } - gesture.externalEnded = { [weak self, weak recognizer] viewAndPoint in - guard let strongSelf = self, let recognizer = recognizer else { + gesture.externalEnded = { [weak self, weak gesture] viewAndPoint in + guard let strongSelf = self, let gesture = gesture else { return } - recognizer.externalUpdated = nil + gesture.externalUpdated = nil if strongSelf.didMoveFromInitialGesturePoint { if let (_, _) = viewAndPoint { if let highlightedActionNode = strongSelf.highlightedActionNode { From d3e2a588298087c72f245181ae1d2c2f75726abe Mon Sep 17 00:00:00 2001 From: Peter <> Date: Sat, 7 Sep 2019 00:04:00 +0400 Subject: [PATCH 4/5] Fix reactions shadow --- .../ReactionSelectionNode/Sources/ReactionContextNode.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift index cbc89c68c9..a0ecc2de81 100644 --- a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift +++ b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift @@ -61,7 +61,7 @@ private func generateBubbleImage(foreground: UIColor, diameter: CGFloat, shadowB private func generateBubbleShadowImage(shadow: UIColor, diameter: CGFloat, shadowBlur: CGFloat) -> UIImage? { return generateImage(CGSize(width: diameter + shadowBlur * 2.0, height: diameter + shadowBlur * 2.0), rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) - context.setFillColor(UIColor.white.cgColor) + context.setFillColor(shadow.cgColor) context.setShadow(offset: CGSize(), blur: shadowBlur, color: shadow.cgColor) context.fillEllipse(in: CGRect(origin: CGPoint(x: shadowBlur, y: shadowBlur), size: CGSize(width: diameter, height: diameter))) context.setShadow(offset: CGSize(), blur: 1.0, color: shadow.cgColor) From c8837e04f86e854840378e1f50f264482531140c Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sat, 7 Sep 2019 09:13:07 +0300 Subject: [PATCH 5/5] iOS 13 style segmented control iPad UI improvements --- .../contents.xcworkspacedata | 3 + .../Sources/CallListController.swift | 4 +- .../Display/Display/GenerateImage.swift | 5 +- .../Sources/HashtagSearchControllerNode.swift | 46 +- .../project.pbxproj | 4 + .../Sources/ItemListController.swift | 4 +- .../Sources/ItemListControllerNode.swift | 58 +- ...ItemListControllerSegmentedTitleView.swift | 57 +- .../ItemListUI/Sources/ItemListItem.swift | 2 + .../Sources/ItemListMaskAccessory.swift | 38 ++ .../Sources/Items/ItemListActionItem.swift | 23 +- .../Items/ItemListDisclosureItem.swift | 24 +- .../Sources/Items/ItemListSwitchItem.swift | 22 +- .../Items/ItemListTextEmptyStateItem.swift | 4 +- .../OpusBinding/Sources/OggOpusReader.m | 2 +- submodules/SegmentedControlNode/Info.plist | 22 + .../project.pbxproj | 563 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../Sources/SegmentedControlNode.h | 11 + .../Sources/SegmentedControlNode.swift | 381 ++++++++++++ .../project.pbxproj | 4 + .../RecentSessionsEmptyStateItem.swift | 8 +- .../Sources/Themes/ThemeNameGenerator.swift | 5 +- .../Themes/ThemePreviewControllerNode.swift | 2 +- .../Sources/ManagedAudioSession.swift | 4 + .../Resources/PresentationResourceKey.swift | 6 +- .../PresentationResourcesItemList.swift | 36 ++ .../ChatButtonKeyboardInputNode.swift | 2 +- .../TelegramUI/ChatController.swift | 3 +- .../TelegramUI/ChatControllerNode.swift | 6 +- .../TelegramUI/TelegramUI/ChatInputNode.swift | 2 +- .../ChatInterfaceStateContextMenus.swift | 2 +- .../TelegramUI/ChatMediaInputGifPane.swift | 19 +- .../TelegramUI/ChatMediaInputNode.swift | 37 +- .../TelegramUI/ChatMediaInputPane.swift | 2 +- .../ChatMediaInputStickerGridItem.swift | 4 +- .../ChatMediaInputStickerPane.swift | 13 +- .../ChatMediaInputTrendingPane.swift | 104 ++-- .../TelegramUI/ChatMessageItemView.swift | 3 +- .../ChatMessageMediaBubbleContentNode.swift | 13 +- .../TelegramUI/TelegramUI/ChatTitleView.swift | 1 - .../TelegramUI/GifPaneSearchContentNode.swift | 2 +- .../TelegramUI/MultiplexedVideoNode.swift | 8 +- .../TelegramUI/PaneSearchContainerNode.swift | 6 +- .../PeerMediaCollectionSectionsNode.swift | 36 +- .../PeerSelectionControllerNode.swift | 35 +- .../StickerPaneSearchContentNode.swift | 8 +- .../StickerPaneSearchGlobaltem.swift | 8 +- ...textResultsChatInputContextPanelNode.swift | 1 - ...ListContextResultsChatInputPanelItem.swift | 34 +- .../Sources/WebSearchControllerNode.swift | 46 +- 52 files changed, 1483 insertions(+), 265 deletions(-) create mode 100644 submodules/ItemListUI/Sources/ItemListMaskAccessory.swift create mode 100644 submodules/SegmentedControlNode/Info.plist create mode 100644 submodules/SegmentedControlNode/SegmentedControlNode_Xcode.xcodeproj/project.pbxproj create mode 100644 submodules/SegmentedControlNode/SegmentedControlNode_Xcode.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 submodules/SegmentedControlNode/SegmentedControlNode_Xcode.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 submodules/SegmentedControlNode/Sources/SegmentedControlNode.h create mode 100644 submodules/SegmentedControlNode/Sources/SegmentedControlNode.swift diff --git a/Telegram-iOS.xcworkspace/contents.xcworkspacedata b/Telegram-iOS.xcworkspace/contents.xcworkspacedata index 9b8384320d..fc87c6f685 100644 --- a/Telegram-iOS.xcworkspace/contents.xcworkspacedata +++ b/Telegram-iOS.xcworkspace/contents.xcworkspacedata @@ -274,6 +274,9 @@ + + diff --git a/submodules/CallListUI/Sources/CallListController.swift b/submodules/CallListUI/Sources/CallListController.swift index fa6e82e03e..18739ac6ac 100644 --- a/submodules/CallListUI/Sources/CallListController.swift +++ b/submodules/CallListUI/Sources/CallListController.swift @@ -45,7 +45,7 @@ public final class CallListController: ViewController { self.mode = mode self.presentationData = context.sharedContext.currentPresentationData.with { $0 } - self.segmentedTitleView = ItemListControllerSegmentedTitleView(segments: [self.presentationData.strings.Calls_All, self.presentationData.strings.Calls_Missed], index: 0, color: self.presentationData.theme.rootController.navigationBar.accentTextColor) + self.segmentedTitleView = ItemListControllerSegmentedTitleView(theme: self.presentationData.theme, segments: [self.presentationData.strings.Calls_All, self.presentationData.strings.Calls_Missed], selectedIndex: 0) super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData)) @@ -101,7 +101,7 @@ public final class CallListController: ViewController { private func updateThemeAndStrings() { let index = self.segmentedTitleView.index self.segmentedTitleView.segments = [self.presentationData.strings.Calls_All, self.presentationData.strings.Calls_Missed] - self.segmentedTitleView.color = self.presentationData.theme.rootController.navigationBar.accentTextColor + self.segmentedTitleView.theme = self.presentationData.theme self.segmentedTitleView.index = index self.tabBarItem.title = self.presentationData.strings.Calls_TabTitle diff --git a/submodules/Display/Display/GenerateImage.swift b/submodules/Display/Display/GenerateImage.swift index b77f26efdb..7e8b5efb9d 100644 --- a/submodules/Display/Display/GenerateImage.swift +++ b/submodules/Display/Display/GenerateImage.swift @@ -151,9 +151,8 @@ public func generateImage(_ size: CGSize, opaque: Bool = false, scale: CGFloat? guard let provider = CGDataProvider(dataInfo: bytes, data: bytes, size: length, releaseData: { bytes, _, _ in free(bytes) - }) - else { - return nil + }) else { + return nil } let bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo.byteOrder32Little.rawValue | (opaque ? CGImageAlphaInfo.noneSkipFirst.rawValue : CGImageAlphaInfo.premultipliedFirst.rawValue)) diff --git a/submodules/HashtagSearchUI/Sources/HashtagSearchControllerNode.swift b/submodules/HashtagSearchUI/Sources/HashtagSearchControllerNode.swift index ced8219be9..fe3f6e5fe5 100644 --- a/submodules/HashtagSearchUI/Sources/HashtagSearchControllerNode.swift +++ b/submodules/HashtagSearchUI/Sources/HashtagSearchControllerNode.swift @@ -6,11 +6,12 @@ import TelegramCore import TelegramPresentationData import AccountContext import ChatListUI +import SegmentedControlNode final class HashtagSearchControllerNode: ASDisplayNode { private let toolbarBackgroundNode: ASDisplayNode private let toolbarSeparatorNode: ASDisplayNode - private let segmentedControl: UISegmentedControl + private let segmentedControlNode: SegmentedControlNode let listNode: ListView var chatController: ChatController? @@ -35,9 +36,11 @@ final class HashtagSearchControllerNode: ASDisplayNode { self.toolbarSeparatorNode = ASDisplayNode() self.toolbarSeparatorNode.backgroundColor = theme.rootController.navigationBar.separatorColor - self.segmentedControl = UISegmentedControl(items: [peer?.displayTitle ?? "", strings.HashtagSearch_AllChats]) - self.segmentedControl.tintColor = theme.rootController.navigationBar.accentTextColor - self.segmentedControl.selectedSegmentIndex = 0 + let items = [ + peer?.displayTitle ?? "", + strings.HashtagSearch_AllChats + ] + self.segmentedControlNode = SegmentedControlNode(theme: SegmentedControlTheme(theme: theme), items: items.map { SegmentedControlItem(title: $0) }, selectedIndex: 0) if let peer = peer { self.chatController = context.sharedContext.makeChatController(context: context, chatLocation: .peer(peer.id), subject: nil, botStart: nil, mode: .inline) @@ -56,7 +59,17 @@ final class HashtagSearchControllerNode: ASDisplayNode { self.addSubnode(self.listNode) self.listNode.isHidden = true - self.segmentedControl.addTarget(self, action: #selector(self.indexChanged), for: .valueChanged) + self.segmentedControlNode.selectedIndexChanged = { [weak self] index in + if let strongSelf = self { + if index == 0 { + strongSelf.chatController?.displayNode.isHidden = false + strongSelf.listNode.isHidden = true + } else { + strongSelf.chatController?.displayNode.isHidden = true + strongSelf.listNode.isHidden = false + } + } + } } func enqueueTransition(_ transition: ChatListSearchContainerTransition, firstTime: Bool) { @@ -84,8 +97,7 @@ final class HashtagSearchControllerNode: ASDisplayNode { if self.chatController != nil && self.toolbarBackgroundNode.supernode == nil { self.addSubnode(self.toolbarBackgroundNode) self.addSubnode(self.toolbarSeparatorNode) - - self.view.addSubview(self.segmentedControl) + self.addSubnode(self.segmentedControlNode) } var insets = layout.insets(options: [.input]) @@ -97,10 +109,8 @@ final class HashtagSearchControllerNode: ASDisplayNode { transition.updateFrame(node: self.toolbarBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: panelY), size: CGSize(width: layout.size.width, height: toolbarHeight))) transition.updateFrame(node: self.toolbarSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: panelY + toolbarHeight), size: CGSize(width: layout.size.width, height: UIScreenPixel))) - var controlSize = self.segmentedControl.sizeThatFits(layout.size) - controlSize.width = layout.size.width - 14.0 * 2.0 - - transition.updateFrame(view: self.segmentedControl, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - controlSize.width) / 2.0), y: panelY + floor((toolbarHeight - controlSize.height) / 2.0)), size: controlSize)) + let controlSize = self.segmentedControlNode.updateLayout(.stretchToFill(width: layout.size.width - 14.0 * 2.0), transition: transition) + transition.updateFrame(node: self.segmentedControlNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - controlSize.width) / 2.0), y: panelY + floor((toolbarHeight - controlSize.height) / 2.0)), size: controlSize)) if let chatController = self.chatController { insets.top += toolbarHeight - 4.0 @@ -146,21 +156,11 @@ final class HashtagSearchControllerNode: ASDisplayNode { self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) - if !hasValidLayout { - hasValidLayout = true + if !self.hasValidLayout { + self.hasValidLayout = true while !self.enqueuedTransitions.isEmpty { self.dequeueTransition() } } } - - @objc private func indexChanged() { - if self.segmentedControl.selectedSegmentIndex == 0 { - self.chatController?.displayNode.isHidden = false - self.listNode.isHidden = true - } else { - self.chatController?.displayNode.isHidden = true - self.listNode.isHidden = false - } - } } diff --git a/submodules/ItemListUI/ItemListUI_Xcode.xcodeproj/project.pbxproj b/submodules/ItemListUI/ItemListUI_Xcode.xcodeproj/project.pbxproj index 6a0b9a1c24..7bafe259fc 100644 --- a/submodules/ItemListUI/ItemListUI_Xcode.xcodeproj/project.pbxproj +++ b/submodules/ItemListUI/ItemListUI_Xcode.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 09CE5B922322985500743FF4 /* ItemListMaskAccessory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09CE5B912322985500743FF4 /* ItemListMaskAccessory.swift */; }; D060182122F35C2300796784 /* ProgressNavigationButtonNode.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D060182022F35C2300796784 /* ProgressNavigationButtonNode.framework */; }; D060185322F35E1F00796784 /* ItemListMultilineInputItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D060184922F35E1E00796784 /* ItemListMultilineInputItem.swift */; }; D060185422F35E1F00796784 /* ItemListTextWithLabelItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D060184A22F35E1E00796784 /* ItemListTextWithLabelItem.swift */; }; @@ -50,6 +51,7 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 09CE5B912322985500743FF4 /* ItemListMaskAccessory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemListMaskAccessory.swift; sourceTree = ""; }; D060182022F35C2300796784 /* ProgressNavigationButtonNode.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = ProgressNavigationButtonNode.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D060184922F35E1E00796784 /* ItemListMultilineInputItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemListMultilineInputItem.swift; sourceTree = ""; }; D060184A22F35E1E00796784 /* ItemListTextWithLabelItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemListTextWithLabelItem.swift; sourceTree = ""; }; @@ -168,6 +170,7 @@ D0D3286322F3366500D07EE2 /* ItemListControllerSearch.swift */, D0D3286122F3366500D07EE2 /* ItemListControllerSegmentedTitleView.swift */, D0D3289922F345C500D07EE2 /* ItemListItem.swift */, + 09CE5B912322985500743FF4 /* ItemListMaskAccessory.swift */, D060186022F35F6B00796784 /* ItemListRevealOptionsNode.swift */, D06018BA22F3663900796784 /* ItemListEditableDeleteControlNode.swift */, D06018B822F3663800796784 /* ItemListEditableReorderControlNode.swift */, @@ -294,6 +297,7 @@ D060185422F35E1F00796784 /* ItemListTextWithLabelItem.swift in Sources */, D0C9BFB222FE327700FAB518 /* ItemListPlaceholderItem.swift in Sources */, D0D3286722F3366600D07EE2 /* ItemListController.swift in Sources */, + 09CE5B922322985500743FF4 /* ItemListMaskAccessory.swift in Sources */, D0D3286822F3366600D07EE2 /* ItemListControllerSearch.swift in Sources */, D060185722F35E1F00796784 /* ItemListSwitchItem.swift in Sources */, D0D3286622F3366600D07EE2 /* ItemListControllerSegmentedTitleView.swift in Sources */, diff --git a/submodules/ItemListUI/Sources/ItemListController.swift b/submodules/ItemListUI/Sources/ItemListController.swift index 6efc7cfd66..c338291667 100644 --- a/submodules/ItemListUI/Sources/ItemListController.swift +++ b/submodules/ItemListUI/Sources/ItemListController.swift @@ -283,7 +283,7 @@ open class ItemListController: ViewController, KeyShor if let segmentedTitleView = strongSelf.segmentedTitleView, segmentedTitleView.segments == sections { segmentedTitleView.index = index } else { - let segmentedTitleView = ItemListControllerSegmentedTitleView(segments: sections, index: index, color: controllerState.theme.rootController.navigationBar.accentTextColor) + let segmentedTitleView = ItemListControllerSegmentedTitleView(theme: controllerState.theme, segments: sections, selectedIndex: index) strongSelf.segmentedTitleView = segmentedTitleView strongSelf.navigationItem.titleView = strongSelf.segmentedTitleView segmentedTitleView.indexUpdated = { index in @@ -406,7 +406,7 @@ open class ItemListController: ViewController, KeyShor strongSelf.navigationBar?.updatePresentationData(NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: strongSelf.theme), strings: NavigationBarStrings(presentationStrings: strongSelf.strings))) strongSelf.statusBar.statusBarStyle = strongSelf.theme.rootController.statusBarStyle.style - strongSelf.segmentedTitleView?.color = controllerState.theme.rootController.navigationBar.accentTextColor + strongSelf.segmentedTitleView?.theme = controllerState.theme var items = strongSelf.navigationItem.rightBarButtonItems ?? [] for i in 0 ..< strongSelf.rightNavigationButtonTitleAndStyle.count { diff --git a/submodules/ItemListUI/Sources/ItemListControllerNode.swift b/submodules/ItemListUI/Sources/ItemListControllerNode.swift index 358ad954c3..5524d79f49 100644 --- a/submodules/ItemListUI/Sources/ItemListControllerNode.swift +++ b/submodules/ItemListUI/Sources/ItemListControllerNode.swift @@ -162,6 +162,8 @@ open class ItemListControllerNode: ASDisplayNode, UISc private let navigationBar: NavigationBar public let listNode: ListView + private let leftOverlayNode: ASDisplayNode + private let rightOverlayNode: ASDisplayNode private var emptyStateItem: ItemListControllerEmptyStateItem? private var emptyStateNode: ItemListControllerEmptyStateItemNode? @@ -204,6 +206,8 @@ open class ItemListControllerNode: ASDisplayNode, UISc self.updateNavigationOffset = updateNavigationOffset self.listNode = ListView() + self.leftOverlayNode = ASDisplayNode() + self.rightOverlayNode = ASDisplayNode() super.init() @@ -351,15 +355,45 @@ open class ItemListControllerNode: ASDisplayNode, UISc var insets = layout.insets(options: [.input]) insets.top += navigationBarHeight - insets.left += layout.safeInsets.left - insets.right += layout.safeInsets.right + + var addedInsets: UIEdgeInsets? + if case .tablet = layout.deviceMetrics.type, layout.size.width > 460.0 { + let inset = max(20.0, floor((layout.size.width - 674.0) / 2.0)) + insets.left += inset + insets.right += inset + addedInsets = UIEdgeInsets(top: 0.0, left: inset, bottom: 0.0, right: inset) + + if self.leftOverlayNode.supernode == nil { + self.addSubnode(self.leftOverlayNode) + } + if self.rightOverlayNode.supernode == nil { + self.addSubnode(self.rightOverlayNode) + } + } else { + insets.left += layout.safeInsets.left + insets.right += layout.safeInsets.right + + if self.leftOverlayNode.supernode != nil { + self.leftOverlayNode.removeFromSupernode() + } + if self.rightOverlayNode.supernode != nil { + self.rightOverlayNode.removeFromSupernode() + } + } self.listNode.bounds = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height) self.listNode.position = CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0) self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: layout.size, insets: insets, duration: duration, curve: listViewCurve), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + self.leftOverlayNode.frame = CGRect(x: 0.0, y: insets.top + 1.0, width: insets.left, height: layout.size.height - insets.top) + self.rightOverlayNode.frame = CGRect(x: layout.size.width - insets.right, y: insets.top + 1.0, width: insets.right, height: layout.size.height - insets.top) + if let emptyStateNode = self.emptyStateNode { + var layout = layout + if let addedInsets = addedInsets { + layout = layout.addedInsets(insets: addedInsets) + } emptyStateNode.updateLayout(layout: layout, navigationBarHeight: navigationBarHeight, transition: transition) } @@ -401,9 +435,13 @@ open class ItemListControllerNode: ASDisplayNode, UISc case .plain: self.backgroundColor = transition.theme.list.plainBackgroundColor self.listNode.backgroundColor = transition.theme.list.plainBackgroundColor + self.leftOverlayNode.backgroundColor = transition.theme.list.plainBackgroundColor + self.rightOverlayNode.backgroundColor = transition.theme.list.plainBackgroundColor case .blocks: self.backgroundColor = transition.theme.list.blocksBackgroundColor self.listNode.backgroundColor = transition.theme.list.blocksBackgroundColor + self.leftOverlayNode.backgroundColor = transition.theme.list.blocksBackgroundColor + self.rightOverlayNode.backgroundColor = transition.theme.list.blocksBackgroundColor } } } @@ -416,9 +454,13 @@ open class ItemListControllerNode: ASDisplayNode, UISc case .plain: self.backgroundColor = transition.theme.list.plainBackgroundColor self.listNode.backgroundColor = transition.theme.list.plainBackgroundColor + self.leftOverlayNode.backgroundColor = transition.theme.list.plainBackgroundColor + self.rightOverlayNode.backgroundColor = transition.theme.list.plainBackgroundColor case .blocks: self.backgroundColor = transition.theme.list.blocksBackgroundColor self.listNode.backgroundColor = transition.theme.list.blocksBackgroundColor + self.leftOverlayNode.backgroundColor = transition.theme.list.blocksBackgroundColor + self.rightOverlayNode.backgroundColor = transition.theme.list.blocksBackgroundColor } } } @@ -553,10 +595,6 @@ open class ItemListControllerNode: ASDisplayNode, UISc if itemTag.isEqual(to: ensureVisibleItemTag) { if let itemNode = itemNode as? ListViewItemNode { strongSelf.listNode.ensureItemNodeVisible(itemNode) - /*itemNode.setHighlighted(true, at: CGPoint(), animated: false) - Queue.mainQueue().after(1.0, { - itemNode.setHighlighted(false, at: CGPoint(), animated: true) - })*/ applied = true } } @@ -613,21 +651,13 @@ open class ItemListControllerNode: ASDisplayNode, UISc open func scrollViewDidScroll(_ scrollView: UIScrollView) { let distanceFromEquilibrium = scrollView.contentOffset.y - scrollView.contentSize.height / 3.0 - - //let transition = 1.0 - min(1.0, max(0.0, abs(distanceFromEquilibrium) / 50.0)) - self.updateNavigationOffset(-distanceFromEquilibrium) - - /*if let toolbarNode = toolbarNode { - toolbarNode.layer.position = CGPoint(x: toolbarNode.layer.position.x, y: self.bounds.size.height - toolbarNode.bounds.size.height / 2.0 + (1.0 - transition) * toolbarNode.bounds.size.height) - }*/ } open func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { targetContentOffset.pointee = scrollView.contentOffset let scrollVelocity = scrollView.panGestureRecognizer.velocity(in: scrollView) - if abs(scrollVelocity.y) > 200.0 { self.animateOut() } diff --git a/submodules/ItemListUI/Sources/ItemListControllerSegmentedTitleView.swift b/submodules/ItemListUI/Sources/ItemListControllerSegmentedTitleView.swift index 1cff70bd53..b9efd223e2 100644 --- a/submodules/ItemListUI/Sources/ItemListControllerSegmentedTitleView.swift +++ b/submodules/ItemListUI/Sources/ItemListControllerSegmentedTitleView.swift @@ -1,50 +1,49 @@ import Foundation import UIKit +import SegmentedControlNode +import TelegramPresentationData public final class ItemListControllerSegmentedTitleView: UIView { + private let segmentedControlNode: SegmentedControlNode + + public var theme: PresentationTheme { + didSet { + self.segmentedControlNode.updateTheme(SegmentedControlTheme(theme: self.theme)) + } + } public var segments: [String] { didSet { if self.segments != oldValue { - self.control.removeAllSegments() - var index = 0 - for segment in self.segments { - self.control.insertSegment(withTitle: segment, at: index, animated: false) - index += 1 - } + self.segmentedControlNode.items = self.segments.map { SegmentedControlItem(title: $0) } self.setNeedsLayout() } } } public var index: Int { - didSet { - self.control.selectedSegmentIndex = self.index + get { + return self.segmentedControlNode.selectedIndex + } + set { + self.segmentedControlNode.selectedIndex = newValue } } - private let control: UISegmentedControl - public var indexUpdated: ((Int) -> Void)? - public var color: UIColor { - didSet { - self.control.tintColor = self.color - } - } - - public init(segments: [String], index: Int, color: UIColor) { + public init(theme: PresentationTheme, segments: [String], selectedIndex: Int) { + self.theme = theme self.segments = segments - self.index = index - self.color = color - self.control = UISegmentedControl(items: segments) - self.control.selectedSegmentIndex = index - self.control.tintColor = color + self.segmentedControlNode = SegmentedControlNode(theme: SegmentedControlTheme(theme: theme), items: segments.map { SegmentedControlItem(title: $0) }, selectedIndex: selectedIndex) super.init(frame: CGRect()) - self.addSubview(self.control) - self.control.addTarget(self, action: #selector(indexChanged), for: .valueChanged) + self.segmentedControlNode.selectedIndexChanged = { [weak self] index in + self?.indexUpdated?(index) + } + + self.addSubnode(self.segmentedControlNode) } required public init?(coder aDecoder: NSCoder) { @@ -55,13 +54,7 @@ public final class ItemListControllerSegmentedTitleView: UIView { super.layoutSubviews() let size = self.bounds.size - - var controlSize = self.control.sizeThatFits(size) - controlSize.width = min(size.width, max(160.0, controlSize.width)) - self.control.frame = CGRect(origin: CGPoint(x: floor((size.width - controlSize.width) / 2.0), y: floor((size.height - controlSize.height) / 2.0)), size: controlSize) - } - - @objc private func indexChanged() { - self.indexUpdated?(self.control.selectedSegmentIndex) + let controlSize = self.segmentedControlNode.updateLayout(.sizeToFit(maximumWidth: size.width, minimumWidth: 160.0), transition: .immediate) + self.segmentedControlNode.frame = CGRect(origin: CGPoint(x: floor((size.width - controlSize.width) / 2.0), y: floor((size.height - controlSize.height) / 2.0)), size: controlSize) } } diff --git a/submodules/ItemListUI/Sources/ItemListItem.swift b/submodules/ItemListUI/Sources/ItemListItem.swift index 78e49e9e3c..40e2ea4161 100644 --- a/submodules/ItemListUI/Sources/ItemListItem.swift +++ b/submodules/ItemListUI/Sources/ItemListItem.swift @@ -14,6 +14,8 @@ public protocol ItemListItem { } public extension ItemListItem { + //let accessoryItem: ListViewAccessoryItem? + var isAlwaysPlain: Bool { return false } diff --git a/submodules/ItemListUI/Sources/ItemListMaskAccessory.swift b/submodules/ItemListUI/Sources/ItemListMaskAccessory.swift new file mode 100644 index 0000000000..8d6f0c3862 --- /dev/null +++ b/submodules/ItemListUI/Sources/ItemListMaskAccessory.swift @@ -0,0 +1,38 @@ +import Foundation +import UIKit +import Display + +final class ItemListMaskAccessoryItem: ListViewAccessoryItem { + private let sectionId: Int32 + + init(sectionId: Int32) { + self.sectionId = sectionId + } + + func isEqualToItem(_ other: ListViewAccessoryItem) -> Bool { + if case let other as ItemListMaskAccessoryItem = other { + return self.sectionId == other.sectionId + } + + return false + } + + func node(synchronous: Bool) -> ListViewAccessoryItemNode { + let node = ItemListMaskAccessoryItemItemNode() + node.frame = CGRect(origin: CGPoint(), size: CGSize(width: 38.0, height: 38.0)) + return node + } +} + +final class ItemListMaskAccessoryItemItemNode: ListViewAccessoryItemNode { + let node: ASDisplayNode + + override init() { + self.node = ASDisplayNode() + self.node.backgroundColor = .red + + super.init() + + self.addSubnode(self.node) + } +} diff --git a/submodules/ItemListUI/Sources/Items/ItemListActionItem.swift b/submodules/ItemListUI/Sources/Items/ItemListActionItem.swift index b29cbafe1b..d85e67ed42 100644 --- a/submodules/ItemListUI/Sources/Items/ItemListActionItem.swift +++ b/submodules/ItemListUI/Sources/Items/ItemListActionItem.swift @@ -92,6 +92,7 @@ public class ItemListActionItemNode: ListViewItemNode, ItemListItemNode { private let topStripeNode: ASDisplayNode private let bottomStripeNode: ASDisplayNode private let highlightedBackgroundNode: ASDisplayNode + private let maskNode: ASImageNode private let titleNode: TextNode @@ -107,7 +108,7 @@ public class ItemListActionItemNode: ListViewItemNode, ItemListItemNode { self.backgroundNode = ASDisplayNode() self.backgroundNode.isLayerBacked = true self.backgroundNode.backgroundColor = .white - + self.maskNode = ASImageNode() self.topStripeNode = ASDisplayNode() self.topStripeNode.isLayerBacked = true @@ -216,7 +217,9 @@ public class ItemListActionItemNode: ListViewItemNode, ItemListItemNode { if strongSelf.bottomStripeNode.supernode == nil { strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 0) } - + if strongSelf.maskNode.supernode != nil { + strongSelf.maskNode.removeFromSupernode() + } strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: leftInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - leftInset, height: separatorHeight)) case .blocks: if strongSelf.backgroundNode.supernode == nil { @@ -228,11 +231,19 @@ public class ItemListActionItemNode: ListViewItemNode, ItemListItemNode { if strongSelf.bottomStripeNode.supernode == nil { strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2) } + if strongSelf.maskNode.supernode == nil { + strongSelf.insertSubnode(strongSelf.maskNode, at: 3) + } + + let hasCorners = params.width > 480 + var hasTopCorners = false + var hasBottomCorners = false switch neighbors.top { case .sameSection(false): strongSelf.topStripeNode.isHidden = true default: - strongSelf.topStripeNode.isHidden = false + hasTopCorners = true + strongSelf.topStripeNode.isHidden = hasCorners } let bottomStripeInset: CGFloat let bottomStripeOffset: CGFloat @@ -243,8 +254,14 @@ public class ItemListActionItemNode: ListViewItemNode, ItemListItemNode { default: bottomStripeInset = 0.0 bottomStripeOffset = 0.0 + hasBottomCorners = true + strongSelf.bottomStripeNode.isHidden = hasCorners } + + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) + strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight)) strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight)) } diff --git a/submodules/ItemListUI/Sources/Items/ItemListDisclosureItem.swift b/submodules/ItemListUI/Sources/Items/ItemListDisclosureItem.swift index 6986cf2537..abc9581317 100644 --- a/submodules/ItemListUI/Sources/Items/ItemListDisclosureItem.swift +++ b/submodules/ItemListUI/Sources/Items/ItemListDisclosureItem.swift @@ -108,6 +108,7 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode { private let topStripeNode: ASDisplayNode private let bottomStripeNode: ASDisplayNode private let highlightedBackgroundNode: ASDisplayNode + private let maskNode: ASImageNode let iconNode: ASImageNode let titleNode: TextNode @@ -137,6 +138,8 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode { self.backgroundNode.isLayerBacked = true self.backgroundNode.backgroundColor = .white + self.maskNode = ASImageNode() + self.topStripeNode = ASDisplayNode() self.topStripeNode.isLayerBacked = true @@ -357,7 +360,9 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode { if strongSelf.bottomStripeNode.supernode == nil { strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 0) } - + if strongSelf.maskNode.supernode != nil { + strongSelf.maskNode.removeFromSupernode() + } strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: leftInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - leftInset, height: separatorHeight)) case .blocks: if strongSelf.backgroundNode.supernode == nil { @@ -369,11 +374,19 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode { if strongSelf.bottomStripeNode.supernode == nil { strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2) } + if strongSelf.maskNode.supernode == nil { + strongSelf.insertSubnode(strongSelf.maskNode, at: 3) + } + + let hasCorners = params.width > 480 + var hasTopCorners = false + var hasBottomCorners = false switch neighbors.top { case .sameSection(false): strongSelf.topStripeNode.isHidden = true default: - strongSelf.topStripeNode.isHidden = false + hasTopCorners = true + strongSelf.topStripeNode.isHidden = hasCorners } let bottomStripeInset: CGFloat switch neighbors.bottom { @@ -381,10 +394,15 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode { bottomStripeInset = leftInset default: bottomStripeInset = 0.0 + hasBottomCorners = true + strongSelf.bottomStripeNode.isHidden = hasCorners } + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) - strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight)) + strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) + strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: leftInset, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight)) strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight)) } diff --git a/submodules/ItemListUI/Sources/Items/ItemListSwitchItem.swift b/submodules/ItemListUI/Sources/Items/ItemListSwitchItem.swift index a7113d6474..d01ded7159 100644 --- a/submodules/ItemListUI/Sources/Items/ItemListSwitchItem.swift +++ b/submodules/ItemListUI/Sources/Items/ItemListSwitchItem.swift @@ -114,6 +114,7 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode { private let topStripeNode: ASDisplayNode private let bottomStripeNode: ASDisplayNode private let highlightedBackgroundNode: ASDisplayNode + private let maskNode: ASImageNode private let titleNode: TextNode private var switchNode: ASDisplayNode & ItemListSwitchNodeImpl @@ -133,6 +134,8 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode { self.backgroundNode.isLayerBacked = true self.backgroundNode.backgroundColor = .white + self.maskNode = ASImageNode() + self.topStripeNode = ASDisplayNode() self.topStripeNode.isLayerBacked = true @@ -299,7 +302,9 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode { if strongSelf.bottomStripeNode.supernode == nil { strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 0) } - + if strongSelf.maskNode.supernode != nil { + strongSelf.maskNode.removeFromSupernode() + } strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: leftInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - leftInset, height: separatorHeight)) case .blocks: if strongSelf.backgroundNode.supernode == nil { @@ -311,11 +316,19 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode { if strongSelf.bottomStripeNode.supernode == nil { strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2) } + if strongSelf.maskNode.supernode == nil { + strongSelf.insertSubnode(strongSelf.maskNode, at: 3) + } + + let hasCorners = params.width > 480 + var hasTopCorners = false + var hasBottomCorners = false switch neighbors.top { case .sameSection(false): strongSelf.topStripeNode.isHidden = true default: - strongSelf.topStripeNode.isHidden = false + hasTopCorners = true + strongSelf.topStripeNode.isHidden = hasCorners } let bottomStripeInset: CGFloat switch neighbors.bottom { @@ -323,9 +336,14 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode { bottomStripeInset = 16.0 + params.leftInset default: bottomStripeInset = 0.0 + hasBottomCorners = true + strongSelf.bottomStripeNode.isHidden = hasCorners } + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) + strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight)) strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight)) } diff --git a/submodules/ItemListUI/Sources/Items/ItemListTextEmptyStateItem.swift b/submodules/ItemListUI/Sources/Items/ItemListTextEmptyStateItem.swift index 425235c863..ea4c91925a 100644 --- a/submodules/ItemListUI/Sources/Items/ItemListTextEmptyStateItem.swift +++ b/submodules/ItemListUI/Sources/Items/ItemListTextEmptyStateItem.swift @@ -60,7 +60,7 @@ public final class ItemListTextEmptyStateItemNode: ItemListControllerEmptyStateI self.validLayout = (layout, navigationBarHeight) var insets = layout.insets(options: [.statusBar]) insets.top += navigationBarHeight - let textSize = self.textNode.measure(CGSize(width: layout.size.width - 40.0 - layout.safeInsets.left - layout.safeInsets.right, height: max(1.0, layout.size.height - insets.top - insets.bottom))) - self.textNode.frame = CGRect(origin: CGPoint(x: layout.safeInsets.left + floor((layout.size.width - layout.safeInsets.left - layout.safeInsets.right - textSize.width) / 2.0), y: insets.top + floor((layout.size.height - insets.top - insets.bottom - textSize.height) / 2.0)), size: textSize) + let textSize = self.textNode.measure(CGSize(width: layout.size.width - 40.0 - layout.safeInsets.left - layout.safeInsets.right - layout.intrinsicInsets.left - layout.intrinsicInsets.right, height: max(1.0, layout.size.height - insets.top - insets.bottom))) + self.textNode.frame = CGRect(origin: CGPoint(x: layout.safeInsets.left + layout.intrinsicInsets.left + floor((layout.size.width - layout.safeInsets.left - layout.safeInsets.right - layout.intrinsicInsets.left - layout.intrinsicInsets.right - textSize.width) / 2.0), y: insets.top + floor((layout.size.height - insets.top - insets.bottom - textSize.height) / 2.0)), size: textSize) } } diff --git a/submodules/OpusBinding/Sources/OggOpusReader.m b/submodules/OpusBinding/Sources/OggOpusReader.m index bf569da19f..5e8bd5af53 100644 --- a/submodules/OpusBinding/Sources/OggOpusReader.m +++ b/submodules/OpusBinding/Sources/OggOpusReader.m @@ -10,7 +10,7 @@ @implementation OggOpusReader -- (instancetype _Nullable)init:(NSString *)path { +- (instancetype _Nullable)initWithPath:(NSString *)path { self = [super init]; if (self != nil) { int error = OPUS_OK; diff --git a/submodules/SegmentedControlNode/Info.plist b/submodules/SegmentedControlNode/Info.plist new file mode 100644 index 0000000000..e1fe4cfb7b --- /dev/null +++ b/submodules/SegmentedControlNode/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/submodules/SegmentedControlNode/SegmentedControlNode_Xcode.xcodeproj/project.pbxproj b/submodules/SegmentedControlNode/SegmentedControlNode_Xcode.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..d4d9d0812c --- /dev/null +++ b/submodules/SegmentedControlNode/SegmentedControlNode_Xcode.xcodeproj/project.pbxproj @@ -0,0 +1,563 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 09CE5B9023225AC200743FF4 /* TelegramPresentationData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 09CE5B8F23225AC200743FF4 /* TelegramPresentationData.framework */; }; + D060188A22F3604000796784 /* SegmentedControlNode.h in Headers */ = {isa = PBXBuildFile; fileRef = D060188822F3604000796784 /* SegmentedControlNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D060189522F3615800796784 /* SegmentedControlNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D060189422F3615800796784 /* SegmentedControlNode.swift */; }; + D060189822F3616300796784 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D060189722F3616300796784 /* Foundation.framework */; }; + D060189A22F3616600796784 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D060189922F3616600796784 /* UIKit.framework */; }; + D060189C22F3616A00796784 /* AsyncDisplayKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D060189B22F3616A00796784 /* AsyncDisplayKit.framework */; }; + D060189E22F3616E00796784 /* LegacyComponents.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D060189D22F3616E00796784 /* LegacyComponents.framework */; }; + D0A0B53922F370DF00628AF3 /* Display.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0A0B53822F370DF00628AF3 /* Display.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 09CE5B8F23225AC200743FF4 /* TelegramPresentationData.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = TelegramPresentationData.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D060188522F3604000796784 /* SegmentedControlNode.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SegmentedControlNode.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D060188822F3604000796784 /* SegmentedControlNode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SegmentedControlNode.h; sourceTree = ""; }; + D060188922F3604000796784 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D060189422F3615800796784 /* SegmentedControlNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SegmentedControlNode.swift; sourceTree = ""; }; + D060189722F3616300796784 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + D060189922F3616600796784 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; + D060189B22F3616A00796784 /* AsyncDisplayKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = AsyncDisplayKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D060189D22F3616E00796784 /* LegacyComponents.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = LegacyComponents.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D0A0B53822F370DF00628AF3 /* Display.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Display.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + D060188222F3604000796784 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 09CE5B9023225AC200743FF4 /* TelegramPresentationData.framework in Frameworks */, + D0A0B53922F370DF00628AF3 /* Display.framework in Frameworks */, + D060189E22F3616E00796784 /* LegacyComponents.framework in Frameworks */, + D060189C22F3616A00796784 /* AsyncDisplayKit.framework in Frameworks */, + D060189A22F3616600796784 /* UIKit.framework in Frameworks */, + D060189822F3616300796784 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + D060187B22F3604000796784 = { + isa = PBXGroup; + children = ( + D060188922F3604000796784 /* Info.plist */, + D060188722F3604000796784 /* Sources */, + D060188622F3604000796784 /* Products */, + D060189622F3616300796784 /* Frameworks */, + ); + sourceTree = ""; + }; + D060188622F3604000796784 /* Products */ = { + isa = PBXGroup; + children = ( + D060188522F3604000796784 /* SegmentedControlNode.framework */, + ); + name = Products; + sourceTree = ""; + }; + D060188722F3604000796784 /* Sources */ = { + isa = PBXGroup; + children = ( + D060189422F3615800796784 /* SegmentedControlNode.swift */, + D060188822F3604000796784 /* SegmentedControlNode.h */, + ); + path = Sources; + sourceTree = ""; + }; + D060189622F3616300796784 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 09CE5B8F23225AC200743FF4 /* TelegramPresentationData.framework */, + D0A0B53822F370DF00628AF3 /* Display.framework */, + D060189D22F3616E00796784 /* LegacyComponents.framework */, + D060189B22F3616A00796784 /* AsyncDisplayKit.framework */, + D060189922F3616600796784 /* UIKit.framework */, + D060189722F3616300796784 /* Foundation.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + D060188022F3604000796784 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + D060188A22F3604000796784 /* SegmentedControlNode.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + D060188422F3604000796784 /* SegmentedControlNode */ = { + isa = PBXNativeTarget; + buildConfigurationList = D060188D22F3604000796784 /* Build configuration list for PBXNativeTarget "SegmentedControlNode" */; + buildPhases = ( + D060188022F3604000796784 /* Headers */, + D060188122F3604000796784 /* Sources */, + D060188222F3604000796784 /* Frameworks */, + D060188322F3604000796784 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = SegmentedControlNode; + productName = SegmentedControlNode; + productReference = D060188522F3604000796784 /* SegmentedControlNode.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + D060187C22F3604000796784 /* Project object */ = { + isa = PBXProject; + attributes = { + DefaultBuildSystemTypeForWorkspace = Latest; + LastUpgradeCheck = 1010; + ORGANIZATIONNAME = "Telegram Messenger LLP"; + TargetAttributes = { + D060188422F3604000796784 = { + CreatedOnToolsVersion = 10.1; + LastSwiftMigration = 1010; + }; + }; + }; + buildConfigurationList = D060187F22F3604000796784 /* Build configuration list for PBXProject "SegmentedControlNode_Xcode" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = D060187B22F3604000796784; + productRefGroup = D060188622F3604000796784 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + D060188422F3604000796784 /* SegmentedControlNode */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + D060188322F3604000796784 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + D060188122F3604000796784 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D060189522F3615800796784 /* SegmentedControlNode.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + D060188B22F3604000796784 /* DebugAppStoreLLC */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = DebugAppStoreLLC; + }; + D060188C22F3604000796784 /* ReleaseAppStoreLLC */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = ReleaseAppStoreLLC; + }; + D060188E22F3604000796784 /* DebugAppStoreLLC */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Manual; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.SegmentedControlNode; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = DebugAppStoreLLC; + }; + D060188F22F3604000796784 /* ReleaseAppStoreLLC */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Manual; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.SegmentedControlNode; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = ReleaseAppStoreLLC; + }; + D060189022F360BF00796784 /* DebugHockeyapp */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = DebugHockeyapp; + }; + D060189122F360BF00796784 /* DebugHockeyapp */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Manual; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.SegmentedControlNode; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = DebugHockeyapp; + }; + D060189222F360CA00796784 /* ReleaseHockeyappInternal */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = ReleaseHockeyappInternal; + }; + D060189322F360CA00796784 /* ReleaseHockeyappInternal */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Manual; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACH_O_TYPE = staticlib; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.SegmentedControlNode; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = ReleaseHockeyappInternal; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + D060187F22F3604000796784 /* Build configuration list for PBXProject "SegmentedControlNode_Xcode" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D060188B22F3604000796784 /* DebugAppStoreLLC */, + D060189022F360BF00796784 /* DebugHockeyapp */, + D060188C22F3604000796784 /* ReleaseAppStoreLLC */, + D060189222F360CA00796784 /* ReleaseHockeyappInternal */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = ReleaseAppStoreLLC; + }; + D060188D22F3604000796784 /* Build configuration list for PBXNativeTarget "SegmentedControlNode" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D060188E22F3604000796784 /* DebugAppStoreLLC */, + D060189122F360BF00796784 /* DebugHockeyapp */, + D060188F22F3604000796784 /* ReleaseAppStoreLLC */, + D060189322F360CA00796784 /* ReleaseHockeyappInternal */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = ReleaseAppStoreLLC; + }; +/* End XCConfigurationList section */ + }; + rootObject = D060187C22F3604000796784 /* Project object */; +} diff --git a/submodules/SegmentedControlNode/SegmentedControlNode_Xcode.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/submodules/SegmentedControlNode/SegmentedControlNode_Xcode.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..919434a625 --- /dev/null +++ b/submodules/SegmentedControlNode/SegmentedControlNode_Xcode.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/submodules/SegmentedControlNode/SegmentedControlNode_Xcode.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/submodules/SegmentedControlNode/SegmentedControlNode_Xcode.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000..18d981003d --- /dev/null +++ b/submodules/SegmentedControlNode/SegmentedControlNode_Xcode.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/submodules/SegmentedControlNode/Sources/SegmentedControlNode.h b/submodules/SegmentedControlNode/Sources/SegmentedControlNode.h new file mode 100644 index 0000000000..a0347abfa0 --- /dev/null +++ b/submodules/SegmentedControlNode/Sources/SegmentedControlNode.h @@ -0,0 +1,11 @@ +#import + +//! Project version number for SegmentedControlNode. +FOUNDATION_EXPORT double SegmentedControlNodeVersionNumber; + +//! Project version string for SegmentedControlNode. +FOUNDATION_EXPORT const unsigned char SegmentedControlNodeVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/submodules/SegmentedControlNode/Sources/SegmentedControlNode.swift b/submodules/SegmentedControlNode/Sources/SegmentedControlNode.swift new file mode 100644 index 0000000000..29e33f9d36 --- /dev/null +++ b/submodules/SegmentedControlNode/Sources/SegmentedControlNode.swift @@ -0,0 +1,381 @@ +import Foundation +import Display +import UIKit +import AsyncDisplayKit +import TelegramPresentationData + +private let textFont = Font.regular(13.0) +private let selectedTextFont = Font.bold(13.0) + +public enum SegmentedControlLayout { + case stretchToFill(width: CGFloat) + case sizeToFit(maximumWidth: CGFloat, minimumWidth: CGFloat) +} + +public final class SegmentedControlTheme: Equatable { + public let backgroundColor: UIColor + public let foregroundColor: UIColor + public let shadowColor: UIColor + public let textColor: UIColor + public let dividerColor: UIColor + + public init(backgroundColor: UIColor, foregroundColor: UIColor, shadowColor: UIColor, textColor: UIColor, dividerColor: UIColor) { + self.backgroundColor = backgroundColor + self.foregroundColor = foregroundColor + self.shadowColor = shadowColor + self.textColor = textColor + self.dividerColor = dividerColor + } + + public static func ==(lhs: SegmentedControlTheme, rhs: SegmentedControlTheme) -> Bool { + if lhs.backgroundColor != rhs.backgroundColor { + return false + } + if lhs.foregroundColor != rhs.foregroundColor { + return false + } + if lhs.shadowColor != rhs.shadowColor { + return false + } + if lhs.textColor != rhs.textColor { + return false + } + if lhs.dividerColor != rhs.dividerColor { + return false + } + return true + } +} + +public extension SegmentedControlTheme { + convenience init(theme: PresentationTheme) { + self.init(backgroundColor: theme.rootController.navigationSearchBar.inputFillColor, foregroundColor: theme.rootController.navigationBar.backgroundColor, shadowColor: .black, textColor: theme.rootController.navigationBar.primaryTextColor, dividerColor: theme.list.freeInputField.strokeColor) + } +} + +private func generateSelectionImage(theme: SegmentedControlTheme) -> UIImage? { + return generateImage(CGSize(width: 20.0, height: 20.0), rotatedContext: { size, context in + let bounds = CGRect(origin: CGPoint(), size: size) + context.clear(bounds) + + if theme.shadowColor != .clear { + context.setShadow(offset: CGSize(width: 0.0, height: -3.0), blur: 6.0, color: theme.shadowColor.withAlphaComponent(0.12).cgColor) + } + context.setFillColor(theme.foregroundColor.cgColor) + context.fillEllipse(in: CGRect(x: 2.0, y: 2.0, width: 16.0, height: 16.0)) + })?.stretchableImage(withLeftCapWidth: 10, topCapHeight: 10) +} + +public struct SegmentedControlItem: Equatable { + public let title: String + + public init(title: String) { + self.title = title + } +} + +public final class SegmentedControlNode: ASDisplayNode, UIGestureRecognizerDelegate { + private var theme: SegmentedControlTheme + private var _items: [SegmentedControlItem] + private var _selectedIndex: Int = 0 + + private var validLayout: SegmentedControlLayout? + + private let selectionNode: ASImageNode + private var itemNodes: [HighlightTrackingButtonNode] + private var dividerNodes: [ASDisplayNode] + + private var gestureRecognizer: UIPanGestureRecognizer? + private var gestureSelectedIndex: Int? + + public var items: [SegmentedControlItem] { + get { + return self._items + } + set { + let previousItems = self._items + self._items = newValue + guard previousItems != newValue else { + return + } + + self.itemNodes.forEach { $0.removeFromSupernode() } + self.itemNodes = self._items.map { item in + let itemNode = HighlightTrackingButtonNode() + itemNode.setTitle(item.title, with: textFont, with: self.theme.textColor, for: .normal) + itemNode.setTitle(item.title, with: selectedTextFont, with: self.theme.textColor, for: .selected) + itemNode.setTitle(item.title, with: selectedTextFont, with: self.theme.textColor, for: [.selected, .highlighted]) + return itemNode + } + self.setupButtons() + self.itemNodes.forEach(self.addSubnode(_:)) + + let dividersCount = self._items.count > 2 ? self._items.count - 1 : 0 + if self.dividerNodes.count != dividersCount { + self.dividerNodes.forEach { $0.removeFromSupernode() } + self.dividerNodes = (0 ..< dividersCount).map { _ in ASDisplayNode() } + } + + if let layout = self.validLayout { + let _ = self.updateLayout(layout, transition: .immediate) + } + } + } + + public var selectedIndex: Int { + get { + return self._selectedIndex + } + set { + guard newValue != self._selectedIndex else { + return + } + self._selectedIndex = newValue + if let layout = self.validLayout { + let _ = self.updateLayout(layout, transition: .immediate) + } + } + } + + public var selectedIndexChanged: (Int) -> Void = { _ in } + + public init(theme: SegmentedControlTheme, items: [SegmentedControlItem], selectedIndex: Int) { + self.theme = theme + self._items = items + self._selectedIndex = selectedIndex + + self.selectionNode = ASImageNode() + self.selectionNode.displaysAsynchronously = false + self.selectionNode.displayWithoutProcessing = true + + self.itemNodes = items.map { item in + let itemNode = HighlightTrackingButtonNode() + itemNode.setTitle(item.title, with: textFont, with: theme.textColor, for: .normal) + itemNode.setTitle(item.title, with: selectedTextFont, with: theme.textColor, for: .selected) + itemNode.setTitle(item.title, with: selectedTextFont, with: theme.textColor, for: [.selected, .highlighted]) + return itemNode + } + + let dividersCount = items.count > 2 ? items.count - 1 : 0 + self.dividerNodes = (0 ..< dividersCount).map { _ in + let node = ASDisplayNode() + node.backgroundColor = theme.dividerColor + return node + } + + super.init() + + self.clipsToBounds = true + self.cornerRadius = 9.0 + + self.addSubnode(self.selectionNode) + self.itemNodes.forEach(self.addSubnode(_:)) + self.setupButtons() + self.dividerNodes.forEach(self.addSubnode(_:)) + + self.backgroundColor = self.theme.backgroundColor + self.selectionNode.image = generateSelectionImage(theme: self.theme) + } + + override public func didLoad() { + super.didLoad() + + self.view.disablesInteractiveTransitionGestureRecognizer = true + + let gestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:))) + gestureRecognizer.delegate = self + self.view.addGestureRecognizer(gestureRecognizer) + self.gestureRecognizer = gestureRecognizer + } + + private func setupButtons() { + for i in 0 ..< self.itemNodes.count { + let itemNode = self.itemNodes[i] + itemNode.addTarget(self, action: #selector(self.buttonPressed(_:)), forControlEvents: .touchUpInside) + itemNode.highligthedChanged = { [weak self, weak itemNode] highlighted in + if let strongSelf = self, let itemNode = itemNode { + let transition = ContainedViewLayoutTransition.animated(duration: 0.25, curve: .easeInOut) + if strongSelf.selectedIndex == i { + if let gestureRecognizer = strongSelf.gestureRecognizer, case .began = gestureRecognizer.state { + } else { + strongSelf.updateButtonsHighlights(highlightedIndex: highlighted ? i : nil, gestureSelectedIndex: strongSelf.gestureSelectedIndex) + } + } else if highlighted { + transition.updateAlpha(node: itemNode, alpha: 0.4) + } + if !highlighted { + transition.updateAlpha(node: itemNode, alpha: 1.0) + } + } + } + } + } + + private func updateButtonsHighlights(highlightedIndex: Int?, gestureSelectedIndex: Int?) { + let transition = ContainedViewLayoutTransition.animated(duration: 0.25, curve: .easeInOut) + if highlightedIndex == nil && gestureSelectedIndex == nil { + transition.updateTransformScale(node: self.selectionNode, scale: 1.0) + } else { + transition.updateTransformScale(node: self.selectionNode, scale: 0.92) + } + for i in 0 ..< self.itemNodes.count { + let itemNode = self.itemNodes[i] + if i == highlightedIndex || i == gestureSelectedIndex { + transition.updateTransformScale(node: itemNode, scale: 0.92) + } else { + transition.updateTransformScale(node: itemNode, scale: 1.0) + } + } + } + + private func updateButtonsHighlights() { + let transition = ContainedViewLayoutTransition.animated(duration: 0.25, curve: .easeInOut) + if let gestureSelectedIndex = self.gestureSelectedIndex { + for i in 0 ..< self.itemNodes.count { + let itemNode = self.itemNodes[i] + transition.updateTransformScale(node: itemNode, scale: i == gestureSelectedIndex ? 0.92 : 1.0) + } + } else { + for itemNode in self.itemNodes { + transition.updateTransformScale(node: itemNode, scale: 1.0) + } + } + } + + public func updateTheme(_ theme: SegmentedControlTheme) { + guard theme != self.theme else { + return + } + self.theme = theme + + self.backgroundColor = self.theme.backgroundColor + self.selectionNode.image = generateSelectionImage(theme: self.theme) + + for itemNode in self.itemNodes { + if let title = itemNode.attributedTitle(for: .normal)?.string { + itemNode.setTitle(title, with: textFont, with: self.theme.textColor, for: .normal) + itemNode.setTitle(title, with: selectedTextFont, with: self.theme.textColor, for: .selected) + itemNode.setTitle(title, with: selectedTextFont, with: self.theme.textColor, for: [.selected, .highlighted]) + } + } + + for dividerNode in self.dividerNodes { + dividerNode.backgroundColor = theme.dividerColor + } + } + + public func updateLayout(_ layout: SegmentedControlLayout, transition: ContainedViewLayoutTransition) -> CGSize { + self.validLayout = layout + + let calculatedWidth: CGFloat = 0.0 + + let width: CGFloat + switch layout { + case let .stretchToFill(targetWidth): + width = targetWidth + case let .sizeToFit(maximumWidth, minimumWidth): + width = max(minimumWidth, min(maximumWidth, calculatedWidth)) + } + + let size = CGSize(width: width, height: 32.0) + if !self.itemNodes.isEmpty { + let itemSize = CGSize(width: floorToScreenPixels(size.width / CGFloat(self.itemNodes.count)), height: size.height) + + let selectedIndex: Int + if let gestureSelectedIndex = self.gestureSelectedIndex { + selectedIndex = gestureSelectedIndex + } else { + selectedIndex = self.selectedIndex + } + + transition.updateBounds(node: self.selectionNode, bounds: CGRect(origin: CGPoint(), size: itemSize)) + transition.updatePosition(node: self.selectionNode, position: CGPoint(x: itemSize.width / 2.0 + itemSize.width * CGFloat(selectedIndex), y: size.height / 2.0)) + + for i in 0 ..< self.itemNodes.count { + let itemNode = self.itemNodes[i] + transition.updateFrame(node: itemNode, frame: CGRect(origin: CGPoint(x: itemSize.width * CGFloat(i), y: (size.height - itemSize.height) / 2.0), size: itemSize)) + + let isSelected = selectedIndex == i + if itemNode.isSelected != isSelected { + if case .animated = transition { + UIView.transition(with: itemNode.view, duration: 0.2, options: .transitionCrossDissolve, animations: { + itemNode.isSelected = isSelected + }, completion: nil) + } else { + itemNode.isSelected = isSelected + } + } + } + } + + if !self.dividerNodes.isEmpty { + let dividerSize = CGSize(width: 1.0, height: 16.0) + let delta: CGFloat = size.width / CGFloat(self.dividerNodes.count + 1) + for i in 0 ..< self.dividerNodes.count { + let dividerNode = self.dividerNodes[i] + transition.updateFrame(node: dividerNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels(delta * CGFloat(i + 1) - dividerSize.width / 2.0), y: (size.height - dividerSize.height) / 2.0), size: dividerSize)) + + let dividerAlpha: CGFloat + if (self.selectedIndex - 1 ... self.selectedIndex).contains(i) { + dividerAlpha = 0.0 + } else { + dividerAlpha = 1.0 + } + transition.updateAlpha(node: dividerNode, alpha: dividerAlpha) + } + } + + return size + } + + @objc private func buttonPressed(_ button: HighlightTrackingButtonNode) { + guard let index = self.itemNodes.firstIndex(of: button) else { + return + } + + self._selectedIndex = index + self.selectedIndexChanged(index) + if let layout = self.validLayout { + let _ = self.updateLayout(layout, transition: .animated(duration: 0.2, curve: .slide)) + } + } + + public override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { + let location = gestureRecognizer.location(in: self.view) + return self.selectionNode.frame.contains(location) + } + + @objc private func panGesture(_ recognizer: UIPanGestureRecognizer) { + let location = recognizer.location(in: self.view) + switch recognizer.state { + case .changed: + if !self.selectionNode.frame.contains(location) { + let point = CGPoint(x: max(0.0, min(self.bounds.width, location.x)), y: 1.0) + for i in 0 ..< self.itemNodes.count { + let itemNode = self.itemNodes[i] + if itemNode.frame.contains(point) { + if i != self.gestureSelectedIndex { + self.gestureSelectedIndex = i + self.updateButtonsHighlights(highlightedIndex: nil, gestureSelectedIndex: i) + if let layout = self.validLayout { + let _ = self.updateLayout(layout, transition: .animated(duration: 0.35, curve: .slide)) + } + } + break + } + } + } + case .ended: + if let gestureSelectedIndex = self.gestureSelectedIndex { + if gestureSelectedIndex != self.selectedIndex { + self._selectedIndex = gestureSelectedIndex + self.selectedIndexChanged(self._selectedIndex) + } + self.gestureSelectedIndex = nil + self.updateButtonsHighlights(highlightedIndex: nil, gestureSelectedIndex: nil) + } + default: + break + } + } +} diff --git a/submodules/SettingsUI/SettingsUI_Xcode.xcodeproj/project.pbxproj b/submodules/SettingsUI/SettingsUI_Xcode.xcodeproj/project.pbxproj index e7dbaa3149..8a132b7a45 100644 --- a/submodules/SettingsUI/SettingsUI_Xcode.xcodeproj/project.pbxproj +++ b/submodules/SettingsUI/SettingsUI_Xcode.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 091EABA5231DAC7500A0EC14 /* ThemeNameGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 091EABA4231DAC7500A0EC14 /* ThemeNameGenerator.swift */; }; 09B4A9B823102B7A005C2E08 /* EditThemeController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B4A9B723102B7A005C2E08 /* EditThemeController.swift */; }; + 09CE5B8E2322154400743FF4 /* SegmentedControlNode.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 09CE5B8D2322154400743FF4 /* SegmentedControlNode.framework */; }; D03E465223075D930049C28B /* SettingsUI.h in Headers */ = {isa = PBXBuildFile; fileRef = D03E465023075D930049C28B /* SettingsUI.h */; settings = {ATTRIBUTES = (Public, ); }; }; D03E466823075E660049C28B /* TabBarAccountSwitchControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03E465C23075E630049C28B /* TabBarAccountSwitchControllerNode.swift */; }; D03E466923075E660049C28B /* LogoutOptionsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03E465D23075E630049C28B /* LogoutOptionsController.swift */; }; @@ -194,6 +195,7 @@ /* Begin PBXFileReference section */ 091EABA4231DAC7500A0EC14 /* ThemeNameGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeNameGenerator.swift; sourceTree = ""; }; 09B4A9B723102B7A005C2E08 /* EditThemeController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditThemeController.swift; sourceTree = ""; }; + 09CE5B8D2322154400743FF4 /* SegmentedControlNode.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SegmentedControlNode.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D03E464D23075D930049C28B /* SettingsUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SettingsUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D03E465023075D930049C28B /* SettingsUI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SettingsUI.h; sourceTree = ""; }; D03E465123075D930049C28B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -383,6 +385,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 09CE5B8E2322154400743FF4 /* SegmentedControlNode.framework in Frameworks */, D03E493A2308678D0049C28B /* InstantPageCache.framework in Frameworks */, D03E490E2308661A0049C28B /* GridMessageSelectionNode.framework in Frameworks */, D03E48E22308649C0049C28B /* CounterContollerTitleView.framework in Frameworks */, @@ -687,6 +690,7 @@ D03E4732230761E10049C28B /* Frameworks */ = { isa = PBXGroup; children = ( + 09CE5B8D2322154400743FF4 /* SegmentedControlNode.framework */, D03E49392308678D0049C28B /* InstantPageCache.framework */, D03E490D2308661A0049C28B /* GridMessageSelectionNode.framework */, D03E48E12308649C0049C28B /* CounterContollerTitleView.framework */, diff --git a/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/RecentSessionsEmptyStateItem.swift b/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/RecentSessionsEmptyStateItem.swift index 6498ea6b3a..f98d49d6c8 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/RecentSessionsEmptyStateItem.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/RecentSessionsEmptyStateItem.swift @@ -84,15 +84,15 @@ final class RecentSessionsEmptyStateItemNode: ItemListControllerEmptyStateItemNo let imageSize = self.imageNode.image?.size ?? CGSize() let imageHeight = layout.size.width < layout.size.height ? imageSize.height + imageSpacing : 0.0 - let titleSize = self.titleNode.measure(CGSize(width: layout.size.width - 50.0, height: max(1.0, layout.size.height - insets.top - insets.bottom))) - let textSize = self.textNode.measure(CGSize(width: layout.size.width - 50.0, height: max(1.0, layout.size.height - insets.top - insets.bottom))) + let titleSize = self.titleNode.measure(CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - layout.intrinsicInsets.left - layout.intrinsicInsets.right - 50.0, height: max(1.0, layout.size.height - insets.top - insets.bottom))) + let textSize = self.textNode.measure(CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - layout.intrinsicInsets.left - layout.intrinsicInsets.right - 50.0, height: max(1.0, layout.size.height - insets.top - insets.bottom))) let totalHeight = imageHeight + titleSize.height + textSpacing + textSize.height let topOffset = insets.top + floor((layout.size.height - insets.top - insets.bottom - totalHeight) / 2.0) transition.updateAlpha(node: self.imageNode, alpha: imageHeight > 0.0 ? 1.0 : 0.0) transition.updateFrame(node: self.imageNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - imageSize.width) / 2.0), y: topOffset), size: imageSize)) - transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - titleSize.width) / 2.0), y: topOffset + imageHeight), size: titleSize)) - transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - textSize.width) / 2.0), y: self.titleNode.frame.maxY + textSpacing), size: textSize)) + transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - titleSize.width - layout.safeInsets.left - layout.safeInsets.right - layout.intrinsicInsets.left - layout.intrinsicInsets.right) / 2.0), y: topOffset + imageHeight), size: titleSize)) + transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - textSize.width - layout.safeInsets.left - layout.safeInsets.right - layout.intrinsicInsets.left - layout.intrinsicInsets.right) / 2.0), y: self.titleNode.frame.maxY + textSpacing), size: textSize)) } } diff --git a/submodules/SettingsUI/Sources/Themes/ThemeNameGenerator.swift b/submodules/SettingsUI/Sources/Themes/ThemeNameGenerator.swift index 694c1757ef..4668967035 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeNameGenerator.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeNameGenerator.swift @@ -335,9 +335,10 @@ func generateThemeName(accentColor: UIColor) -> String { if let color = nearest?.color, let colorName = colors[color]?.capitalized { if arc4random() % 2 == 0 { - return "\(adjectives[Int(arc4random()) % adjectives.count].capitalized) \(colorName)" + + return "\((adjectives.randomElement() ?? "").capitalized) \(colorName)" } else { - return "\(colorName) \(subjectives[Int(arc4random()) % subjectives.count].capitalized)" + return "\(colorName) \((subjectives.randomElement() ?? "").capitalized)" } } else { return "" diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift index 10ce1e8cd2..f1d5517ba6 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift @@ -83,6 +83,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { self.chatListBackgroundNode = ASDisplayNode() self.chatContainerNode = ASDisplayNode() + self.chatContainerNode.clipsToBounds = true self.instantChatBackgroundNode = WallpaperBackgroundNode() self.instantChatBackgroundNode.displaysAsynchronously = false self.instantChatBackgroundNode.image = chatControllerBackgroundImage(theme: previewTheme, wallpaper: previewTheme.chat.defaultWallpaper, mediaBox: context.sharedContext.accountManager.mediaBox, knockoutMode: context.sharedContext.immediateExperimentalUISettings.knockoutWallpaper) @@ -94,7 +95,6 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { self.remoteChatBackgroundNode.view.contentMode = .scaleAspectFill self.blurredNode = BlurredImageNode() - self.blurredNode.clipsToBounds = true self.blurredNode.blurView.contentMode = .scaleAspectFill self.toolbarNode = WallpaperGalleryToolbarNode(theme: self.previewTheme, strings: self.presentationData.strings) diff --git a/submodules/TelegramAudio/Sources/ManagedAudioSession.swift b/submodules/TelegramAudio/Sources/ManagedAudioSession.swift index 38964341ad..efa126565c 100644 --- a/submodules/TelegramAudio/Sources/ManagedAudioSession.swift +++ b/submodules/TelegramAudio/Sources/ManagedAudioSession.swift @@ -362,6 +362,10 @@ public final class ManagedAudioSession { } |> runOn(queue) } + public func isOtherAudioPlaying() -> Bool { + return AVAudioSession.sharedInstance().secondaryAudioShouldBeSilencedHint + } + public func push(audioSessionType: ManagedAudioSessionType, outputMode: AudioSessionOutputMode = .system, once: Bool = false, activate: @escaping (AudioSessionActivationState) -> Void, deactivate: @escaping () -> Signal) -> Disposable { return self.push(audioSessionType: audioSessionType, once: once, manualActivate: { control in control.setupAndActivate(synchronous: false, { state in diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift index cbca01ce42..6378d155ee 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift @@ -47,13 +47,13 @@ public enum PresentationResourceKey: Int32 { case itemListAddExceptionIcon case itemListAddPhoneIcon case itemListClearInputIcon - case itemListStickerItemUnreadDot case itemListVerifiedPeerIcon - case itemListCloudFetchIcon - case itemListCloseIconImage + case itemListCornersTop + case itemListCornersBottom + case itemListCornersBoth case chatListLockTopUnlockedImage case chatListLockBottomUnlockedImage diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesItemList.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesItemList.swift index 84a91cad0f..8b4cbfd0bf 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesItemList.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesItemList.swift @@ -144,4 +144,40 @@ public struct PresentationResourcesItemList { }) }) } + + public static func cornersImage(_ theme: PresentationTheme, top: Bool, bottom: Bool) -> UIImage? { + if !top && !bottom { + return nil + } + let key: PresentationResourceKey + if top && bottom { + key = PresentationResourceKey.itemListCornersBoth + } else if top { + key = PresentationResourceKey.itemListCornersTop + } else { + key = PresentationResourceKey.itemListCornersBottom + } + return theme.image(key.rawValue, { theme in + return generateImage(CGSize(width: 50.0, height: 50.0), rotatedContext: { (size, context) in + let bounds = CGRect(origin: CGPoint(), size: size) + context.setFillColor(theme.list.blocksBackgroundColor.cgColor) + context.fill(bounds) + + context.setBlendMode(.clear) + + var corners: UIRectCorner = [] + if top { + corners.insert(.topLeft) + corners.insert(.topRight) + } + if bottom { + corners.insert(.bottomLeft) + corners.insert(.bottomRight) + } + let path = UIBezierPath(roundedRect: bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: 11.0, height: 11.0)) + context.addPath(path.cgPath) + context.fillPath() + })?.stretchableImage(withLeftCapWidth: 25, topCapHeight: 25) + }) + } } diff --git a/submodules/TelegramUI/TelegramUI/ChatButtonKeyboardInputNode.swift b/submodules/TelegramUI/TelegramUI/ChatButtonKeyboardInputNode.swift index 9a2f2c65f5..767403dada 100644 --- a/submodules/TelegramUI/TelegramUI/ChatButtonKeyboardInputNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatButtonKeyboardInputNode.swift @@ -69,7 +69,7 @@ final class ChatButtonKeyboardInputNode: ChatInputNode { } } - override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, standardInputHeight: CGFloat, inputHeight: CGFloat, maximumHeight: CGFloat, inputPanelHeight: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, isVisible: Bool) -> (CGFloat, CGFloat) { + override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, standardInputHeight: CGFloat, inputHeight: CGFloat, maximumHeight: CGFloat, inputPanelHeight: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, deviceMetrics: DeviceMetrics, isVisible: Bool) -> (CGFloat, CGFloat) { transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: width, height: UIScreenPixel))) if self.theme !== interfaceState.theme { diff --git a/submodules/TelegramUI/TelegramUI/ChatController.swift b/submodules/TelegramUI/TelegramUI/ChatController.swift index b17d798b9c..759f41f5bd 100644 --- a/submodules/TelegramUI/TelegramUI/ChatController.swift +++ b/submodules/TelegramUI/TelegramUI/ChatController.swift @@ -4152,7 +4152,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G subscriber.putCompletion() return EmptyDisposable } - subscriber.putNext(strongSelf.traceVisibility() && isTopmostChatController(strongSelf)) + + subscriber.putNext(strongSelf.traceVisibility() && isTopmostChatController(strongSelf) && !strongSelf.context.sharedContext.mediaManager.audioSession.isOtherAudioPlaying()) subscriber.putCompletion() return EmptyDisposable } |> then(.complete() |> delay(1.0, queue: Queue.mainQueue())) |> restart diff --git a/submodules/TelegramUI/TelegramUI/ChatControllerNode.swift b/submodules/TelegramUI/TelegramUI/ChatControllerNode.swift index ab5f9aeefd..5f85557a49 100644 --- a/submodules/TelegramUI/TelegramUI/ChatControllerNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatControllerNode.swift @@ -591,7 +591,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { self.insertSubnode(inputNode, aboveSubnode: self.inputPanelBackgroundNode) } } - inputNodeHeightAndOverflow = inputNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: cleanInsets.bottom, standardInputHeight: layout.standardInputHeight, inputHeight: layout.inputHeight ?? 0.0, maximumHeight: maximumInputNodeHeight, inputPanelHeight: inputPanelNodeBaseHeight, transition: immediatelyLayoutInputNodeAndAnimateAppearance ? .immediate : transition, interfaceState: self.chatPresentationInterfaceState, isVisible: true) + inputNodeHeightAndOverflow = inputNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: cleanInsets.bottom, standardInputHeight: layout.standardInputHeight, inputHeight: layout.inputHeight ?? 0.0, maximumHeight: maximumInputNodeHeight, inputPanelHeight: inputPanelNodeBaseHeight, transition: immediatelyLayoutInputNodeAndAnimateAppearance ? .immediate : transition, interfaceState: self.chatPresentationInterfaceState, deviceMetrics: layout.deviceMetrics, isVisible: true) } else if let inputNode = self.inputNode { dismissedInputNode = inputNode self.inputNode = nil @@ -672,7 +672,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { } if let inputMediaNode = self.inputMediaNode, inputMediaNode != self.inputNode { - let _ = inputMediaNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: cleanInsets.bottom, standardInputHeight: layout.standardInputHeight, inputHeight: layout.inputHeight ?? 0.0, maximumHeight: maximumInputNodeHeight, inputPanelHeight: inputPanelSize?.height ?? 0.0, transition: .immediate, interfaceState: self.chatPresentationInterfaceState, isVisible: false) + let _ = inputMediaNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: cleanInsets.bottom, standardInputHeight: layout.standardInputHeight, inputHeight: layout.inputHeight ?? 0.0, maximumHeight: maximumInputNodeHeight, inputPanelHeight: inputPanelSize?.height ?? 0.0, transition: .immediate, interfaceState: self.chatPresentationInterfaceState, deviceMetrics: layout.deviceMetrics, isVisible: false) } transition.updateFrame(node: self.titleAccessoryPanelContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: layout.size.width, height: 56.0))) @@ -1573,7 +1573,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { inputNode.interfaceInteraction = interfaceInteraction self.inputMediaNode = inputNode if let (validLayout, _) = self.validLayout { - let _ = inputNode.updateLayout(width: validLayout.size.width, leftInset: validLayout.safeInsets.left, rightInset: validLayout.safeInsets.right, bottomInset: validLayout.intrinsicInsets.bottom, standardInputHeight: validLayout.standardInputHeight, inputHeight: validLayout.inputHeight ?? 0.0, maximumHeight: validLayout.standardInputHeight, inputPanelHeight: 44.0, transition: .immediate, interfaceState: self.chatPresentationInterfaceState, isVisible: false) + let _ = inputNode.updateLayout(width: validLayout.size.width, leftInset: validLayout.safeInsets.left, rightInset: validLayout.safeInsets.right, bottomInset: validLayout.intrinsicInsets.bottom, standardInputHeight: validLayout.standardInputHeight, inputHeight: validLayout.inputHeight ?? 0.0, maximumHeight: validLayout.standardInputHeight, inputPanelHeight: 44.0, transition: .immediate, interfaceState: self.chatPresentationInterfaceState, deviceMetrics: validLayout.deviceMetrics, isVisible: false) } self.textInputPanelNode?.loadTextInputNodeIfNeeded() diff --git a/submodules/TelegramUI/TelegramUI/ChatInputNode.swift b/submodules/TelegramUI/TelegramUI/ChatInputNode.swift index 9302ed63c9..d87af58c89 100644 --- a/submodules/TelegramUI/TelegramUI/ChatInputNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatInputNode.swift @@ -10,7 +10,7 @@ class ChatInputNode: ASDisplayNode { return .single(Void()) } - func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, standardInputHeight: CGFloat, inputHeight: CGFloat, maximumHeight: CGFloat, inputPanelHeight: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, isVisible: Bool) -> (CGFloat, CGFloat) { + func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, standardInputHeight: CGFloat, inputHeight: CGFloat, maximumHeight: CGFloat, inputPanelHeight: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, deviceMetrics: DeviceMetrics, isVisible: Bool) -> (CGFloat, CGFloat) { return (0.0, 0.0) } } diff --git a/submodules/TelegramUI/TelegramUI/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/TelegramUI/ChatInterfaceStateContextMenus.swift index 43e2155ef4..bcc9b2c698 100644 --- a/submodules/TelegramUI/TelegramUI/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/TelegramUI/ChatInterfaceStateContextMenus.swift @@ -34,7 +34,7 @@ private func canEditMessage(accountPeerId: PeerId, limitsConfiguration: LimitsCo if let peer = message.peers[message.id.peerId], let channel = peer as? TelegramChannel { switch channel.info { case .broadcast: - if channel.hasPermission(.editAllMessages) { + if message.author?.id == message.id.peerId || channel.hasPermission(.editAllMessages) { hasEditRights = true } default: diff --git a/submodules/TelegramUI/TelegramUI/ChatMediaInputGifPane.swift b/submodules/TelegramUI/TelegramUI/ChatMediaInputGifPane.swift index 3b4d31123e..13c5324388 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMediaInputGifPane.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMediaInputGifPane.swift @@ -36,7 +36,7 @@ final class ChatMediaInputGifPane: ChatMediaInputPane, UIScrollViewDelegate { private let disposable = MetaDisposable() let trendingPromise = Promise<[FileMediaReference]?>(nil) - private var validLayout: (CGSize, CGFloat, CGFloat, Bool, Bool)? + private var validLayout: (CGSize, CGFloat, CGFloat, Bool, Bool, DeviceMetrics)? private var didScrollPreviousOffset: CGFloat? private var didScrollPreviousState: ChatMediaInputPaneScrollState? @@ -76,18 +76,18 @@ final class ChatMediaInputGifPane: ChatMediaInputPane, UIScrollViewDelegate { self.searchPlaceholderNode.setup(theme: theme, strings: strings, type: .gifs) if let layout = self.validLayout { - self.updateLayout(size: layout.0, topInset: layout.1, bottomInset: layout.2, isExpanded: layout.3, isVisible: layout.4, transition: .immediate) + self.updateLayout(size: layout.0, topInset: layout.1, bottomInset: layout.2, isExpanded: layout.3, isVisible: layout.4, deviceMetrics: layout.5, transition: .immediate) } } - override func updateLayout(size: CGSize, topInset: CGFloat, bottomInset: CGFloat, isExpanded: Bool, isVisible: Bool, transition: ContainedViewLayoutTransition) { + override func updateLayout(size: CGSize, topInset: CGFloat, bottomInset: CGFloat, isExpanded: Bool, isVisible: Bool, deviceMetrics: DeviceMetrics, transition: ContainedViewLayoutTransition) { var changedIsExpanded = false - if let (_, _, _, previousIsExpanded, _) = self.validLayout { + if let (_, _, _, previousIsExpanded, _, _) = self.validLayout { if previousIsExpanded != isExpanded { changedIsExpanded = true } } - self.validLayout = (size, topInset, bottomInset, isExpanded, isVisible) + self.validLayout = (size, topInset, bottomInset, isExpanded, isVisible, deviceMetrics) let emptySize = self.emptyNode.updateLayout(size) transition.updateFrame(node: self.emptyNode, frame: CGRect(origin: CGPoint(x: floor(size.width - emptySize.width) / 2.0, y: topInset + floor(size.height - topInset - emptySize.height) / 2.0), size: emptySize)) @@ -96,8 +96,15 @@ final class ChatMediaInputGifPane: ChatMediaInputPane, UIScrollViewDelegate { let previousBounds = multiplexedNode.layer.bounds multiplexedNode.topInset = topInset + 60.0 multiplexedNode.bottomInset = bottomInset + + if case .tablet = deviceMetrics.type, size.width > 480.0 { + multiplexedNode.idealHeight = 120.0 + } else { + multiplexedNode.idealHeight = 93.0 + } + let nodeFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)) - + var targetBounds = CGRect(origin: previousBounds.origin, size: nodeFrame.size) if changedIsExpanded { targetBounds.origin.y = isExpanded || multiplexedNode.files.isEmpty ? 0.0 : 60.0 diff --git a/submodules/TelegramUI/TelegramUI/ChatMediaInputNode.swift b/submodules/TelegramUI/TelegramUI/ChatMediaInputNode.swift index 6a8aaf86c0..d1114a7664 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMediaInputNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMediaInputNode.swift @@ -425,7 +425,7 @@ final class ChatMediaInputNode: ChatInputNode { private var currentView: ItemCollectionsView? private let dismissedPeerSpecificStickerPack = Promise() - private var validLayout: (CGFloat, CGFloat, CGFloat, CGFloat, CGFloat, CGFloat, CGFloat, CGFloat, ChatPresentationInterfaceState, Bool)? + private var validLayout: (CGFloat, CGFloat, CGFloat, CGFloat, CGFloat, CGFloat, CGFloat, CGFloat, ChatPresentationInterfaceState, DeviceMetrics, Bool)? private var paneArrangement: ChatMediaInputPaneArrangement private var initializedArrangement = false @@ -1045,8 +1045,8 @@ final class ChatMediaInputNode: ChatInputNode { if let index = self.paneArrangement.panes.firstIndex(of: pane), index != self.paneArrangement.currentIndex { let previousGifPanelWasActive = self.paneArrangement.panes[self.paneArrangement.currentIndex] == .gifs self.paneArrangement = self.paneArrangement.withIndexTransition(0.0).withCurrentIndex(index) - if let (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, isVisible) = self.validLayout { - let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, standardInputHeight: standardInputHeight, inputHeight: inputHeight, maximumHeight: maximumHeight, inputPanelHeight: inputPanelHeight, transition: .animated(duration: 0.25, curve: .spring), interfaceState: interfaceState, isVisible: isVisible) + if let (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, deviceMetrics, isVisible) = self.validLayout { + let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, standardInputHeight: standardInputHeight, inputHeight: inputHeight, maximumHeight: maximumHeight, inputPanelHeight: inputPanelHeight, transition: .animated(duration: 0.25, curve: .spring), interfaceState: interfaceState, deviceMetrics: deviceMetrics, isVisible: isVisible) self.updateAppearanceTransition(transition: transition) } let updatedGifPanelWasActive = self.paneArrangement.panes[self.paneArrangement.currentIndex] == .gifs @@ -1066,8 +1066,8 @@ final class ChatMediaInputNode: ChatInputNode { self.setHighlightedItemCollectionId(ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.trending.rawValue, id: 0)) } } else { - if let (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, isVisible) = self.validLayout { - let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, standardInputHeight: standardInputHeight, inputHeight: inputHeight, maximumHeight: maximumHeight, inputPanelHeight: inputPanelHeight, transition: .animated(duration: 0.25, curve: .spring), interfaceState: interfaceState, isVisible: isVisible) + if let (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, deviceMetrics, isVisible) = self.validLayout { + let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, standardInputHeight: standardInputHeight, inputHeight: inputHeight, maximumHeight: maximumHeight, inputPanelHeight: inputPanelHeight, transition: .animated(duration: 0.25, curve: .spring), interfaceState: interfaceState, deviceMetrics: deviceMetrics, isVisible: isVisible) } } } @@ -1195,13 +1195,13 @@ final class ChatMediaInputNode: ChatInputNode { } } - override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, standardInputHeight: CGFloat, inputHeight: CGFloat, maximumHeight: CGFloat, inputPanelHeight: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, isVisible: Bool) -> (CGFloat, CGFloat) { + override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, standardInputHeight: CGFloat, inputHeight: CGFloat, maximumHeight: CGFloat, inputPanelHeight: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, deviceMetrics: DeviceMetrics, isVisible: Bool) -> (CGFloat, CGFloat) { var searchMode: ChatMediaInputSearchMode? - if let (_, _, _, _, _, _, _, _, interfaceState, _) = self.validLayout, case let .media(_, maybeExpanded) = interfaceState.inputMode, let expanded = maybeExpanded, case let .search(mode) = expanded { + if let (_, _, _, _, _, _, _, _, interfaceState, _, _) = self.validLayout, case let .media(_, maybeExpanded) = interfaceState.inputMode, let expanded = maybeExpanded, case let .search(mode) = expanded { searchMode = mode } - self.validLayout = (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, isVisible) + self.validLayout = (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, deviceMetrics, isVisible) if self.theme !== interfaceState.theme || self.strings !== interfaceState.strings { self.updateThemeAndStrings(theme: interfaceState.theme, strings: interfaceState.strings) @@ -1235,12 +1235,12 @@ final class ChatMediaInputNode: ChatInputNode { let containerFrame = CGRect(origin: CGPoint(x: 0.0, y: -inputPanelHeight), size: CGSize(width: width, height: panelHeight + inputPanelHeight)) if searchContainerNode.supernode != nil { transition.updateFrame(node: searchContainerNode, frame: containerFrame) - searchContainerNode.updateLayout(size: containerFrame.size, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, inputHeight: inputHeight, transition: transition) + searchContainerNode.updateLayout(size: containerFrame.size, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, inputHeight: inputHeight, deviceMetrics: deviceMetrics, transition: transition) } else { self.searchContainerNode = searchContainerNode self.insertSubnode(searchContainerNode, belowSubnode: self.collectionListContainer) searchContainerNode.frame = containerFrame - searchContainerNode.updateLayout(size: containerFrame.size, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, inputHeight: inputHeight, transition: .immediate) + searchContainerNode.updateLayout(size: containerFrame.size, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, inputHeight: inputHeight, deviceMetrics: deviceMetrics, transition: .immediate) var placeholderNode: PaneSearchBarPlaceholderNode? if let searchMode = searchMode { switch searchMode { @@ -1349,10 +1349,9 @@ final class ChatMediaInputNode: ChatInputNode { } } - self.gifPane.updateLayout(size: CGSize(width: width - leftInset - rightInset, height: panelHeight), topInset: 41.0, bottomInset: bottomInset, isExpanded: isExpanded, isVisible: isVisible, transition: transition) - - self.stickerPane.updateLayout(size: CGSize(width: width - leftInset - rightInset, height: panelHeight), topInset: 41.0, bottomInset: bottomInset, isExpanded: isExpanded, isVisible: isVisible && visiblePanes.contains(where: { $0.0 == .stickers }), transition: transition) - self.trendingPane.updateLayout(size: CGSize(width: width - leftInset - rightInset, height: panelHeight), topInset: 41.0, bottomInset: bottomInset, isExpanded: isExpanded, isVisible: isVisible, transition: transition) + self.gifPane.updateLayout(size: CGSize(width: width - leftInset - rightInset, height: panelHeight), topInset: 41.0, bottomInset: bottomInset, isExpanded: isExpanded, isVisible: isVisible, deviceMetrics: deviceMetrics, transition: transition) + self.stickerPane.updateLayout(size: CGSize(width: width - leftInset - rightInset, height: panelHeight), topInset: 41.0, bottomInset: bottomInset, isExpanded: isExpanded, isVisible: isVisible && visiblePanes.contains(where: { $0.0 == .stickers }), deviceMetrics: deviceMetrics, transition: transition) + self.trendingPane.updateLayout(size: CGSize(width: width - leftInset - rightInset, height: panelHeight), topInset: 41.0, bottomInset: bottomInset, isExpanded: isExpanded, isVisible: isVisible, deviceMetrics: deviceMetrics, transition: transition) if self.gifPane.supernode != nil { if !visiblePanes.contains(where: { $0.0 == .gifs }) { @@ -1525,7 +1524,7 @@ final class ChatMediaInputNode: ChatInputNode { self.trendingPane.removeFromSupernode() } case .changed: - if let (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, isVisible) = self.validLayout { + if let (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, deviceMetrics, isVisible) = self.validLayout { let translationX = -recognizer.translation(in: self.view).x var indexTransition = translationX / width if self.paneArrangement.currentIndex == 0 { @@ -1534,10 +1533,10 @@ final class ChatMediaInputNode: ChatInputNode { indexTransition = min(0.0, indexTransition) } self.paneArrangement = self.paneArrangement.withIndexTransition(indexTransition) - let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, standardInputHeight: standardInputHeight, inputHeight: inputHeight, maximumHeight: maximumHeight, inputPanelHeight: inputPanelHeight, transition: .immediate, interfaceState: interfaceState, isVisible: isVisible) + let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, standardInputHeight: standardInputHeight, inputHeight: inputHeight, maximumHeight: maximumHeight, inputPanelHeight: inputPanelHeight, transition: .immediate, interfaceState: interfaceState, deviceMetrics: deviceMetrics, isVisible: isVisible) } case .ended: - if let (width, _, _, _, _, _, _, _, _, _) = self.validLayout { + if let (width, _, _, _, _, _, _, _, _, _, _) = self.validLayout { var updatedIndex = self.paneArrangement.currentIndex if abs(self.paneArrangement.indexTransition * width) > 30.0 { if self.paneArrangement.indexTransition < 0.0 { @@ -1550,9 +1549,9 @@ final class ChatMediaInputNode: ChatInputNode { self.setCurrentPane(self.paneArrangement.panes[updatedIndex], transition: .animated(duration: 0.25, curve: .spring)) } case .cancelled: - if let (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, isVisible) = self.validLayout { + if let (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, deviceMetrics, isVisible) = self.validLayout { self.paneArrangement = self.paneArrangement.withIndexTransition(0.0) - let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, standardInputHeight: standardInputHeight, inputHeight: inputHeight, maximumHeight: maximumHeight, inputPanelHeight: inputPanelHeight, transition: .animated(duration: 0.25, curve: .spring), interfaceState: interfaceState, isVisible: isVisible) + let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, standardInputHeight: standardInputHeight, inputHeight: inputHeight, maximumHeight: maximumHeight, inputPanelHeight: inputPanelHeight, transition: .animated(duration: 0.25, curve: .spring), interfaceState: interfaceState, deviceMetrics: deviceMetrics, isVisible: isVisible) } default: break diff --git a/submodules/TelegramUI/TelegramUI/ChatMediaInputPane.swift b/submodules/TelegramUI/TelegramUI/ChatMediaInputPane.swift index f1c8647566..012627463c 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMediaInputPane.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMediaInputPane.swift @@ -16,7 +16,7 @@ class ChatMediaInputPane: ASDisplayNode { return false } - func updateLayout(size: CGSize, topInset: CGFloat, bottomInset: CGFloat, isExpanded: Bool, isVisible: Bool, transition: ContainedViewLayoutTransition) { + func updateLayout(size: CGSize, topInset: CGFloat, bottomInset: CGFloat, isExpanded: Bool, isVisible: Bool, deviceMetrics: DeviceMetrics, transition: ContainedViewLayoutTransition) { } func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) { diff --git a/submodules/TelegramUI/TelegramUI/ChatMediaInputStickerGridItem.swift b/submodules/TelegramUI/TelegramUI/ChatMediaInputStickerGridItem.swift index 8fa2250297..a978a9f21d 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMediaInputStickerGridItem.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMediaInputStickerGridItem.swift @@ -251,7 +251,7 @@ final class ChatMediaInputStickerGridItemNode: GridItemNode { if self.currentSize != size { self.currentSize = size - let sideSize: CGFloat = min(75.0 - 10.0, size.width) + let sideSize: CGFloat = size.width - 10.0 //min(75.0 - 10.0, size.width) let boundingSize = CGSize(width: sideSize, height: sideSize) if let (_, _, mediaDimensions) = self.currentState { @@ -271,7 +271,7 @@ final class ChatMediaInputStickerGridItemNode: GridItemNode { return } if let interfaceInteraction = self.interfaceInteraction, let (_, item, _) = self.currentState, case .ended = recognizer.state { - interfaceInteraction.sendSticker(.standalone(media: item.file), false, self, self.bounds) + let _ = interfaceInteraction.sendSticker(.standalone(media: item.file), false, self, self.bounds) self.imageNode.layer.animateAlpha(from: 0.5, to: 1.0, duration: 1.0) } } diff --git a/submodules/TelegramUI/TelegramUI/ChatMediaInputStickerPane.swift b/submodules/TelegramUI/TelegramUI/ChatMediaInputStickerPane.swift index d2caf0a433..1629717a22 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMediaInputStickerPane.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMediaInputStickerPane.swift @@ -114,7 +114,7 @@ final class ChatMediaInputStickerPane: ChatMediaInputPane { self.gridNode.scrollView.alwaysBounceVertical = true } - override func updateLayout(size: CGSize, topInset: CGFloat, bottomInset: CGFloat, isExpanded: Bool, isVisible: Bool, transition: ContainedViewLayoutTransition) { + override func updateLayout(size: CGSize, topInset: CGFloat, bottomInset: CGFloat, isExpanded: Bool, isVisible: Bool, deviceMetrics: DeviceMetrics, transition: ContainedViewLayoutTransition) { var changedIsExpanded = false if let previousIsExpanded = self.isExpanded { if previousIsExpanded != isExpanded { @@ -123,10 +123,17 @@ final class ChatMediaInputStickerPane: ChatMediaInputPane { } self.isExpanded = isExpanded + let maxItemSize: CGSize + if case .tablet = deviceMetrics.type, size.width > 480.0 { + maxItemSize = CGSize(width: 90.0, height: 96.0) + } else { + maxItemSize = CGSize(width: 75.0, height: 80.0) + } + let sideInset: CGFloat = 2.0 var itemSide: CGFloat = floor((size.width - sideInset * 2.0) / 5.0) - itemSide = min(itemSide, 75.0) - let itemSize = CGSize(width: itemSide, height: max(itemSide, 80.0)) + itemSide = min(itemSide, maxItemSize.width) + let itemSize = CGSize(width: itemSide, height: max(itemSide, maxItemSize.height)) var scrollToItem: GridNodeScrollToItem? if changedIsExpanded { diff --git a/submodules/TelegramUI/TelegramUI/ChatMediaInputTrendingPane.swift b/submodules/TelegramUI/TelegramUI/ChatMediaInputTrendingPane.swift index 9b2568f367..aa50bcfaa7 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMediaInputTrendingPane.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMediaInputTrendingPane.swift @@ -75,24 +75,31 @@ private final class TrendingPaneEntry: Identifiable, Comparable { return lhs.index < rhs.index } - func item(account: Account, interaction: TrendingPaneInteraction) -> ListViewItem { - return MediaInputPaneTrendingItem(account: account, theme: self.theme, strings: self.strings, interaction: interaction, info: self.info, topItems: self.topItems, installed: self.installed, unread: self.unread) + func item(account: Account, interaction: TrendingPaneInteraction) -> GridItem { + let info = self.info + return StickerPaneSearchGlobalItem(account: account, theme: self.theme, strings: self.strings, info: self.info, topItems: self.topItems, grid: true, installed: self.installed, unread: self.unread, open: { + interaction.openPack(info) + }, install: { + interaction.installPack(info) + }, getItemIsPreviewed: { item in + return interaction.getItemIsPreviewed(item) + }) } } private struct TrendingPaneTransition { - let deletions: [ListViewDeleteItem] - let insertions: [ListViewInsertItem] - let updates: [ListViewUpdateItem] + let deletions: [Int] + let insertions: [GridNodeInsertItem] + let updates: [GridNodeUpdateItem] let initial: Bool } private func preparedTransition(from fromEntries: [TrendingPaneEntry], to toEntries: [TrendingPaneEntry], account: Account, interaction: TrendingPaneInteraction, initial: Bool) -> TrendingPaneTransition { let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries) - let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } - let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, interaction: interaction), directionHint: nil) } - let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, interaction: interaction), directionHint: nil) } + let deletions = deleteIndices + let insertions = indicesAndItems.map { GridNodeInsertItem(index: $0.0, item: $0.1.item(account: account, interaction: interaction), previousIndex: $0.2) } + let updates = updateIndices.map { GridNodeUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, interaction: interaction)) } return TrendingPaneTransition(deletions: deletions, insertions: insertions, updates: updates, initial: initial) } @@ -114,7 +121,7 @@ final class ChatMediaInputTrendingPane: ChatMediaInputPane { private let controllerInteraction: ChatControllerInteraction private let getItemIsPreviewed: (StickerPackItem) -> Bool - private let listNode: ListView + let gridNode: GridNode private var enqueuedTransitions: [TrendingPaneTransition] = [] private var validLayout: (CGSize, CGFloat)? @@ -135,13 +142,13 @@ final class ChatMediaInputTrendingPane: ChatMediaInputPane { self.controllerInteraction = controllerInteraction self.getItemIsPreviewed = getItemIsPreviewed - self.listNode = ListView() + self.gridNode = GridNode() super.init() - self.addSubnode(self.listNode) + self.addSubnode(self.gridNode) - self.listNode.beganInteractiveDragging = { [weak self] in + self.gridNode.scrollingInitiated = { [weak self] in self?.scrollingInitiated?() } } @@ -224,28 +231,39 @@ final class ChatMediaInputTrendingPane: ChatMediaInputPane { }) } - override func updateLayout(size: CGSize, topInset: CGFloat, bottomInset: CGFloat, isExpanded: Bool, isVisible: Bool, transition: ContainedViewLayoutTransition) { + override func updateLayout(size: CGSize, topInset: CGFloat, bottomInset: CGFloat, isExpanded: Bool, isVisible: Bool, deviceMetrics: DeviceMetrics, transition: ContainedViewLayoutTransition) { let hadValidLayout = self.validLayout != nil self.validLayout = (size, bottomInset) - transition.updateFrame(node: self.listNode, frame: CGRect(origin: CGPoint(), size: size)) - - var duration: Double = 0.0 - var listViewCurve: ListViewAnimationCurve = .Default(duration: nil) - switch transition { - case .immediate: - break - case let .animated(animationDuration, animationCurve): - duration = animationDuration - switch animationCurve { - case .easeInOut, .custom: - listViewCurve = .Default(duration: duration) - case .spring: - listViewCurve = .Spring(duration: duration) - } + let itemSize: CGSize + if case .tablet = deviceMetrics.type, size.width > 480.0 { + itemSize = CGSize(width: floor(size.width / 2.0), height: 128.0) + } else { + itemSize = CGSize(width: size.width, height: 128.0) } - self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: size, insets: UIEdgeInsets(top: topInset, left: 0.0, bottom: bottomInset, right: 0.0), duration: duration, curve: listViewCurve), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: size, insets: UIEdgeInsets(top: topInset, left: 0.0, bottom: bottomInset, right: 0.0), preloadSize: isVisible ? 300.0 : 0.0, type: .fixed(itemSize: itemSize, fillWidth: nil, lineSpacing: 0.0, itemSpacing: nil)), transition: transition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in }) + + transition.updateFrame(node: self.gridNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height))) + +// transition.updateFrame(node: self.listNode, frame: CGRect(origin: CGPoint(), size: size)) +// +// var duration: Double = 0.0 +// var listViewCurve: ListViewAnimationCurve = .Default(duration: nil) +// switch transition { +// case .immediate: +// break +// case let .animated(animationDuration, animationCurve): +// duration = animationDuration +// switch animationCurve { +// case .easeInOut, .custom: +// listViewCurve = .Default(duration: duration) +// case .spring: +// listViewCurve = .Spring(duration: duration) +// } +// } +// +// self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: size, insets: UIEdgeInsets(top: topInset, left: 0.0, bottom: bottomInset, right: 0.0), duration: duration, curve: listViewCurve), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) if !hadValidLayout { while !self.enqueuedTransitions.isEmpty { @@ -274,38 +292,28 @@ final class ChatMediaInputTrendingPane: ChatMediaInputPane { if let transition = self.enqueuedTransitions.first { self.enqueuedTransitions.remove(at: 0) - var options = ListViewDeleteAndInsertOptions() - if transition.initial { - options.insert(.Synchronous) - options.insert(.LowLatency) - options.insert(.PreferSynchronousResourceLoading) - } else { - options.insert(.AnimateInsertion) - } - - self.listNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, updateSizeAndInsets: nil, updateOpaqueState: nil, completion: { _ in - }) + let itemTransition: ContainedViewLayoutTransition = .immediate + self.gridNode.transaction(GridNodeTransaction(deleteItems: transition.deletions, insertItems: transition.insertions, updateItems: transition.updates, scrollToItem: nil, updateLayout: nil, itemTransition: itemTransition, stationaryItems: .none, updateFirstIndexInSectionOffset: nil, synchronousLoads: transition.initial), completion: { _ in }) } } func itemAt(point: CGPoint) -> (ASDisplayNode, StickerPackItem)? { - let localPoint = self.view.convert(point, to: self.listNode.view) - var resultNode: MediaInputPaneTrendingItemNode? - self.listNode.forEachItemNode { itemNode in - if itemNode.frame.contains(localPoint), let itemNode = itemNode as? MediaInputPaneTrendingItemNode { + let localPoint = self.view.convert(point, to: self.gridNode.view) + var resultNode: StickerPaneSearchGlobalItemNode? + self.gridNode.forEachItemNode { itemNode in + if itemNode.frame.contains(localPoint), let itemNode = itemNode as? StickerPaneSearchGlobalItemNode { resultNode = itemNode } } if let resultNode = resultNode { - return resultNode.itemAt(point: self.listNode.view.convert(localPoint, to: resultNode.view)) + return resultNode.itemAt(point: self.gridNode.view.convert(localPoint, to: resultNode.view)) } - return nil } func updatePreviewing(animated: Bool) { - self.listNode.forEachItemNode { itemNode in - if let itemNode = itemNode as? MediaInputPaneTrendingItemNode { + self.gridNode.forEachItemNode { itemNode in + if let itemNode = itemNode as? StickerPaneSearchGlobalItemNode { itemNode.updatePreviewing(animated: animated) } } diff --git a/submodules/TelegramUI/TelegramUI/ChatMessageItemView.swift b/submodules/TelegramUI/TelegramUI/ChatMessageItemView.swift index 592d3a429e..d498ebeb99 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMessageItemView.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMessageItemView.swift @@ -31,6 +31,7 @@ struct ChatMessageItemBubbleLayoutConstants { let minimumSize: CGSize let contentInsets: UIEdgeInsets let borderInset: CGFloat + let strokeInsets: UIEdgeInsets } struct ChatMessageItemTextLayoutConstants { @@ -81,7 +82,7 @@ struct ChatMessageItemLayoutConstants { self.avatarDiameter = 37.0 self.timestampHeaderHeight = 34.0 - self.bubble = ChatMessageItemBubbleLayoutConstants(edgeInset: 4.0, defaultSpacing: 2.0 + UIScreenPixel, mergedSpacing: 1.0, maximumWidthFill: ChatMessageItemWidthFill(compactInset: 36.0, compactWidthBoundary: 500.0, freeMaximumFillFactor: 0.85), minimumSize: CGSize(width: 40.0, height: 35.0), contentInsets: UIEdgeInsets(top: 0.0, left: 6.0, bottom: 0.0, right: 0.0), borderInset: UIScreenPixel) + self.bubble = ChatMessageItemBubbleLayoutConstants(edgeInset: 4.0, defaultSpacing: 2.0 + UIScreenPixel, mergedSpacing: 1.0, maximumWidthFill: ChatMessageItemWidthFill(compactInset: 36.0, compactWidthBoundary: 500.0, freeMaximumFillFactor: 0.85), minimumSize: CGSize(width: 40.0, height: 35.0), contentInsets: UIEdgeInsets(top: 0.0, left: 6.0, bottom: 0.0, right: 0.0), borderInset: UIScreenPixel, strokeInsets: UIEdgeInsets(top: 1.0 - UIScreenPixel, left: 1.0 - UIScreenPixel, bottom: 1.0 - UIScreenPixel, right: 1.0 - UIScreenPixel)) self.text = ChatMessageItemTextLayoutConstants(bubbleInsets: UIEdgeInsets(top: 6.0 + UIScreenPixel, left: 12.0, bottom: 6.0 - UIScreenPixel, right: 12.0)) self.image = ChatMessageItemImageLayoutConstants(bubbleInsets: UIEdgeInsets(top: 1.0 + UIScreenPixel, left: 1.0 + UIScreenPixel, bottom: 1.0 + UIScreenPixel, right: 1.0 + UIScreenPixel), statusInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 6.0, right: 6.0), defaultCornerRadius: 17.0, mergedCornerRadius: 5.0, contentMergedCornerRadius: 5.0, maxDimensions: CGSize(width: 300.0, height: 300.0), minDimensions: CGSize(width: 170.0, height: 74.0)) self.video = ChatMessageItemVideoLayoutConstants(maxHorizontalHeight: 250.0, maxVerticalHeight: 360.0) diff --git a/submodules/TelegramUI/TelegramUI/ChatMessageMediaBubbleContentNode.swift b/submodules/TelegramUI/TelegramUI/ChatMessageMediaBubbleContentNode.swift index 5f1ee57306..4c0b023338 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMessageMediaBubbleContentNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMessageMediaBubbleContentNode.swift @@ -6,6 +6,7 @@ import SwiftSignalKit import Postbox import TelegramCore import TelegramUIPreferences +import TelegramPresentationData import AccountContext import GridMessageSelectionNode @@ -115,7 +116,17 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { switch preparePosition { case .linear: if case .color = item.presentationData.theme.wallpaper { - bubbleInsets = UIEdgeInsets() + let colors: PresentationThemeBubbleColorComponents + if item.message.effectivelyIncoming(item.context.account.peerId) { + colors = item.presentationData.theme.theme.chat.message.incoming.bubble.withoutWallpaper + } else { + colors = item.presentationData.theme.theme.chat.message.outgoing.bubble.withoutWallpaper + } + if colors.fill == colors.stroke { + bubbleInsets = UIEdgeInsets() + } else { + bubbleInsets = layoutConstants.bubble.strokeInsets + } } else { bubbleInsets = layoutConstants.image.bubbleInsets } diff --git a/submodules/TelegramUI/TelegramUI/ChatTitleView.swift b/submodules/TelegramUI/TelegramUI/ChatTitleView.swift index 90f968a5e1..cd9c0c1077 100644 --- a/submodules/TelegramUI/TelegramUI/ChatTitleView.swift +++ b/submodules/TelegramUI/TelegramUI/ChatTitleView.swift @@ -666,7 +666,6 @@ final class ChatTitleView: UIView, NavigationBarTitleView { func animateLayoutTransition() { UIView.transition(with: self, duration: 0.25, options: [.transitionCrossDissolve], animations: { - }, completion: nil) } } diff --git a/submodules/TelegramUI/TelegramUI/GifPaneSearchContentNode.swift b/submodules/TelegramUI/TelegramUI/GifPaneSearchContentNode.swift index 7938ea2828..0b20127a15 100644 --- a/submodules/TelegramUI/TelegramUI/GifPaneSearchContentNode.swift +++ b/submodules/TelegramUI/TelegramUI/GifPaneSearchContentNode.swift @@ -196,7 +196,7 @@ final class GifPaneSearchContentNode: ASDisplayNode & PaneSearchContentNode { } } - func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, inputHeight: CGFloat, transition: ContainedViewLayoutTransition) { + func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, inputHeight: CGFloat, deviceMetrics: DeviceMetrics, transition: ContainedViewLayoutTransition) { let firstLayout = self.validLayout == nil self.validLayout = size diff --git a/submodules/TelegramUI/TelegramUI/MultiplexedVideoNode.swift b/submodules/TelegramUI/TelegramUI/MultiplexedVideoNode.swift index fb03be4a5f..16e897f26e 100644 --- a/submodules/TelegramUI/TelegramUI/MultiplexedVideoNode.swift +++ b/submodules/TelegramUI/TelegramUI/MultiplexedVideoNode.swift @@ -51,6 +51,12 @@ final class MultiplexedVideoNode: ASScrollNode, UIScrollViewDelegate { } } + var idealHeight: CGFloat = 93.0 { + didSet { + self.setNeedsLayout() + } + } + var files: [FileMediaReference] = [] { didSet { self.updateVisibleItems() @@ -341,7 +347,7 @@ final class MultiplexedVideoNode: ASScrollNode, UIScrollViewDelegate { if !drawableSize.width.isZero { var displayItems: [VisibleVideoItem] = [] - let idealHeight: CGFloat = 93.0 + let idealHeight = self.idealHeight var weights: [Int] = [] var totalItemSize: CGFloat = 0.0 diff --git a/submodules/TelegramUI/TelegramUI/PaneSearchContainerNode.swift b/submodules/TelegramUI/TelegramUI/PaneSearchContainerNode.swift index ddd7ae64ca..ece3b008e0 100644 --- a/submodules/TelegramUI/TelegramUI/PaneSearchContainerNode.swift +++ b/submodules/TelegramUI/TelegramUI/PaneSearchContainerNode.swift @@ -17,7 +17,7 @@ protocol PaneSearchContentNode { func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) func updateText(_ text: String, languageCode: String?) - func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, inputHeight: CGFloat, transition: ContainedViewLayoutTransition) + func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, inputHeight: CGFloat, deviceMetrics: DeviceMetrics, transition: ContainedViewLayoutTransition) func animateIn(additivePosition: CGFloat, transition: ContainedViewLayoutTransition) func animateOut(transition: ContainedViewLayoutTransition) @@ -103,7 +103,7 @@ final class PaneSearchContainerNode: ASDisplayNode { return self.contentNode.itemAt(point: CGPoint(x: point.x, y: point.y - searchBarHeight)) } - func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, inputHeight: CGFloat, transition: ContainedViewLayoutTransition) { + func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, inputHeight: CGFloat, deviceMetrics: DeviceMetrics, transition: ContainedViewLayoutTransition) { self.validLayout = size transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: size)) @@ -113,7 +113,7 @@ final class PaneSearchContainerNode: ASDisplayNode { let contentFrame = CGRect(origin: CGPoint(x: leftInset, y: searchBarHeight), size: CGSize(width: size.width - leftInset - rightInset, height: size.height - searchBarHeight)) transition.updateFrame(node: self.contentNode, frame: contentFrame) - self.contentNode.updateLayout(size: contentFrame.size, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, inputHeight: inputHeight, transition: transition) + self.contentNode.updateLayout(size: contentFrame.size, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, inputHeight: inputHeight, deviceMetrics: deviceMetrics, transition: transition) } func deactivate() { diff --git a/submodules/TelegramUI/TelegramUI/PeerMediaCollectionSectionsNode.swift b/submodules/TelegramUI/TelegramUI/PeerMediaCollectionSectionsNode.swift index 6958bdc7cc..1aee8717c2 100644 --- a/submodules/TelegramUI/TelegramUI/PeerMediaCollectionSectionsNode.swift +++ b/submodules/TelegramUI/TelegramUI/PeerMediaCollectionSectionsNode.swift @@ -4,12 +4,13 @@ import AsyncDisplayKit import Display import TelegramCore import TelegramPresentationData +import SegmentedControlNode final class PeerMediaCollectionSectionsNode: ASDisplayNode { private var theme: PresentationTheme private var strings: PresentationStrings - private let segmentedControl: UISegmentedControl + private let segmentedControlNode: SegmentedControlNode private let separatorNode: ASDisplayNode var indexUpdated: ((Int) -> Void)? @@ -17,15 +18,14 @@ final class PeerMediaCollectionSectionsNode: ASDisplayNode { init(theme: PresentationTheme, strings: PresentationStrings) { self.theme = theme self.strings = strings - - self.segmentedControl = UISegmentedControl(items: [ + + let items = [ strings.SharedMedia_CategoryMedia, strings.SharedMedia_CategoryDocs, strings.SharedMedia_CategoryLinks, strings.SharedMedia_CategoryOther - ]) - self.segmentedControl.selectedSegmentIndex = 0 - self.segmentedControl.tintColor = theme.rootController.navigationBar.accentTextColor + ] + self.segmentedControlNode = SegmentedControlNode(theme: SegmentedControlTheme(theme: theme), items: items.map { SegmentedControlItem(title: $0) }, selectedIndex: 0) self.separatorNode = ASDisplayNode() self.separatorNode.isLayerBacked = true @@ -34,22 +34,22 @@ final class PeerMediaCollectionSectionsNode: ASDisplayNode { super.init() - self.addSubnode(self.separatorNode) - self.view.addSubview(self.segmentedControl) - self.backgroundColor = self.theme.rootController.navigationBar.backgroundColor - self.segmentedControl.addTarget(self, action: #selector(indexChanged), for: .valueChanged) + self.segmentedControlNode.selectedIndexChanged = { [weak self] index in + self?.indexUpdated?(index) + } + + self.addSubnode(self.separatorNode) + self.addSubnode(self.segmentedControlNode) } func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, additionalInset: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: PeerMediaCollectionInterfaceState) -> CGFloat { let panelHeight: CGFloat = 39.0 + additionalInset - - let controlHeight: CGFloat = 29.0 let sideInset: CGFloat = 8.0 - transition.animateView { - self.segmentedControl.frame = CGRect(origin: CGPoint(x: sideInset + leftInset, y: panelHeight - 11.0 - controlHeight), size: CGSize(width: width - sideInset * 2.0 - leftInset - rightInset, height: controlHeight)) - } + + let controlSize = self.segmentedControlNode.updateLayout(.stretchToFill(width: width - sideInset * 2.0 - leftInset - rightInset), transition: transition) + transition.updateFrame(node: self.segmentedControlNode, frame: CGRect(origin: CGPoint(x: sideInset + leftInset, y: panelHeight - 8.0 - controlSize.height), size: controlSize)) transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: panelHeight - UIScreenPixel), size: CGSize(width: width, height: UIScreenPixel))) @@ -57,13 +57,9 @@ final class PeerMediaCollectionSectionsNode: ASDisplayNode { self.theme = interfaceState.theme self.separatorNode.backgroundColor = self.theme.rootController.navigationBar.separatorColor self.backgroundColor = self.theme.rootController.navigationBar.backgroundColor - self.segmentedControl.tintColor = theme.rootController.navigationBar.accentTextColor + self.segmentedControlNode.updateTheme(SegmentedControlTheme(theme: self.theme)) } return panelHeight } - - @objc func indexChanged() { - self.indexUpdated?(self.segmentedControl.selectedSegmentIndex) - } } diff --git a/submodules/TelegramUI/TelegramUI/PeerSelectionControllerNode.swift b/submodules/TelegramUI/TelegramUI/PeerSelectionControllerNode.swift index 4a44cd0267..c6c0415231 100644 --- a/submodules/TelegramUI/TelegramUI/PeerSelectionControllerNode.swift +++ b/submodules/TelegramUI/TelegramUI/PeerSelectionControllerNode.swift @@ -11,6 +11,7 @@ import SearchBarNode import SearchUI import ContactListUI import ChatListUI +import SegmentedControlNode final class PeerSelectionControllerNode: ASDisplayNode { private let context: AccountContext @@ -28,7 +29,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { private let toolbarBackgroundNode: ASDisplayNode? private let toolbarSeparatorNode: ASDisplayNode? - private let segmentedControl: UISegmentedControl? + private let segmentedControlNode: SegmentedControlNode? var contactListNode: ContactListNode? let chatListNode: ChatListNode @@ -71,13 +72,15 @@ final class PeerSelectionControllerNode: ASDisplayNode { self.toolbarSeparatorNode = ASDisplayNode() self.toolbarSeparatorNode?.backgroundColor = self.presentationData.theme.rootController.navigationBar.separatorColor - self.segmentedControl = UISegmentedControl(items: [self.presentationData.strings.DialogList_TabTitle, self.presentationData.strings.Contacts_TabTitle]) - self.segmentedControl?.tintColor = self.presentationData.theme.rootController.navigationBar.accentTextColor - self.segmentedControl?.selectedSegmentIndex = 0 + let items = [ + self.presentationData.strings.DialogList_TabTitle, + self.presentationData.strings.Contacts_TabTitle + ] + self.segmentedControlNode = SegmentedControlNode(theme: SegmentedControlTheme(theme: self.presentationData.theme), items: items.map { SegmentedControlItem(title: $0) }, selectedIndex: 0) } else { self.toolbarBackgroundNode = nil self.toolbarSeparatorNode = nil - self.segmentedControl = nil + self.segmentedControlNode = nil } @@ -121,10 +124,13 @@ final class PeerSelectionControllerNode: ASDisplayNode { }) if hasContactSelector { + self.segmentedControlNode!.selectedIndexChanged = { [weak self] index in + self?.indexChanged(index) + } + self.addSubnode(self.toolbarBackgroundNode!) self.addSubnode(self.toolbarSeparatorNode!) - self.view.addSubview(self.segmentedControl!) - self.segmentedControl!.addTarget(self, action: #selector(indexChanged), for: .valueChanged) + self.addSubnode(self.segmentedControlNode!) } @@ -142,7 +148,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { self.toolbarBackgroundNode?.backgroundColor = self.presentationData.theme.rootController.navigationBar.backgroundColor self.toolbarSeparatorNode?.backgroundColor = self.presentationData.theme.rootController.navigationBar.separatorColor - self.segmentedControl?.tintColor = self.presentationData.theme.rootController.navigationBar.accentTextColor + self.segmentedControlNode?.updateTheme(SegmentedControlTheme(theme: self.presentationData.theme)) } func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, actualNavigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { @@ -152,14 +158,13 @@ final class PeerSelectionControllerNode: ASDisplayNode { var toolbarHeight: CGFloat = cleanInsets.bottom - if let segmentedControl = segmentedControl, let toolbarBackgroundNode = toolbarBackgroundNode, let toolbarSeparatorNode = toolbarSeparatorNode { + if let segmentedControlNode = self.segmentedControlNode, let toolbarBackgroundNode = self.toolbarBackgroundNode, let toolbarSeparatorNode = self.toolbarSeparatorNode { toolbarHeight += 44 transition.updateFrame(node: toolbarBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - toolbarHeight), size: CGSize(width: layout.size.width, height: toolbarHeight))) transition.updateFrame(node: toolbarSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - toolbarHeight), size: CGSize(width: layout.size.width, height: UIScreenPixel))) - var controlSize = segmentedControl.sizeThatFits(layout.size) - controlSize.width = min(layout.size.width, max(200.0, controlSize.width)) - transition.updateFrame(view: segmentedControl, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - controlSize.width) / 2.0), y: layout.size.height - toolbarHeight + floor((44.0 - controlSize.height) / 2.0)), size: controlSize)) + let controlSize = segmentedControlNode.updateLayout(.sizeToFit(maximumWidth: layout.size.width, minimumWidth: 200.0), transition: transition) + transition.updateFrame(node: segmentedControlNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - controlSize.width) / 2.0), y: layout.size.height - toolbarHeight + floor((44.0 - controlSize.height) / 2.0)), size: controlSize)) } var insets = layout.insets(options: [.input]) @@ -318,12 +323,12 @@ final class PeerSelectionControllerNode: ASDisplayNode { }) } - @objc func indexChanged() { - guard let (layout, navigationHeight, actualNavigationHeight) = self.containerLayout, let segmentedControl = self.segmentedControl else { + private func indexChanged(_ index: Int) { + guard let (layout, navigationHeight, actualNavigationHeight) = self.containerLayout else { return } - let contactListActive = segmentedControl.selectedSegmentIndex == 1 + let contactListActive = index == 1 if contactListActive != self.contactListActive { self.contactListActive = contactListActive if contactListActive { diff --git a/submodules/TelegramUI/TelegramUI/StickerPaneSearchContentNode.swift b/submodules/TelegramUI/TelegramUI/StickerPaneSearchContentNode.swift index 9913c3bb61..1fb455df4f 100644 --- a/submodules/TelegramUI/TelegramUI/StickerPaneSearchContentNode.swift +++ b/submodules/TelegramUI/TelegramUI/StickerPaneSearchContentNode.swift @@ -99,7 +99,7 @@ private enum StickerSearchEntry: Identifiable, Comparable { interaction.sendSticker(.standalone(media: stickerItem.file), node, rect) }) case let .global(_, info, topItems, installed): - return StickerPaneSearchGlobalItem(account: account, theme: theme, strings: strings, info: info, topItems: topItems, installed: installed, unread: false, open: { + return StickerPaneSearchGlobalItem(account: account, theme: theme, strings: strings, info: info, topItems: topItems, grid: false, installed: installed, unread: false, open: { interaction.open(info) }, install: { interaction.install(info) @@ -247,7 +247,7 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode { } }, sendSticker: { [weak self] file, sourceNode, sourceRect in if let strongSelf = self { - strongSelf.controllerInteraction.sendSticker(file, false, sourceNode, sourceRect) + let _ = strongSelf.controllerInteraction.sendSticker(file, false, sourceNode, sourceRect) } }, getItemIsPreviewed: { item in return inputNodeInteraction.previewedStickerPackItem == .pack(item) @@ -460,7 +460,7 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode { return nil } - func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, inputHeight: CGFloat, transition: ContainedViewLayoutTransition) { + func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, inputHeight: CGFloat, deviceMetrics: DeviceMetrics, transition: ContainedViewLayoutTransition) { let firstLayout = self.validLayout == nil self.validLayout = size @@ -478,7 +478,7 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode { self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: contentFrame.size, insets: UIEdgeInsets(top: 4.0, left: 0.0, bottom: 4.0 + bottomInset, right: 0.0), preloadSize: 300.0, type: .fixed(itemSize: CGSize(width: 75.0, height: 75.0), fillWidth: nil, lineSpacing: 0.0, itemSpacing: nil)), transition: transition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in }) transition.updateFrame(node: self.trendingPane, frame: contentFrame) - self.trendingPane.updateLayout(size: contentFrame.size, topInset: 0.0, bottomInset: bottomInset, isExpanded: false, isVisible: true, transition: transition) + self.trendingPane.updateLayout(size: contentFrame.size, topInset: 0.0, bottomInset: bottomInset, isExpanded: false, isVisible: true, deviceMetrics: deviceMetrics, transition: transition) transition.updateFrame(node: self.gridNode, frame: contentFrame) if firstLayout { diff --git a/submodules/TelegramUI/TelegramUI/StickerPaneSearchGlobaltem.swift b/submodules/TelegramUI/TelegramUI/StickerPaneSearchGlobaltem.swift index 65bce49a26..8557f94b73 100644 --- a/submodules/TelegramUI/TelegramUI/StickerPaneSearchGlobaltem.swift +++ b/submodules/TelegramUI/TelegramUI/StickerPaneSearchGlobaltem.swift @@ -37,6 +37,7 @@ final class StickerPaneSearchGlobalItem: GridItem { let strings: PresentationStrings let info: StickerPackCollectionInfo let topItems: [StickerPackItem] + let grid: Bool let installed: Bool let unread: Bool let open: () -> Void @@ -44,14 +45,17 @@ final class StickerPaneSearchGlobalItem: GridItem { let getItemIsPreviewed: (StickerPackItem) -> Bool let section: GridSection? = StickerPaneSearchGlobalSection() - let fillsRowWithHeight: CGFloat? = 128.0 + var fillsRowWithHeight: CGFloat? { + return self.grid ? nil : 128.0 + } - init(account: Account, theme: PresentationTheme, strings: PresentationStrings, info: StickerPackCollectionInfo, topItems: [StickerPackItem], installed: Bool, unread: Bool, open: @escaping () -> Void, install: @escaping () -> Void, getItemIsPreviewed: @escaping (StickerPackItem) -> Bool) { + init(account: Account, theme: PresentationTheme, strings: PresentationStrings, info: StickerPackCollectionInfo, topItems: [StickerPackItem], grid: Bool, installed: Bool, unread: Bool, open: @escaping () -> Void, install: @escaping () -> Void, getItemIsPreviewed: @escaping (StickerPackItem) -> Bool) { self.account = account self.theme = theme self.strings = strings self.info = info self.topItems = topItems + self.grid = grid self.installed = installed self.unread = unread self.open = open diff --git a/submodules/TelegramUI/TelegramUI/VerticalListContextResultsChatInputContextPanelNode.swift b/submodules/TelegramUI/TelegramUI/VerticalListContextResultsChatInputContextPanelNode.swift index 925a759fef..31c7f122f8 100644 --- a/submodules/TelegramUI/TelegramUI/VerticalListContextResultsChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/TelegramUI/VerticalListContextResultsChatInputContextPanelNode.swift @@ -126,7 +126,6 @@ final class VerticalListContextResultsChatInputContextPanelNode: ChatInputContex private var enqueuedTransitions: [(VerticalListContextResultsChatInputContextPanelTransition, Bool)] = [] private var validLayout: (CGSize, CGFloat, CGFloat)? - override init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings) { self.listView = ListView() self.listView.isOpaque = false diff --git a/submodules/TelegramUI/TelegramUI/VerticalListContextResultsChatInputPanelItem.swift b/submodules/TelegramUI/TelegramUI/VerticalListContextResultsChatInputPanelItem.swift index 016f1502ef..898fce5b81 100644 --- a/submodules/TelegramUI/TelegramUI/VerticalListContextResultsChatInputPanelItem.swift +++ b/submodules/TelegramUI/TelegramUI/VerticalListContextResultsChatInputPanelItem.swift @@ -8,6 +8,7 @@ import Postbox import TelegramPresentationData import RadialStatusNode import PhotoResources +import StickerResources final class VerticalListContextResultsChatInputPanelItem: ListViewItem { fileprivate let account: Account @@ -165,7 +166,6 @@ final class VerticalListContextResultsChatInputPanelItemNode: ListViewItemNode { var textString: NSAttributedString? var iconText: NSAttributedString? - var iconImageRepresentation: TelegramMediaImageRepresentation? var updateIconImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>? var updatedStatusSignal: Signal? @@ -178,8 +178,9 @@ final class VerticalListContextResultsChatInputPanelItemNode: ListViewItemNode { } var imageResource: TelegramMediaResource? + var stickerFile: TelegramMediaFile? switch item.result { - case let .externalReference(_, _, _, title, _, url, content, thumbnail, _): + case let .externalReference(_, _, _, _, _, url, content, thumbnail, _): if let thumbnail = thumbnail { imageResource = thumbnail.resource } @@ -198,11 +199,16 @@ final class VerticalListContextResultsChatInputPanelItemNode: ListViewItemNode { iconText = NSAttributedString(string: host.substring(to: host.index(after: host.startIndex)).uppercased(), font: iconFont, textColor: UIColor.white) } } - case let .internalReference(_, _, _, title, _, image, file, _): + case let .internalReference(_, _, _, _, _, image, file, _): if let image = image { imageResource = imageRepresentationLargerThan(image.representations, size: CGSize(width: 200.0, height: 200.0))?.resource } else if let file = file { - imageResource = smallestImageRepresentation(file.previewRepresentations)?.resource + if file.isSticker { + stickerFile = file + imageResource = file.resource + } else { + imageResource = smallestImageRepresentation(file.previewRepresentations)?.resource + } } } @@ -215,9 +221,15 @@ final class VerticalListContextResultsChatInputPanelItemNode: ListViewItemNode { var iconImageApply: (() -> Void)? if let imageResource = imageResource { - let iconSize = CGSize(width: 55.0, height: 55.0) + let boundingSize = CGSize(width: 55.0, height: 55.0) + let iconSize: CGSize + if let stickerFile = stickerFile, let dimensions = stickerFile.dimensions { + iconSize = dimensions.fitted(boundingSize) + } else { + iconSize = boundingSize + } let imageCorners = ImageCorners(topLeft: .Corner(2.0), topRight: .Corner(2.0), bottomLeft: .Corner(2.0), bottomRight: .Corner(2.0)) - let arguments = TransformImageArguments(corners: imageCorners, imageSize: iconSize, boundingSize: iconSize, intrinsicInsets: UIEdgeInsets()) + let arguments = TransformImageArguments(corners: imageCorners, imageSize: iconSize, boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets()) iconImageApply = iconImageLayout(arguments) updatedStatusSignal = item.account.postbox.mediaBox.resourceStatus(imageResource) @@ -235,9 +247,13 @@ final class VerticalListContextResultsChatInputPanelItemNode: ListViewItemNode { if updatedIconImageResource { if let imageResource = imageResource { - let tmpRepresentation = TelegramMediaImageRepresentation(dimensions: CGSize(width: 55.0, height: 55.0), resource: imageResource) - let tmpImage = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [tmpRepresentation], immediateThumbnailData: nil, reference: nil, partialReference: nil) - updateIconImageSignal = chatWebpageSnippetPhoto(account: item.account, photoReference: .standalone(media: tmpImage)) + if let stickerFile = stickerFile { + updateIconImageSignal = chatMessageSticker(account: item.account, file: stickerFile, small: false, fetched: true) + } else { + let tmpRepresentation = TelegramMediaImageRepresentation(dimensions: CGSize(width: 55.0, height: 55.0), resource: imageResource) + let tmpImage = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [tmpRepresentation], immediateThumbnailData: nil, reference: nil, partialReference: nil) + updateIconImageSignal = chatWebpageSnippetPhoto(account: item.account, photoReference: .standalone(media: tmpImage)) + } } else { updateIconImageSignal = .complete() } diff --git a/submodules/WebSearchUI/Sources/WebSearchControllerNode.swift b/submodules/WebSearchUI/Sources/WebSearchControllerNode.swift index c10344cfd0..998b641035 100644 --- a/submodules/WebSearchUI/Sources/WebSearchControllerNode.swift +++ b/submodules/WebSearchUI/Sources/WebSearchControllerNode.swift @@ -12,6 +12,7 @@ import MergeLists import AccountContext import GalleryUI import ChatListSearchItemHeader +import SegmentedControlNode private struct WebSearchContextResultStableId: Hashable { let result: ChatContextResult @@ -135,7 +136,7 @@ class WebSearchControllerNode: ASDisplayNode { private let segmentedBackgroundNode: ASDisplayNode private let segmentedSeparatorNode: ASDisplayNode - private let segmentedControl: UISegmentedControl + private let segmentedControlNode: SegmentedControlNode private let toolbarBackgroundNode: ASDisplayNode private let toolbarSeparatorNode: ASDisplayNode @@ -189,8 +190,11 @@ class WebSearchControllerNode: ASDisplayNode { self.segmentedBackgroundNode = ASDisplayNode() self.segmentedSeparatorNode = ASDisplayNode() - self.segmentedControl = UISegmentedControl(items: [strings.WebSearch_Images, strings.WebSearch_GIFs]) - self.segmentedControl.selectedSegmentIndex = 0 + let items = [ + strings.WebSearch_Images, + strings.WebSearch_GIFs + ] + self.segmentedControlNode = SegmentedControlNode(theme: SegmentedControlTheme(theme: theme), items: items.map { SegmentedControlItem(title: $0) }, selectedIndex: 0) self.toolbarBackgroundNode = ASDisplayNode() self.toolbarSeparatorNode = ASDisplayNode() @@ -221,7 +225,7 @@ class WebSearchControllerNode: ASDisplayNode { self.addSubnode(self.segmentedBackgroundNode) self.addSubnode(self.segmentedSeparatorNode) if case .media = mode { - self.view.addSubview(self.segmentedControl) + self.addSubnode(self.segmentedControlNode) } self.addSubnode(self.toolbarBackgroundNode) self.addSubnode(self.toolbarSeparatorNode) @@ -230,7 +234,16 @@ class WebSearchControllerNode: ASDisplayNode { self.addSubnode(self.attributionNode) self.addSubnode(self.badgeNode) - self.segmentedControl.addTarget(self, action: #selector(self.indexChanged), for: .valueChanged) + self.segmentedControlNode.selectedIndexChanged = { [weak self] index in + if let strongSelf = self, let scope = WebSearchScope(rawValue: Int32(index)) { + let _ = updateWebSearchSettingsInteractively(accountManager: strongSelf.context.sharedContext.accountManager) { _ -> WebSearchSettings in + return WebSearchSettings(scope: scope) + }.start() + strongSelf.requestUpdateInterfaceState(true) { current in + return current.withUpdatedScope(scope) + } + } + } self.cancelButton.addTarget(self, action: #selector(self.cancelPressed), forControlEvents: .touchUpInside) self.sendButton.addTarget(self, action: #selector(self.sendPressed), forControlEvents: .touchUpInside) @@ -334,7 +347,7 @@ class WebSearchControllerNode: ASDisplayNode { self.segmentedBackgroundNode.backgroundColor = self.theme.rootController.navigationBar.backgroundColor self.segmentedSeparatorNode.backgroundColor = self.theme.rootController.navigationBar.separatorColor - self.segmentedControl.tintColor = self.theme.rootController.navigationBar.accentTextColor + self.segmentedControlNode.updateTheme(SegmentedControlTheme(theme: self.theme)) self.toolbarBackgroundNode.backgroundColor = self.theme.rootController.navigationBar.backgroundColor self.toolbarSeparatorNode.backgroundColor = self.theme.rootController.navigationBar.separatorColor @@ -358,16 +371,14 @@ class WebSearchControllerNode: ASDisplayNode { var insets = layout.insets(options: [.input]) insets.top += navigationBarHeight - let segmentedHeight: CGFloat = self.segmentedControl.superview != nil ? 44.0 : 5.0 + let segmentedHeight: CGFloat = self.segmentedControlNode.supernode != nil ? 44.0 : 5.0 let panelY: CGFloat = insets.top - UIScreenPixel - 4.0 transition.updateFrame(node: self.segmentedBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: panelY), size: CGSize(width: layout.size.width, height: segmentedHeight))) transition.updateFrame(node: self.segmentedSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: panelY + segmentedHeight), size: CGSize(width: layout.size.width, height: UIScreenPixel))) - var controlSize = self.segmentedControl.sizeThatFits(layout.size) - controlSize.width = layout.size.width - layout.safeInsets.left - layout.safeInsets.right - 10.0 * 2.0 - - transition.updateFrame(view: self.segmentedControl, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + floor((layout.size.width - layout.safeInsets.left - layout.safeInsets.right - controlSize.width) / 2.0), y: panelY + 5.0), size: controlSize)) + let controlSize = self.segmentedControlNode.updateLayout(.stretchToFill(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - 10.0 * 2.0), transition: transition) + transition.updateFrame(node: self.segmentedControlNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + floor((layout.size.width - layout.safeInsets.left - layout.safeInsets.right - controlSize.width) / 2.0), y: panelY + 5.0), size: controlSize)) insets.top -= 4.0 @@ -471,7 +482,7 @@ class WebSearchControllerNode: ASDisplayNode { self.webSearchInterfaceStatePromise.set(self.webSearchInterfaceState) if let state = interfaceState.state { - self.segmentedControl.selectedSegmentIndex = Int(state.scope.rawValue) + self.segmentedControlNode.selectedIndex = Int(state.scope.rawValue) } if let validLayout = self.containerLayout { @@ -656,17 +667,6 @@ class WebSearchControllerNode: ASDisplayNode { } } - @objc private func indexChanged() { - if let scope = WebSearchScope(rawValue: Int32(self.segmentedControl.selectedSegmentIndex)) { - let _ = updateWebSearchSettingsInteractively(accountManager: self.context.sharedContext.accountManager) { _ -> WebSearchSettings in - return WebSearchSettings(scope: scope) - }.start() - self.requestUpdateInterfaceState(true) { current in - return current.withUpdatedScope(scope) - } - } - } - @objc private func cancelPressed() { self.cancel?() }