Merge branch 'navigation'

This commit is contained in:
Peter 2019-09-10 19:39:24 +04:00
commit afe71368b0
16 changed files with 801 additions and 230 deletions

10
BUCK
View File

@ -45,6 +45,7 @@ resource_dependencies = [
"//submodules/TelegramUI:TelegramUIResources",
"//:AppResources",
"//:AppStringResources",
"//:InfoPlistStringResources",
"//:AppIntentVocabularyResources",
"//:Icons",
"//:AdditionalIcons",
@ -80,6 +81,15 @@ apple_resource(
visibility = ["PUBLIC"],
)
apple_resource(
name = "InfoPlistStringResources",
files = [],
variants = glob([
"Telegram-iOS/*.lproj/InfoPlist.strings",
]),
visibility = ["PUBLIC"],
)
apple_asset_catalog(
name = "Icons",
dirs = [

View File

@ -114,7 +114,7 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, source: ChatC
if let (group, index) = groupAndIndex {
if !isSavedMessages {
let isArchived = group == Namespaces.PeerGroup.archive
items.append(.action(ContextMenuActionItem(text: isArchived ? strings.ChatList_Context_Archive : strings.ChatList_Context_Unarchive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: isArchived ? "Chat/Context Menu/Unarchive" : "Chat/Context Menu/Archive"), color: theme.contextMenu.primaryColor) }, action: { _, f in
items.append(.action(ContextMenuActionItem(text: isArchived ? strings.ChatList_Context_Unarchive : strings.ChatList_Context_Archive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: isArchived ? "Chat/Context Menu/Unarchive" : "Chat/Context Menu/Archive"), color: theme.contextMenu.primaryColor) }, action: { _, f in
if isArchived {
let _ = (context.account.postbox.transaction { transaction -> Void in
updatePeerGroupIdInteractively(transaction: transaction, peerId: peerId, groupId: .root)

View File

@ -509,6 +509,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
if let strongSelf = self {
if let navigationController = strongSelf.navigationController as? NavigationController {
let chatListController = ChatListControllerImpl(context: strongSelf.context, groupId: groupId, controlsHistoryPreload: false, enableDebugActions: false)
chatListController.navigationPresentation = .master
navigationController.pushViewController(chatListController)
strongSelf.chatListDisplayNode.chatListNode.clearHighlightAnimated(true)
}
@ -664,6 +665,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
switch item.content {
case let .groupReference(groupReference):
let chatListController = ChatListControllerImpl(context: strongSelf.context, groupId: groupReference.groupId, controlsHistoryPreload: false, enableDebugActions: false)
chatListController.navigationPresentation = .master
let contextController = ContextController(account: strongSelf.context.account, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, source: .controller(ContextControllerContentSourceImpl(controller: chatListController, sourceNode: node)), items: archiveContextMenuItems(context: strongSelf.context, groupId: groupReference.groupId, chatListController: strongSelf), reactionItems: [], gesture: gesture)
strongSelf.presentInGlobalOverlay(contextController)
case let .peer(peer):
@ -1094,6 +1096,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
}
case let .groupReference(groupId, _, _, _, _):
let chatListController = ChatListControllerImpl(context: self.context, groupId: groupId, controlsHistoryPreload: false, enableDebugActions: false)
chatListController.navigationPresentation = .master
chatListController.containerLayoutUpdated(ContainerViewLayout(size: contentSize, metrics: LayoutMetrics(), deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(), safeInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: false), transition: .immediate)
return (chatListController, sourceRect)
}

View File

@ -281,7 +281,8 @@ public class ContactsController: ViewController {
}
let presentPeersNearby = {
let controller = strongSelf.context.sharedContext.makePeersNearbyController(context: strongSelf.context)
(strongSelf.navigationController as? NavigationController)?.replaceAllButRootController(controller, animated: true, completion: { [weak self] in
controller.navigationPresentation = .master
(strongSelf.navigationController as? NavigationController)?.pushViewController(controller, animated: true, completion: { [weak self] in
if let strongSelf = self {
strongSelf.contactsNode.contactListNode.listNode.clearHighlightAnimated(true)
}
@ -294,6 +295,7 @@ public class ContactsController: ViewController {
default:
let controller = PermissionController(context: strongSelf.context, splashScreen: false)
controller.setState(.permission(.nearbyLocation(status: PermissionRequestStatus(accessType: status))), animated: false)
controller.navigationPresentation = .master
controller.proceed = { result in
if result {
presentPeersNearby()

View File

@ -10,6 +10,7 @@ private enum ContextItemNode {
}
final class ContextActionsContainerNode: ASDisplayNode {
private var effectView: UIVisualEffectView?
private var itemNodes: [ContextItemNode]
init(theme: PresentationTheme, items: [ContextMenuItem], getController: @escaping () -> ContextController?, actionSelected: @escaping (ContextMenuActionResult) -> Void) {
@ -51,8 +52,28 @@ final class ContextActionsContainerNode: ASDisplayNode {
})
}
func updateLayout(constrainedWidth: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize {
let minActionsWidth = min(constrainedWidth, max(250.0, floor(constrainedWidth / 3.0)))
func updateLayout(widthClass: ContainerViewLayoutSizeClass, constrainedWidth: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize {
var minActionsWidth: CGFloat = 250.0
switch widthClass {
case .compact:
minActionsWidth = max(minActionsWidth, floor(constrainedWidth / 3.0))
if let effectView = self.effectView {
self.effectView = nil
effectView.removeFromSuperview()
}
case .regular:
if self.effectView == nil {
let effectView: UIVisualEffectView
if #available(iOS 10.0, *) {
effectView = UIVisualEffectView(effect: UIBlurEffect(style: .regular))
} else {
effectView = UIVisualEffectView(effect: UIBlurEffect(style: .light))
}
self.effectView = effectView
self.view.insertSubview(effectView, at: 0)
}
}
minActionsWidth = min(minActionsWidth, constrainedWidth)
let separatorHeight: CGFloat = 8.0
var maxWidth: CGFloat = 0.0
@ -111,7 +132,11 @@ final class ContextActionsContainerNode: ASDisplayNode {
}
}
return CGSize(width: maxWidth, height: verticalOffset)
let size = CGSize(width: maxWidth, height: verticalOffset)
if let effectView = self.effectView {
transition.updateFrame(view: effectView, frame: CGRect(origin: CGPoint(), size: size))
}
return size
}
func updateTheme(theme: PresentationTheme) {

View File

@ -27,9 +27,11 @@ public final class ContextExtractedContentNode: ASDisplayNode {
}
final class ContextControllerContentNode: ASDisplayNode {
let sourceNode: ASDisplayNode
let controller: ViewController
init(controller: ViewController) {
init(sourceNode: ASDisplayNode, controller: ViewController) {
self.sourceNode = sourceNode
self.controller = controller
super.init()

View File

@ -80,6 +80,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
private var propertyAnimator: AnyObject?
private var displayLinkAnimator: DisplayLinkAnimator?
private let dimNode: ASDisplayNode
private let withoutBlurDimNode: ASDisplayNode
private let dismissNode: ASDisplayNode
private let clippingNode: ASDisplayNode
@ -132,6 +133,10 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
self.dimNode.backgroundColor = theme.contextMenu.dimColor
self.dimNode.alpha = 0.0
self.withoutBlurDimNode = ASDisplayNode()
self.withoutBlurDimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.4)
self.withoutBlurDimNode.alpha = 0.0
self.dismissNode = ASDisplayNode()
self.clippingNode = ASDisplayNode()
@ -164,8 +169,8 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
self.scrollNode.view.delegate = self
self.view.addSubview(self.effectView)
self.addSubnode(self.dimNode)
self.addSubnode(self.withoutBlurDimNode)
self.addSubnode(self.clippingNode)
@ -437,14 +442,14 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
self.originalProjectedContentViewFrame = (convertFrame(takenViewInfo.contentContainingNode.frame, from: parentSupernode.view, to: self.view), convertFrame(takenViewInfo.contentContainingNode.contentRect, from: takenViewInfo.contentContainingNode.view, to: self.view))
}
case let .controller(source):
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 contentParentNode = ContextControllerContentNode(sourceNode: sourceNode, controller: source.controller)
self.contentContainerNode.contentNode = .controller(contentParentNode)
self.contentContainerNode.clipsToBounds = true
self.contentContainerNode.cornerRadius = 14.0
self.contentContainerNode.addSubnode(contentParentNode)
let projectedFrame = convertFrame(sourceNodeRect, from: sourceNode.view, to: self.view)
self.originalProjectedContentViewFrame = (projectedFrame, projectedFrame)
}
@ -483,8 +488,13 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
self.updateLayout(layout: validLayout, transition: .immediate, previousActionsContainerNode: nil)
}
self.dimNode.alpha = 1.0
self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2 * animationDurationFactor)
if !self.dimNode.isHidden {
self.dimNode.alpha = 1.0
self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2 * animationDurationFactor)
} else {
self.withoutBlurDimNode.alpha = 1.0
self.withoutBlurDimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2 * animationDurationFactor)
}
if #available(iOS 10.0, *) {
if let propertyAnimator = self.propertyAnimator {
@ -662,7 +672,12 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
})
}
self.dimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: transitionDuration * animationDurationFactor, removeOnCompletion: false)
if !self.dimNode.isHidden {
self.dimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: transitionDuration * animationDurationFactor, removeOnCompletion: false)
} else {
self.withoutBlurDimNode.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()
@ -799,7 +814,11 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
})
}
self.dimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: transitionDuration * animationDurationFactor, removeOnCompletion: false)
if !self.dimNode.isHidden {
self.dimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: transitionDuration * animationDurationFactor, removeOnCompletion: false)
} else {
self.withoutBlurDimNode.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()
@ -872,10 +891,6 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
strongSelf.reactionContextNode = nil
reactionCompleted = true
intermediateCompletion()
/*strongSelf.animateOut(result: .default, completion: {
reactionCompleted = true
intermediateCompletion()
})*/
})
}
@ -939,6 +954,31 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
transition.updateFrame(view: self.effectView, frame: CGRect(origin: CGPoint(), size: layout.size))
transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: layout.size))
transition.updateFrame(node: self.withoutBlurDimNode, frame: CGRect(origin: CGPoint(), size: layout.size))
switch layout.metrics.widthClass {
case .compact:
if self.effectView.superview == nil {
self.view.insertSubview(self.effectView, at: 0)
if #available(iOS 10.0, *) {
if let propertyAnimator = self.propertyAnimator {
let propertyAnimator = propertyAnimator as? UIViewPropertyAnimator
propertyAnimator?.stopAnimation(true)
}
}
self.effectView.effect = makeCustomZoomBlurEffect()
self.dimNode.alpha = 1.0
}
self.dimNode.isHidden = false
self.withoutBlurDimNode.isHidden = true
case .regular:
if self.effectView.superview != nil {
self.effectView.removeFromSuperview()
self.withoutBlurDimNode.alpha = 1.0
}
self.dimNode.isHidden = true
self.withoutBlurDimNode.isHidden = false
}
transition.updateFrame(node: self.clippingNode, frame: CGRect(origin: CGPoint(), size: layout.size))
transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: layout.size))
@ -958,7 +998,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
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 actionsSize = self.actionsContainerNode.updateLayout(widthClass: layout.metrics.widthClass, constrainedWidth: layout.size.width - actionsSideInset * 2.0, transition: actionsContainerTransition)
let contentSize = originalProjectedContentViewFrame.1.size
self.contentContainerNode.updateLayout(size: contentSize, scaledSize: contentSize, transition: transition)
@ -1006,65 +1046,105 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
}
}
case let .controller(contentParentNode):
let contentActionsSpacing: CGFloat = actionsSideInset
let topEdge = max(contentTopInset, self.contentAreaInScreenSpace?.minY ?? 0.0)
let projectedFrame = convertFrame(contentParentNode.sourceNode.bounds, from: contentParentNode.sourceNode.view, to: self.view)
self.originalProjectedContentViewFrame = (projectedFrame, projectedFrame)
//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 contentUnscaledSize: CGSize
if !contentParentNode.controller.preferredContentSize.width.isZero {
contentUnscaledSize = contentParentNode.controller.preferredContentSize
} else {
let proposedContentHeight = layout.size.height - topEdge - contentActionsSpacing - actionsSize.height - layout.intrinsicInsets.bottom - actionsBottomInset
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: min(maximumActionsFrameOrigin, floor((layout.size.height - contentActionsSpacing - contentSize.height) / 2.0) + contentSize.height + contentActionsSpacing)), 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)
if let originalProjectedContentViewFrame = self.originalProjectedContentViewFrame {
let contentActionsSpacing: CGFloat = actionsSideInset
let topEdge = max(contentTopInset, self.contentAreaInScreenSpace?.minY ?? 0.0)
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(widthClass: layout.metrics.widthClass, constrainedWidth: layout.size.width - actionsSideInset * 2.0, transition: actionsContainerTransition)
let contentScale = (layout.size.width - actionsSideInset * 2.0) / layout.size.width
var contentUnscaledSize: CGSize
if case .compact = layout.metrics.widthClass {
let proposedContentHeight = layout.size.height - topEdge - contentActionsSpacing - actionsSize.height - layout.intrinsicInsets.bottom - actionsBottomInset
contentUnscaledSize = CGSize(width: layout.size.width, height: max(400.0, proposedContentHeight))
if let preferredSize = contentParentNode.controller.preferredContentSizeForLayout(ContainerViewLayout(size: contentUnscaledSize, metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact), deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(), safeInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: false)) {
contentUnscaledSize = preferredSize
}
} else {
let proposedContentHeight = layout.size.height - topEdge - contentActionsSpacing - actionsSize.height - layout.intrinsicInsets.bottom - actionsBottomInset
contentUnscaledSize = CGSize(width: min(layout.size.width, 340.0), height: min(568.0, proposedContentHeight))
if let preferredSize = contentParentNode.controller.preferredContentSizeForLayout(ContainerViewLayout(size: contentUnscaledSize, metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact), deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(), safeInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: false)) {
contentUnscaledSize = preferredSize
}
}
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
var originalContentFrame: CGRect
var contentHeight: CGFloat
if case .compact = layout.metrics.widthClass {
originalActionsFrame = CGRect(origin: CGPoint(x: actionsSideInset, y: min(maximumActionsFrameOrigin, floor((layout.size.height - contentActionsSpacing - contentSize.height) / 2.0) + contentSize.height + contentActionsSpacing)), size: actionsSize)
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)
}
contentHeight = max(layout.size.height, max(layout.size.height, originalActionsFrame.maxY + actionsBottomInset) - originalContentFrame.minY + contentTopInset)
} else {
originalContentFrame = CGRect(origin: CGPoint(x: floor(originalProjectedContentViewFrame.1.midX - contentSize.width / 2.0), y: floor(originalProjectedContentViewFrame.1.midY - contentSize.height / 2.0)), size: contentSize)
originalContentFrame.origin.x = min(originalContentFrame.origin.x, layout.size.width - actionsSideInset - contentSize.width)
originalContentFrame.origin.x = max(originalContentFrame.origin.x, actionsSideInset)
originalContentFrame.origin.y = min(originalContentFrame.origin.y, layout.size.height - layout.intrinsicInsets.bottom - actionsSideInset - contentSize.height)
originalContentFrame.origin.y = max(originalContentFrame.origin.y, contentTopInset)
if originalContentFrame.maxX <= layout.size.width - actionsSideInset - actionsSize.width - contentActionsSpacing {
originalActionsFrame = CGRect(origin: CGPoint(x: originalContentFrame.maxX + contentActionsSpacing, y: originalContentFrame.minY), size: actionsSize)
if originalActionsFrame.maxX > layout.size.width - actionsSideInset {
let offset = originalActionsFrame.maxX - (layout.size.width - actionsSideInset)
originalActionsFrame.origin.x -= offset
originalContentFrame.origin.x -= offset
}
} else {
originalActionsFrame = CGRect(origin: CGPoint(x: originalContentFrame.minX - contentActionsSpacing - actionsSize.width, y: originalContentFrame.minY), size: actionsSize)
if originalActionsFrame.minX < actionsSideInset {
let offset = actionsSideInset - originalActionsFrame.minX
originalActionsFrame.origin.x += offset
originalContentFrame.origin.x += offset
}
}
contentHeight = layout.size.height
contentHeight = max(contentHeight, originalActionsFrame.maxY + actionsBottomInset)
contentHeight = max(contentHeight, originalContentFrame.maxY + actionsBottomInset)
}
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)
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)
}
}
}

View File

@ -23,21 +23,17 @@ public final class ContextControllerSourceNode: ASDisplayNode {
guard let strongSelf = self, !strongSelf.bounds.width.isZero else {
return
}
let minScale: CGFloat = (strongSelf.bounds.width - 20.0) / strongSelf.bounds.width
let minScale: CGFloat = (strongSelf.bounds.width - 10.0) / strongSelf.bounds.width
let currentScale = 1.0 * (1.0 - progress) + minScale * progress
switch update {
case .update:
strongSelf.layer.sublayerTransform = CATransform3DMakeScale(currentScale, currentScale, 1.0)
case .begin:
strongSelf.layer.sublayerTransform = CATransform3DMakeScale(currentScale, currentScale, 1.0)
/*let previousProgress: CGFloat = 0.0
let previousScale = 1.0 * (1.0 - previousProgress) + minScale * previousProgress
strongSelf.layer.sublayerTransform = CATransform3DMakeScale(currentScale, currentScale, 1.0)
strongSelf.layer.animate(from: previousScale as NSNumber, to: currentScale as NSNumber, keyPath: "sublayerTransform.scale", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.3, additive: false)*/
case let .ended(previousProgress):
let previousScale = 1.0 * (1.0 - previousProgress) + minScale * previousProgress
strongSelf.layer.sublayerTransform = CATransform3DMakeScale(currentScale, currentScale, 1.0)
strongSelf.layer.animate(from: previousScale as NSNumber, to: currentScale as NSNumber, keyPath: "sublayerTransform.scale", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.3)
strongSelf.layer.animateSpring(from: previousScale as NSNumber, to: currentScale as NSNumber, keyPath: "sublayerTransform.scale", duration: 0.5, delay: 0.0, initialVelocity: 0.0, damping: 90.0)
}
}
contextGesture.activated = { [weak self] gesture in

View File

@ -40,6 +40,7 @@ private func cancelParentGestures(view: UIView) {
public final class ContextGesture: UIGestureRecognizer, UIGestureRecognizerDelegate {
private var currentProgress: CGFloat = 0.0
private var delayTimer: Timer?
private var animator: DisplayLinkAnimator?
private var isValidated: Bool = false
public var activationProgress: ((CGFloat, ContextGestureTransition) -> Void)?
@ -62,6 +63,8 @@ public final class ContextGesture: UIGestureRecognizer, UIGestureRecognizerDeleg
self.isValidated = false
self.externalUpdated = nil
self.externalEnded = nil
self.animator?.invalidate()
self.animator = nil
}
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
@ -80,6 +83,33 @@ public final class ContextGesture: UIGestureRecognizer, UIGestureRecognizerDeleg
return
}
strongSelf.isValidated = true
if strongSelf.animator == nil {
strongSelf.animator = DisplayLinkAnimator(duration: 0.3, from: 0.0, to: 1.0, update: { value in
guard let strongSelf = self else {
return
}
if strongSelf.isValidated {
strongSelf.currentProgress = value
strongSelf.activationProgress?(value, .update)
}
}, completion: {
guard let strongSelf = self else {
return
}
switch strongSelf.state {
case .possible:
strongSelf.delayTimer?.invalidate()
strongSelf.animator?.invalidate()
strongSelf.activated?(strongSelf)
if let view = strongSelf.view?.superview {
cancelParentGestures(view: view)
}
strongSelf.state = .began
default:
break
}
})
}
strongSelf.activationProgress?(strongSelf.currentProgress, .begin)
}, selector: #selector(TimerTargetWrapper.timerEvent), userInfo: nil, repeats: false)
self.delayTimer = delayTimer
@ -91,7 +121,7 @@ public final class ContextGesture: UIGestureRecognizer, UIGestureRecognizerDeleg
super.touchesMoved(touches, with: event)
if let touch = touches.first {
if #available(iOS 9.0, *) {
/*if #available(iOS 9.0, *) {
let maxForce: CGFloat = max(2.5, min(3.0, touch.maximumPossibleForce))
let progress = touch.force / maxForce
self.currentProgress = progress
@ -111,7 +141,7 @@ public final class ContextGesture: UIGestureRecognizer, UIGestureRecognizerDeleg
break
}
}
}
}*/
self.externalUpdated?(self.view, touch.location(in: self.view))
}
@ -131,6 +161,7 @@ public final class ContextGesture: UIGestureRecognizer, UIGestureRecognizerDeleg
}
self.delayTimer?.invalidate()
self.animator?.invalidate()
self.state = .failed
}
@ -145,6 +176,7 @@ public final class ContextGesture: UIGestureRecognizer, UIGestureRecognizerDeleg
}
self.delayTimer?.invalidate()
self.animator?.invalidate()
self.state = .failed
}
@ -154,6 +186,7 @@ public final class ContextGesture: UIGestureRecognizer, UIGestureRecognizerDeleg
self.activationProgress?(0.0, .ended(self.currentProgress))
self.delayTimer?.invalidate()
self.animator?.invalidate()
self.state = .failed
}
}
@ -163,6 +196,7 @@ public final class ContextGesture: UIGestureRecognizer, UIGestureRecognizerDeleg
let previousProgress = self.currentProgress
self.currentProgress = 0.0
self.delayTimer?.invalidate()
self.animator?.invalidate()
self.isValidated = false
self.activationProgress?(0.0, .ended(previousProgress))
}

View File

@ -3,8 +3,8 @@ load("//Config:buck_rule_macros.bzl", "framework")
framework(
name = "Display",
srcs = glob([
"Display/*.swift",
"Display/*.m",
"Display/**/*.swift",
"Display/**/*.m",
]),
headers = glob([
"Display/*.h",

View File

@ -22,6 +22,7 @@ public protocol ContainableController: class {
func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition)
func updateToInterfaceOrientation(_ orientation: UIInterfaceOrientation)
func updateModalTransition(_ value: CGFloat, transition: ContainedViewLayoutTransition)
func preferredContentSizeForLayout(_ layout: ContainerViewLayout) -> CGSize?
func viewWillAppear(_ animated: Bool)
func viewWillDisappear(_ animated: Bool)

View File

@ -44,7 +44,12 @@ public final class DisplayLinkAnimator {
self.displayLink.invalidate()
}
@objc func tick() {
public func invalidate() {
self.displayLink.isPaused = true
self.displayLink.invalidate()
}
@objc private func tick() {
if self.completed {
return
}

View File

@ -42,6 +42,7 @@ private final class NavigationControllerView: UITracingLayerView {
var inTransition = false
let sharedStatusBar: StatusBar
let masterContainerView: NavigationControllerContainerView
let containerView: NavigationControllerContainerView
let separatorView: UIView
var navigationBackgroundView: UIView?
@ -51,24 +52,15 @@ private final class NavigationControllerView: UITracingLayerView {
var masterDetailsBlackout: ASDisplayNode?
var topControllerNode: ASDisplayNode?
/*override var accessibilityElements: [Any]? {
get {
var accessibilityElements: [Any] = []
if let topControllerNode = self.topControllerNode {
addAccessibilityChildren(of: topControllerNode, container: self, to: &accessibilityElements)
}
return accessibilityElements
} set(value) {
}
}*/
override init(frame: CGRect) {
self.masterContainerView = NavigationControllerContainerView()
self.containerView = NavigationControllerContainerView()
self.separatorView = UIView()
self.sharedStatusBar = StatusBar()
super.init(frame: frame)
self.addSubview(self.masterContainerView)
self.addSubview(self.containerView)
}
@ -149,6 +141,7 @@ open class NavigationController: UINavigationController, ContainableController,
private var scheduledLayoutTransitionRequestId: Int = 0
private var scheduledLayoutTransitionRequest: (Int, ContainedViewLayoutTransition)?
private var masterTransitionCoordinator: NavigationTransitionCoordinator?
private var navigationTransitionCoordinator: NavigationTransitionCoordinator?
private var currentPushDisposable = MetaDisposable()
@ -235,18 +228,16 @@ open class NavigationController: UINavigationController, ContainableController,
self.controllerView.separatorView.backgroundColor = theme.navigationBar.separatorColor
self.controllerView.navigationBackgroundView?.backgroundColor = theme.navigationBar.backgroundColor
self.controllerView.navigationSeparatorView?.backgroundColor = theme.navigationBar.separatorColor
// if let emptyDetailView = self.controllerView.emptyDetailView {
// emptyDetailView.image = theme.emptyDetailIcon
// if let image = theme.emptyDetailIcon {
// emptyDetailView.frame = CGRect(origin: CGPoint(x: floor(emptyDetailView.center.x - image.size.width / 2.0), y: floor(emptyDetailView.center.y - image.size.height / 2.0)), size: image.size)
// }
// }
}
}
private var previouslyLaidOutMasterController: UIViewController?
private var previouslyLaidOutTopController: UIViewController?
open func preferredContentSizeForLayout(_ layout: ContainerViewLayout) -> CGSize? {
return nil
}
private func layoutConfiguration(for layout: ContainerViewLayout) -> ControllerLayoutConfiguration {
switch self.mode {
case .single:
@ -292,7 +283,7 @@ open class NavigationController: UINavigationController, ContainableController,
switch layoutConfiguration {
case .masterDetail:
self.viewControllers.first?.view.clipsToBounds = true
self.controllerView.masterContainerView.clipsToBounds = true
self.controllerView.containerView.clipsToBounds = true
let masterData = layoutDataForConfiguration(layoutConfiguration, layout: layout, index: 0)
firstControllerFrameAndLayout = masterData
@ -419,6 +410,7 @@ open class NavigationController: UINavigationController, ContainableController,
})
}
}
self.controllerView.masterContainerView.clipsToBounds = false
self.controllerView.containerView.clipsToBounds = false
lastControllerFrameAndLayout = layoutDataForConfiguration(layoutConfiguration, layout: layout, index: 1)
transition.updateFrame(view: self.controllerView.separatorView, frame: CGRect(origin: CGPoint(x: -UIScreenPixel, y: 0.0), size: CGSize(width: UIScreenPixel, height: layout.size.height)), completion: { [weak self] completed in
@ -427,6 +419,9 @@ open class NavigationController: UINavigationController, ContainableController,
}
})
}
if let firstControllerFrameAndLayout = firstControllerFrameAndLayout {
transition.updateFrame(view: self.controllerView.masterContainerView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: firstControllerFrameAndLayout.0.size))
}
transition.updateFrame(view: self.controllerView.containerView, frame: CGRect(origin: CGPoint(x: firstControllerFrameAndLayout?.0.maxX ?? 0.0, y: 0.0), size: lastControllerFrameAndLayout.0.size))
switch layoutConfiguration {
@ -451,11 +446,28 @@ open class NavigationController: UINavigationController, ContainableController,
}
var controllersAndFrames: [(Bool, ControllerRecord, ContainerViewLayout)] = []
var masterRange: ClosedRange<Int>?
if case .masterDetail = layoutConfiguration {
for i in 0 ..< self._viewControllers.count {
if let controller = self._viewControllers[i].controller as? ViewController {
switch controller.navigationPresentation {
case .default:
break
case .master:
if let masterRangeValue = masterRange {
masterRange = masterRangeValue.lowerBound ... i
} else {
masterRange = i ... i
}
}
}
}
}
for i in 0 ..< self._viewControllers.count {
if let controller = self._viewControllers[i].controller as? ViewController {
if i == 0 {
controller.navigationBar?.previousItem = nil
} else if case .masterDetail = layoutConfiguration, i == 1 {
} else if let masterRange = masterRange, i == masterRange.upperBound + 1 {
controller.navigationBar?.previousItem = .close
} else {
controller.navigationBar?.previousItem = .item(self.viewControllers[i - 1].navigationItem)
@ -468,13 +480,25 @@ open class NavigationController: UINavigationController, ContainableController,
}
self.viewControllers[i].navigation_setNavigationController(self)
if i == 0, let (_, layout) = firstControllerFrameAndLayout {
if let (_, layout) = firstControllerFrameAndLayout, let controller = self._viewControllers[i].controller as? ViewController, case .master = controller.navigationPresentation {
controllersAndFrames.append((true, self._viewControllers[i], layout))
} else if i == self._viewControllers.count - 1 {
controllersAndFrames.append((false, self._viewControllers[i], lastControllerFrameAndLayout.1))
}
}
while controllersAndFrames.count >= 2 {
if controllersAndFrames[0].0 {
if controllersAndFrames[1].0 {
controllersAndFrames.remove(at: 0)
} else {
break
}
} else {
break
}
}
var masterController: UIViewController?
var appearingMasterController: ControllerRecord?
var appearingDetailController: ControllerRecord?
@ -506,10 +530,10 @@ open class NavigationController: UINavigationController, ContainableController,
} else {
appearingDetailController = record
}
} else if record.controller.view.superview !== (isMaster ? self.controllerView : self.controllerView.containerView) {
} else if record.controller.view.superview !== (isMaster ? self.controllerView.masterContainerView : self.controllerView.containerView) {
record.controller.setIgnoreAppearanceMethodInvocations(true)
if isMaster {
self.controllerView.insertSubview(record.controller.view, at: 0)
self.controllerView.masterContainerView.addSubview(record.controller.view)
} else {
self.controllerView.containerView.addSubview(record.controller.view)
}
@ -535,8 +559,76 @@ open class NavigationController: UINavigationController, ContainableController,
}
var animatedAppearingDetailController = false
var animatedAppearingMasterController = false
if let previousController = self.previouslyLaidOutTopController, !controllersAndFrames.contains(where: { $0.1.controller === previousController }), previousController.view.superview != nil {
if let previousController = self.previouslyLaidOutMasterController, !controllersAndFrames.contains(where: { $0.1.controller === previousController }), previousController.view.superview != nil {
if transition.isAnimated, let record = appearingMasterController {
animatedAppearingMasterController = true
previousController.viewWillDisappear(true)
record.controller.viewWillAppear(true)
record.controller.setIgnoreAppearanceMethodInvocations(true)
if let controller = record.controller as? ViewController, !controller.hasActiveInput {
let (_, controllerLayout) = self.layoutDataForConfiguration(self.layoutConfiguration(for: layout), layout: layout, index: 0)
let appliedLayout = controllerLayout.withUpdatedInputHeight(controller.hasActiveInput ? controllerLayout.inputHeight : nil)
controller.containerLayoutUpdated(appliedLayout, transition: .immediate)
}
self.controllerView.masterContainerView.addSubview(record.controller.view)
record.controller.setIgnoreAppearanceMethodInvocations(false)
if let _ = previousControllers.firstIndex(where: { $0.controller === record.controller }) {
let masterTransitionCoordinator = NavigationTransitionCoordinator(transition: .Pop, container: self.controllerView.masterContainerView, topView: previousController.view, topNavigationBar: (previousController as? ViewController)?.navigationBar, bottomView: record.controller.view, bottomNavigationBar: (record.controller as? ViewController)?.navigationBar)
self.masterTransitionCoordinator = masterTransitionCoordinator
self.controllerView.inTransition = true
masterTransitionCoordinator.animateCompletion(0.0, completion: { [weak self] in
if let strongSelf = self {
strongSelf.masterTransitionCoordinator = nil
strongSelf.controllerView.inTransition = false
record.controller.viewDidAppear(true)
previousController.setIgnoreAppearanceMethodInvocations(true)
previousController.view.removeFromSuperview()
previousController.setIgnoreAppearanceMethodInvocations(false)
previousController.viewDidDisappear(true)
}
})
} else {
if let index = self._viewControllers.firstIndex(where: { $0.controller === previousController }) {
self._viewControllers[index].transition = .appearance
}
let masterTransitionCoordinator = NavigationTransitionCoordinator(transition: .Push, container: self.controllerView.masterContainerView, topView: record.controller.view, topNavigationBar: (record.controller as? ViewController)?.navigationBar, bottomView: previousController.view, bottomNavigationBar: (previousController as? ViewController)?.navigationBar)
self.masterTransitionCoordinator = masterTransitionCoordinator
self.controllerView.inTransition = true
masterTransitionCoordinator.animateCompletion(0.0, completion: { [weak self] in
if let strongSelf = self {
if let index = strongSelf._viewControllers.firstIndex(where: { $0.controller === previousController }) {
strongSelf._viewControllers[index].transition = .none
}
strongSelf.masterTransitionCoordinator = nil
strongSelf.controllerView.inTransition = false
record.controller.viewDidAppear(true)
previousController.setIgnoreAppearanceMethodInvocations(true)
previousController.view.removeFromSuperview()
previousController.setIgnoreAppearanceMethodInvocations(false)
previousController.viewDidDisappear(true)
}
})
}
} else {
previousController.viewWillDisappear(false)
previousController.view.removeFromSuperview()
previousController.viewDidDisappear(false)
}
}
if let previousController = self.previouslyLaidOutTopController, previousController !== self.previouslyLaidOutMasterController, !controllersAndFrames.contains(where: { $0.1.controller === previousController }), previousController.view.superview != nil {
if transition.isAnimated, let record = appearingDetailController {
animatedAppearingDetailController = true
@ -554,7 +646,6 @@ open class NavigationController: UINavigationController, ContainableController,
record.controller.setIgnoreAppearanceMethodInvocations(false)
if let _ = previousControllers.firstIndex(where: { $0.controller === record.controller }) {
//previousControllers[index].transition = .appearance
let navigationTransitionCoordinator = NavigationTransitionCoordinator(transition: .Pop, container: self.controllerView.containerView, topView: previousController.view, topNavigationBar: (previousController as? ViewController)?.navigationBar, bottomView: record.controller.view, bottomNavigationBar: (record.controller as? ViewController)?.navigationBar)
self.navigationTransitionCoordinator = navigationTransitionCoordinator
@ -615,10 +706,10 @@ open class NavigationController: UINavigationController, ContainableController,
}
}
if let record = appearingMasterController, let firstControllerFrameAndLayout = firstControllerFrameAndLayout {
if !animatedAppearingMasterController, let record = appearingMasterController, let firstControllerFrameAndLayout = firstControllerFrameAndLayout {
record.controller.viewWillAppear(false)
record.controller.setIgnoreAppearanceMethodInvocations(true)
self.controllerView.insertSubview(record.controller.view, belowSubview: self.controllerView.containerView)
self.controllerView.masterContainerView.addSubview(record.controller.view)
record.controller.setIgnoreAppearanceMethodInvocations(false)
record.controller.viewDidAppear(false)
if let controller = record.controller as? ViewController {
@ -799,127 +890,281 @@ open class NavigationController: UINavigationController, ContainableController,
@objc func panGesture(_ recognizer: UIPanGestureRecognizer) {
switch recognizer.state {
case .began:
guard let layout = self.validLayout else {
case .began:
guard let layout = self.validLayout else {
return
}
var beginMasterGesture = false
var beginDetailGesture = false
let masterControllers: [ControllerRecord]
let detailControllers: [ControllerRecord]
switch self.layoutConfiguration(for: layout) {
case .masterDetail:
masterControllers = self._viewControllers.filter({ record in
if let controller = record.controller as? ViewController {
if case .master = controller.navigationPresentation {
return true
} else {
return false
}
} else {
return false
}
})
detailControllers = self._viewControllers.filter({ record in
if let controller = record.controller as? ViewController {
if case .master = controller.navigationPresentation {
return false
} else {
return true
}
} else {
return true
}
})
let locationInMaster = recognizer.location(in: self.controllerView.masterContainerView)
let locationInDetail = recognizer.location(in: self.controllerView.containerView)
if self.controllerView.masterContainerView.bounds.contains(locationInMaster) {
beginMasterGesture = masterControllers.count >= 2
}
if self.controllerView.containerView.bounds.contains(locationInDetail) {
beginDetailGesture = detailControllers.count >= 2
}
case .single:
beginDetailGesture = self._viewControllers.count >= 2
masterControllers = []
detailControllers = self._viewControllers
}
if beginMasterGesture {
guard self.masterTransitionCoordinator == nil else {
return
}
let topController = masterControllers[masterControllers.count - 1].controller
let bottomController = masterControllers[masterControllers.count - 2].controller
if let topController = topController as? ViewController {
if !topController.attemptNavigation({ [weak self, weak topController] in
if let topController = topController {
self?.filterController(topController, animated: true)
}
}) {
return
}
}
topController.viewWillDisappear(true)
let topView = topController.view!
if let bottomController = bottomController as? ViewController {
let (_, controllerLayout) = self.layoutDataForConfiguration(self.layoutConfiguration(for: layout), layout: layout, index: 0)
let appliedLayout = controllerLayout.withUpdatedInputHeight(bottomController.hasActiveInput ? controllerLayout.inputHeight : nil)
bottomController.containerLayoutUpdated(appliedLayout, transition: .immediate)
}
bottomController.viewWillAppear(true)
let bottomView = bottomController.view!
let masterTransitionCoordinator = NavigationTransitionCoordinator(transition: .Pop, container: self.controllerView.masterContainerView, topView: topView, topNavigationBar: (topController as? ViewController)?.navigationBar, bottomView: bottomView, bottomNavigationBar: (bottomController as? ViewController)?.navigationBar, didUpdateProgress: { [weak self, weak topController, weak bottomController] progress, transition in
if let strongSelf = self, let topController = topController, let bottomController = bottomController {
(bottomController as? ViewController)?.updateNavigationCustomData((topController as? ViewController)?.customData, progress: 1.0 - progress, transition: transition)
}
})
if let bottomController = bottomController as? ViewController {
bottomController.displayNode.recursivelyEnsureDisplaySynchronously(true)
}
self.masterTransitionCoordinator = masterTransitionCoordinator
}
if beginDetailGesture {
guard self.navigationTransitionCoordinator == nil else {
return
}
let beginGesture: Bool
switch self.layoutConfiguration(for: layout) {
case .masterDetail:
let location = recognizer.location(in: self.controllerView.containerView)
if self.controllerView.containerView.bounds.contains(location) {
beginGesture = self._viewControllers.count >= 3
} else {
beginGesture = false
}
case .single:
beginGesture = self._viewControllers.count >= 2
let topController = detailControllers[detailControllers.count - 1].controller
let bottomController = detailControllers[detailControllers.count - 2].controller
if let topController = topController as? ViewController {
if !topController.attemptNavigation({ [weak self] in
let _ = self?.popViewController(animated: true)
}) {
return
}
}
if beginGesture {
let topController = self.viewControllers[self.viewControllers.count - 1] as UIViewController
let bottomController = self.viewControllers[self.viewControllers.count - 2] as UIViewController
topController.viewWillDisappear(true)
let topView = topController.view!
if let bottomController = bottomController as? ViewController {
let (_, controllerLayout) = self.layoutDataForConfiguration(self.layoutConfiguration(for: layout), layout: layout, index: 1)
if let topController = topController as? ViewController {
if !topController.attemptNavigation({ [weak self] in
let _ = self?.popViewController(animated: true)
}) {
return
}
}
topController.viewWillDisappear(true)
let topView = topController.view!
if let bottomController = bottomController as? ViewController {
let (_, controllerLayout) = self.layoutDataForConfiguration(self.layoutConfiguration(for: layout), layout: layout, index: self.viewControllers.count - 2)
let appliedLayout = controllerLayout.withUpdatedInputHeight(bottomController.hasActiveInput ? controllerLayout.inputHeight : nil)
bottomController.containerLayoutUpdated(appliedLayout, transition: .immediate)
}
bottomController.viewWillAppear(true)
let bottomView = bottomController.view!
let navigationTransitionCoordinator = NavigationTransitionCoordinator(transition: .Pop, container: self.controllerView.containerView, topView: topView, topNavigationBar: (topController as? ViewController)?.navigationBar, bottomView: bottomView, bottomNavigationBar: (bottomController as? ViewController)?.navigationBar, didUpdateProgress: { [weak self] progress, transition in
if let strongSelf = self {
for i in 0 ..< strongSelf._viewControllers.count {
if let controller = strongSelf._viewControllers[i].controller as? ViewController {
if i < strongSelf._viewControllers.count - 1 {
controller.updateNavigationCustomData((strongSelf.viewControllers[i + 1] as? ViewController)?.customData, progress: 1.0 - progress, transition: transition)
} else {
controller.updateNavigationCustomData(nil, progress: 1.0 - progress, transition: transition)
}
}
}
}
})
if let bottomController = bottomController as? ViewController {
bottomController.displayNode.recursivelyEnsureDisplaySynchronously(true)
}
self.navigationTransitionCoordinator = navigationTransitionCoordinator
let appliedLayout = controllerLayout.withUpdatedInputHeight(bottomController.hasActiveInput ? controllerLayout.inputHeight : nil)
bottomController.containerLayoutUpdated(appliedLayout, transition: .immediate)
}
case .changed:
if let navigationTransitionCoordinator = self.navigationTransitionCoordinator, !navigationTransitionCoordinator.animatingCompletion {
bottomController.viewWillAppear(true)
let bottomView = bottomController.view!
let navigationTransitionCoordinator = NavigationTransitionCoordinator(transition: .Pop, container: self.controllerView.containerView, topView: topView, topNavigationBar: (topController as? ViewController)?.navigationBar, bottomView: bottomView, bottomNavigationBar: (bottomController as? ViewController)?.navigationBar, didUpdateProgress: { [weak self, weak topController, weak bottomController] progress, transition in
if let strongSelf = self, let topController = topController, let bottomController = bottomController {
(bottomController as? ViewController)?.updateNavigationCustomData((topController as? ViewController)?.customData, progress: 1.0 - progress, transition: transition)
}
})
if let bottomController = bottomController as? ViewController {
bottomController.displayNode.recursivelyEnsureDisplaySynchronously(true)
}
self.navigationTransitionCoordinator = navigationTransitionCoordinator
}
case .changed:
if let layout = self.validLayout {
if let masterTransitionCoordinator = self.masterTransitionCoordinator, !masterTransitionCoordinator.animatingCompletion {
let (_, controllerLayout) = self.layoutDataForConfiguration(self.layoutConfiguration(for: layout), layout: layout, index: 0)
let translation = recognizer.translation(in: self.view).x
let progress = max(0.0, min(1.0, translation / self.view.frame.width))
let progress = max(0.0, min(1.0, translation / controllerLayout.size.width))
masterTransitionCoordinator.progress = progress
}
if let navigationTransitionCoordinator = self.navigationTransitionCoordinator, !navigationTransitionCoordinator.animatingCompletion {
let (_, controllerLayout) = self.layoutDataForConfiguration(self.layoutConfiguration(for: layout), layout: layout, index: 1)
let translation = recognizer.translation(in: self.view).x
let progress = max(0.0, min(1.0, translation / controllerLayout.size.width))
navigationTransitionCoordinator.progress = progress
}
case .ended:
if let navigationTransitionCoordinator = self.navigationTransitionCoordinator, !navigationTransitionCoordinator.animatingCompletion {
let velocity = recognizer.velocity(in: self.view).x
if velocity > 1000 || navigationTransitionCoordinator.progress > 0.2 {
(self.view as! NavigationControllerView).inTransition = true
navigationTransitionCoordinator.animateCompletion(velocity, completion: {
(self.view as! NavigationControllerView).inTransition = false
self.navigationTransitionCoordinator = nil
if self.viewControllers.count >= 2 && self.navigationTransitionCoordinator == nil {
let topController = self.viewControllers[self.viewControllers.count - 1] as UIViewController
let bottomController = self.viewControllers[self.viewControllers.count - 2] as UIViewController
topController.setIgnoreAppearanceMethodInvocations(true)
bottomController.setIgnoreAppearanceMethodInvocations(true)
let _ = self.popViewController(animated: false)
topController.setIgnoreAppearanceMethodInvocations(false)
bottomController.setIgnoreAppearanceMethodInvocations(false)
topController.viewDidDisappear(true)
bottomController.viewDidAppear(true)
}
case .ended:
if let masterTransitionCoordinator = self.masterTransitionCoordinator, !masterTransitionCoordinator.animatingCompletion {
let velocity = recognizer.velocity(in: self.view).x
let masterControllers = self._viewControllers.filter({ record in
if let controller = record.controller as? ViewController, case .master = controller.navigationPresentation {
return true
}
return false
})
if velocity > 1000 || masterTransitionCoordinator.progress > 0.2 {
(self.view as! NavigationControllerView).inTransition = true
masterTransitionCoordinator.animateCompletion(velocity, completion: {
(self.view as! NavigationControllerView).inTransition = false
self.masterTransitionCoordinator = nil
let masterControllers = self._viewControllers.filter({ record in
if let controller = record.controller as? ViewController, case .master = controller.navigationPresentation {
return true
}
return false
})
} else {
if self.viewControllers.count >= 2 && self.navigationTransitionCoordinator == nil {
let topController = self.viewControllers[self.viewControllers.count - 1] as UIViewController
let bottomController = self.viewControllers[self.viewControllers.count - 2] as UIViewController
if masterControllers.count >= 2 && self.masterTransitionCoordinator == nil {
let topController = masterControllers[masterControllers.count - 1].controller
let bottomController = masterControllers[masterControllers.count - 2].controller
topController.viewWillAppear(true)
bottomController.viewWillDisappear(true)
topController.setIgnoreAppearanceMethodInvocations(true)
bottomController.setIgnoreAppearanceMethodInvocations(true)
if let topController = topController as? ViewController {
self.filterController(topController, animated: false)
}
topController.setIgnoreAppearanceMethodInvocations(false)
bottomController.setIgnoreAppearanceMethodInvocations(false)
topController.viewDidDisappear(true)
bottomController.viewDidAppear(true)
}
})
} else {
if masterControllers.count >= 2 && masterControllers == nil {
let topController = masterControllers[masterControllers.count - 1].controller
let bottomController = masterControllers[masterControllers.count - 2].controller
topController.viewWillAppear(true)
bottomController.viewWillDisappear(true)
}
(self.view as! NavigationControllerView).inTransition = true
masterTransitionCoordinator.animateCancel({
(self.view as! NavigationControllerView).inTransition = false
self.masterTransitionCoordinator = nil
let masterControllers = self._viewControllers.filter({ record in
if let controller = record.controller as? ViewController, case .master = controller.navigationPresentation {
return true
}
return false
})
if masterControllers.count >= 2 && self.masterTransitionCoordinator == nil {
let topController = masterControllers[masterControllers.count - 1].controller
let bottomController = masterControllers[masterControllers.count - 2].controller
topController.viewDidAppear(true)
bottomController.viewDidDisappear(true)
}
})
}
}
if let layout = self.validLayout, let navigationTransitionCoordinator = self.navigationTransitionCoordinator, !navigationTransitionCoordinator.animatingCompletion {
let velocity = recognizer.velocity(in: self.view).x
let detailControllers: [ControllerRecord]
switch self.layoutConfiguration(for: layout) {
case .masterDetail:
detailControllers = self._viewControllers.filter({ record in
if let controller = record.controller as? ViewController {
if case .master = controller.navigationPresentation {
return false
} else {
return true
}
} else {
return true
}
})
case .single:
detailControllers = self._viewControllers
}
if velocity > 1000 || navigationTransitionCoordinator.progress > 0.2 {
(self.view as! NavigationControllerView).inTransition = true
navigationTransitionCoordinator.animateCompletion(velocity, completion: {
(self.view as! NavigationControllerView).inTransition = false
let detailControllers: [ControllerRecord]
switch self.layoutConfiguration(for: layout) {
case .masterDetail:
detailControllers = self._viewControllers.filter({ record in
if let controller = record.controller as? ViewController {
if case .master = controller.navigationPresentation {
return false
} else {
return true
}
} else {
return true
}
})
case .single:
detailControllers = self._viewControllers
}
(self.view as! NavigationControllerView).inTransition = true
navigationTransitionCoordinator.animateCancel({
(self.view as! NavigationControllerView).inTransition = false
self.navigationTransitionCoordinator = nil
self.navigationTransitionCoordinator = nil
if detailControllers.count >= 2 && self.navigationTransitionCoordinator == nil {
let topController = detailControllers[detailControllers.count - 1].controller
let bottomController = detailControllers[detailControllers.count - 2].controller
if self.viewControllers.count >= 2 && self.navigationTransitionCoordinator == nil {
let topController = self.viewControllers[self.viewControllers.count - 1] as UIViewController
let bottomController = self.viewControllers[self.viewControllers.count - 2] as UIViewController
topController.viewDidAppear(true)
bottomController.viewDidDisappear(true)
}
})
}
}
case .cancelled:
if let navigationTransitionCoordinator = self.navigationTransitionCoordinator, !navigationTransitionCoordinator.animatingCompletion {
if self.viewControllers.count >= 2 && self.navigationTransitionCoordinator == nil {
let topController = self.viewControllers[self.viewControllers.count - 1] as UIViewController
let bottomController = self.viewControllers[self.viewControllers.count - 2] as UIViewController
topController.setIgnoreAppearanceMethodInvocations(true)
bottomController.setIgnoreAppearanceMethodInvocations(true)
self.filterController(topController as! ViewController, animated: false)
topController.setIgnoreAppearanceMethodInvocations(false)
bottomController.setIgnoreAppearanceMethodInvocations(false)
topController.viewDidDisappear(true)
bottomController.viewDidAppear(true)
}
})
} else {
if detailControllers.count >= 2 && self.navigationTransitionCoordinator == nil {
let topController = detailControllers[detailControllers.count - 1].controller
let bottomController = detailControllers[detailControllers.count - 2].controller
topController.viewWillAppear(true)
bottomController.viewWillDisappear(true)
@ -930,17 +1175,133 @@ open class NavigationController: UINavigationController, ContainableController,
(self.view as! NavigationControllerView).inTransition = false
self.navigationTransitionCoordinator = nil
if self.viewControllers.count >= 2 && self.navigationTransitionCoordinator == nil {
let topController = self.viewControllers[self.viewControllers.count - 1] as UIViewController
let bottomController = self.viewControllers[self.viewControllers.count - 2] as UIViewController
let detailControllers: [ControllerRecord]
switch self.layoutConfiguration(for: layout) {
case .masterDetail:
detailControllers = self._viewControllers.filter({ record in
if let controller = record.controller as? ViewController {
if case .master = controller.navigationPresentation {
return false
} else {
return true
}
} else {
return true
}
})
case .single:
detailControllers = self._viewControllers
}
if detailControllers.count >= 2 && self.navigationTransitionCoordinator == nil {
let topController = detailControllers[detailControllers.count - 1].controller
let bottomController = detailControllers[detailControllers.count - 2].controller
topController.viewDidAppear(true)
bottomController.viewDidDisappear(true)
}
})
}
default:
break
}
case .cancelled:
if let masterTransitionCoordinator = self.masterTransitionCoordinator, !masterTransitionCoordinator.animatingCompletion {
let masterControllers = self._viewControllers.filter({ record in
if let controller = record.controller as? ViewController, case .master = controller.navigationPresentation {
return true
}
return false
})
if masterControllers.count >= 2 && self.masterTransitionCoordinator == nil {
let topController = masterControllers[masterControllers.count - 1].controller
let bottomController = masterControllers[masterControllers.count - 2].controller
topController.viewWillAppear(true)
bottomController.viewWillDisappear(true)
}
(self.view as! NavigationControllerView).inTransition = true
masterTransitionCoordinator.animateCancel({
(self.view as! NavigationControllerView).inTransition = false
self.masterTransitionCoordinator = nil
let masterControllers = self._viewControllers.filter({ record in
if let controller = record.controller as? ViewController, case .master = controller.navigationPresentation {
return true
}
return false
})
if masterControllers.count >= 2 && self.masterTransitionCoordinator == nil {
let topController = masterControllers[masterControllers.count - 1].controller
let bottomController = masterControllers[masterControllers.count - 2].controller
topController.viewDidAppear(true)
bottomController.viewDidDisappear(true)
}
})
}
if let layout = self.validLayout, let navigationTransitionCoordinator = self.navigationTransitionCoordinator, !navigationTransitionCoordinator.animatingCompletion {
let detailControllers: [ControllerRecord]
switch self.layoutConfiguration(for: layout) {
case .masterDetail:
detailControllers = self._viewControllers.filter({ record in
if let controller = record.controller as? ViewController {
if case .master = controller.navigationPresentation {
return false
} else {
return true
}
} else {
return true
}
})
case .single:
detailControllers = self._viewControllers
}
if detailControllers.count >= 2 && self.navigationTransitionCoordinator == nil {
let topController = detailControllers[detailControllers.count - 1].controller
let bottomController = detailControllers[detailControllers.count - 2].controller
topController.viewWillAppear(true)
bottomController.viewWillDisappear(true)
}
(self.view as! NavigationControllerView).inTransition = true
navigationTransitionCoordinator.animateCancel({
(self.view as! NavigationControllerView).inTransition = false
self.navigationTransitionCoordinator = nil
let detailControllers: [ControllerRecord]
switch self.layoutConfiguration(for: layout) {
case .masterDetail:
detailControllers = self._viewControllers.filter({ record in
if let controller = record.controller as? ViewController {
if case .master = controller.navigationPresentation {
return false
} else {
return true
}
} else {
return true
}
})
case .single:
detailControllers = self._viewControllers
}
if detailControllers.count >= 2 && self.navigationTransitionCoordinator == nil {
let topController = detailControllers[detailControllers.count - 1].controller
let bottomController = detailControllers[detailControllers.count - 2].controller
topController.viewDidAppear(true)
bottomController.viewDidDisappear(true)
}
})
}
default:
break
}
}
@ -1002,7 +1363,30 @@ open class NavigationController: UINavigationController, ContainableController,
self.currentPushDisposable.set(nil)
var controllers = self.viewControllers
controllers.append(viewController)
if let controller = viewController as? ViewController {
switch controller.navigationPresentation {
case .default:
controllers.append(viewController)
case .master:
var i = 0
loop: while i < controllers.count {
if let currentController = controllers[i] as? ViewController {
switch currentController.navigationPresentation {
case .master:
break
case .default:
break loop
}
} else {
break loop
}
i += 1
}
controllers.insert(viewController, at: i)
}
} else {
controllers.append(viewController)
}
self.setViewControllers(controllers, animated: animated)
}

View File

@ -55,6 +55,11 @@ open class ViewControllerPresentationArguments {
}
}
public enum ViewControllerNavigationPresentation {
case `default`
case master
}
@objc open class ViewController: UIViewController, ContainableController {
private var validLayout: ContainerViewLayout?
public var currentlyAppliedLayout: ContainerViewLayout? {
@ -117,6 +122,8 @@ open class ViewControllerPresentationArguments {
return self.preferNavigationUIHidden
}
open var navigationPresentation: ViewControllerNavigationPresentation = .default
public var presentationArguments: Any?
public var tabBarItemDebugTapAction: (() -> Void)?
@ -236,6 +243,10 @@ open class ViewControllerPresentationArguments {
return true
}
open func preferredContentSizeForLayout(_ layout: ContainerViewLayout) -> CGSize? {
return nil
}
private func updateScrollToTopView() {
if self.scrollToTop != nil {
if let displayNode = self._displayNode , self.scrollToTopView == nil {
@ -267,9 +278,20 @@ open class ViewControllerPresentationArguments {
self.navigationBar?.backPressed = { [weak self] in
if let strongSelf = self, strongSelf.attemptNavigation({
self?.navigationController?.popViewController(animated: true)
guard let strongSelf = self else {
return
}
if let navigationController = strongSelf.navigationController as? NavigationController {
navigationController.filterController(strongSelf, animated: true)
} else {
strongSelf.navigationController?.popViewController(animated: true)
}
}) {
strongSelf.navigationController?.popViewController(animated: true)
if let navigationController = strongSelf.navigationController as? NavigationController {
navigationController.filterController(strongSelf, animated: true)
} else {
strongSelf.navigationController?.popViewController(animated: true)
}
}
}
self.navigationBar?.requestContainerLayout = { [weak self] transition in

View File

@ -945,6 +945,14 @@ public class GalleryController: ViewController {
self.accountInUseDisposable.set(nil)
}
override public func preferredContentSizeForLayout(_ layout: ContainerViewLayout) -> CGSize? {
if let centralItemNode = self.galleryNode.pager.centralItemNode(), let itemSize = centralItemNode.contentSize() {
return itemSize.aspectFitted(layout.size)
} else {
return nil
}
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition)
@ -955,7 +963,7 @@ public class GalleryController: ViewController {
self.adjustedForInitialPreviewingLayout = true
self.galleryNode.setControlsHidden(true, animated: false)
if let centralItemNode = self.galleryNode.pager.centralItemNode(), let itemSize = centralItemNode.contentSize() {
self.preferredContentSize = itemSize.aspectFitted(self.view.bounds.size)
self.preferredContentSize = itemSize.aspectFitted(layout.size)
self.containerLayoutUpdated(ContainerViewLayout(size: self.preferredContentSize, metrics: LayoutMetrics(), deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(), safeInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: false), transition: .immediate)
}
}

View File

@ -63,8 +63,6 @@ public final class TelegramRootController: NavigationController {
if previousTheme !== presentationData.theme {
strongSelf.rootTabController?.updateTheme(navigationBarPresentationData: NavigationBarPresentationData(presentationData: presentationData), theme: TabBarControllerTheme(rootControllerTheme: presentationData.theme))
strongSelf.rootTabController?.statusBar.statusBarStyle = presentationData.theme.rootController.statusBarStyle.style
}
}
})
@ -81,6 +79,7 @@ public final class TelegramRootController: NavigationController {
public func addRootControllers(showCallsTab: Bool) {
let tabBarController = TabBarController(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData), theme: TabBarControllerTheme(rootControllerTheme: self.presentationData.theme))
tabBarController.navigationPresentation = .master
let chatListController = self.context.sharedContext.makeChatListController(context: self.context, groupId: .root, controlsHistoryPreload: true, hideNetworkActivityStatus: false, enableDebugActions: !GlobalExperimentalSettings.isAppStoreBuild)
if let sharedContext = self.context.sharedContext as? SharedAccountContextImpl {
chatListController.tabBarItem.badgeValue = sharedContext.switchingData.chatListBadge