diff --git a/Display/GenerateImage.swift b/Display/GenerateImage.swift index 23e9fd43fc..f3a4bf6414 100644 --- a/Display/GenerateImage.swift +++ b/Display/GenerateImage.swift @@ -217,6 +217,16 @@ public func generateTintedImage(image: UIImage?, color: UIColor, backgroundColor return tintedImage } +public func generateScaledImage(image: UIImage?, size: CGSize, scale: CGFloat? = nil) -> UIImage? { + guard let image = image else { + return nil + } + + return generateImage(size, opaque: true, scale: scale, rotatedContext: { size, context in + context.draw(image.cgImage!, in: CGRect(origin: CGPoint(), size: size)) + }) +} + private func generateSingleColorImage(size: CGSize, color: UIColor) -> UIImage? { return generateImage(size, contextGenerator: { size, context in context.setFillColor(color.cgColor) diff --git a/Display/ListView.swift b/Display/ListView.swift index bd5063b751..f7f23eb69a 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -194,6 +194,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel public final var visibleContentOffsetChanged: (ListViewVisibleContentOffset) -> Void = { _ in } public final var visibleBottomContentOffsetChanged: (ListViewVisibleContentOffset) -> Void = { _ in } public final var beganInteractiveDragging: () -> Void = { } + public final var didEndScrolling: (() -> Void)? public final var reorderItem: (Int, Int, Any?) -> Void = { _, _, _ in } @@ -497,6 +498,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.updateHeaderItemsFlashing(animated: true) self.lastContentOffsetTimestamp = 0.0 + self.didEndScrolling?() } } @@ -505,6 +507,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.isDeceleratingAfterTracking = false self.resetHeaderItemsFlashTimer(start: true) self.updateHeaderItemsFlashing(animated: true) + self.didEndScrolling?() } public func scrollViewDidScroll(_ scrollView: UIScrollView) { @@ -1413,6 +1416,16 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } + /*if !insertedIndexSet.intersection(updateIndices).isEmpty { + print("int") + }*/ + let explicitelyUpdateIndices = Set(updateIndicesAndItems.map({$0.index})) + /*if !explicitelyUpdateIndices.intersection(updateIndices).isEmpty { + print("int") + }*/ + + updateIndices.subtract(explicitelyUpdateIndices) + self.updateNodes(synchronous: options.contains(.Synchronous), animated: animated, updateIndicesAndItems: updateIndicesAndItems, inputState: updatedState, previousNodes: previousNodes, inputOperations: operations, completion: { updatedState, operations in self.updateAdjacent(synchronous: options.contains(.Synchronous), animated: animated, state: updatedState, updateAdjacentItemsIndices: updateIndices, operations: operations, completion: { state, operations in var updatedState = state @@ -3235,6 +3248,14 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } + public func ensureItemNodeVisibleAtTopInset(_ node: ListViewItemNode) { + if let index = node.index { + if node.frame.minY != self.insets.top { + self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: ListViewScrollToItem(index: index, position: ListViewScrollPosition.top(0.0), animated: true, curve: ListViewAnimationCurve.Default, directionHint: ListViewScrollToItemDirectionHint.Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + } + } + } + public func itemNodeRelativeOffset(_ node: ListViewItemNode) -> CGFloat? { if let _ = node.index { return node.frame.minY - self.insets.top diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index b376d42815..7f69e7deda 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -21,7 +21,7 @@ private final class NavigationControllerContainerView: UIView { } } -private final class NavigationControllerView: UIView { +private final class NavigationControllerView: UITracingLayerView { var inTransition = false let sharedStatusBar: StatusBar @@ -93,6 +93,9 @@ open class NavigationController: UINavigationController, ContainableController, private var validLayout: ContainerViewLayout? + private var scheduledLayoutTransitionRequestId: Int = 0 + private var scheduledLayoutTransitionRequest: (Int, ContainedViewLayoutTransition)? + private var navigationTransitionCoordinator: NavigationTransitionCoordinator? private var currentPushDisposable = MetaDisposable() @@ -283,7 +286,7 @@ open class NavigationController: UINavigationController, ContainableController, } if let _ = layout.statusBarHeight { - self.controllerView.sharedStatusBar.frame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: 40.0)) + self.controllerView.sharedStatusBar.frame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: 60.0)) } var controllersAndFrames: [(Bool, ControllerRecord, ContainerViewLayout)] = [] @@ -360,6 +363,13 @@ open class NavigationController: UINavigationController, ContainableController, 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: 1) + + let appliedLayout = controllerLayout.withUpdatedInputHeight(controller.hasActiveInput ? controllerLayout.inputHeight : nil) + controller.containerLayoutUpdated(appliedLayout, transition: .immediate) + } self.controllerView.containerView.addSubview(record.controller.view) record.controller.setIgnoreAppearanceMethodInvocations(false) @@ -602,6 +612,12 @@ open class NavigationController: UINavigationController, ContainableController, 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! @@ -696,30 +712,35 @@ open class NavigationController: UINavigationController, ContainableController, guard let strongSelf = self else { return } - - if let validLayout = strongSelf.validLayout { - let (_, controllerLayout) = strongSelf.layoutDataForConfiguration(strongSelf.layoutConfiguration(for: validLayout), layout: validLayout, index: strongSelf.viewControllers.count) - - let appliedLayout = controllerLayout.withUpdatedInputHeight(controller.hasActiveInput ? controllerLayout.inputHeight : nil) - controller.containerLayoutUpdated(appliedLayout, transition: .immediate) - strongSelf.currentPushDisposable.set((controller.ready.get() |> take(1)).start(next: { _ in - if let strongSelf = self, let validLayout = strongSelf.validLayout { - let (_, controllerLayout) = strongSelf.layoutDataForConfiguration(strongSelf.layoutConfiguration(for: validLayout), layout: validLayout, index: strongSelf.viewControllers.count) - - let containerLayout = controllerLayout.withUpdatedInputHeight(controller.hasActiveInput ? controllerLayout.inputHeight : nil) - if containerLayout != appliedLayout { - controller.containerLayoutUpdated(containerLayout, transition: .immediate) - } - strongSelf.pushViewController(controller, animated: true) - } - })) - } else { - strongSelf.pushViewController(controller, animated: false) - } - + if !controller.hasActiveInput { strongSelf.view.endEditing(true) } + strongSelf.scheduleAfterLayout({ + guard let strongSelf = self else { + return + } + + if let validLayout = strongSelf.validLayout { + let (_, controllerLayout) = strongSelf.layoutDataForConfiguration(strongSelf.layoutConfiguration(for: validLayout), layout: validLayout, index: strongSelf.viewControllers.count) + + let appliedLayout = controllerLayout.withUpdatedInputHeight(controller.hasActiveInput ? controllerLayout.inputHeight : nil) + controller.containerLayoutUpdated(appliedLayout, transition: .immediate) + strongSelf.currentPushDisposable.set((controller.ready.get() |> take(1)).start(next: { _ in + if let strongSelf = self, let validLayout = strongSelf.validLayout { + let (_, controllerLayout) = strongSelf.layoutDataForConfiguration(strongSelf.layoutConfiguration(for: validLayout), layout: validLayout, index: strongSelf.viewControllers.count) + + let containerLayout = controllerLayout.withUpdatedInputHeight(controller.hasActiveInput ? controllerLayout.inputHeight : nil) + if containerLayout != appliedLayout { + controller.containerLayoutUpdated(containerLayout, transition: .immediate) + } + strongSelf.pushViewController(controller, animated: true) + } + })) + } else { + strongSelf.pushViewController(controller, animated: false) + } + }) } if let lastController = self.viewControllers.last as? ViewController, !lastController.attemptNavigation(navigateAction) { @@ -926,4 +947,32 @@ open class NavigationController: UINavigationController, ContainableController, } return nil } + + private func scheduleAfterLayout(_ f: @escaping () -> Void) { + (self.view as? UITracingLayerView)?.schedule(layout: { [weak self] in + f() + }) + self.view.setNeedsLayout() + } + + private func scheduleLayoutTransitionRequest(_ transition: ContainedViewLayoutTransition) { + let requestId = self.scheduledLayoutTransitionRequestId + self.scheduledLayoutTransitionRequestId += 1 + self.scheduledLayoutTransitionRequest = (requestId, transition) + (self.view as? UITracingLayerView)?.schedule(layout: { [weak self] in + if let strongSelf = self { + if let (currentRequestId, currentRequestTransition) = strongSelf.scheduledLayoutTransitionRequest, currentRequestId == requestId { + strongSelf.scheduledLayoutTransitionRequest = nil + strongSelf.requestLayout(transition: currentRequestTransition) + } + } + }) + self.view.setNeedsLayout() + } + + private func requestLayout(transition: ContainedViewLayoutTransition) { + if self.isViewLoaded, let validLayout = self.validLayout { + self.containerLayoutUpdated(validLayout, transition: transition) + } + } } diff --git a/Display/StatusBarProxyNode.swift b/Display/StatusBarProxyNode.swift index 791332e55e..95df717884 100644 --- a/Display/StatusBarProxyNode.swift +++ b/Display/StatusBarProxyNode.swift @@ -41,19 +41,37 @@ func makeStatusBarProxy(_ statusBarStyle: StatusBarStyle, statusBar: UIView) -> return StatusBarProxyNode(statusBarStyle: statusBarStyle, statusBar: statusBar) } +private func maxSubviewBounds(_ view: UIView) -> CGRect { + var bounds = view.bounds + for subview in view.subviews { + let subviewFrame = subview.frame + let subviewBounds = maxSubviewBounds(subview).offsetBy(dx: subviewFrame.minX, dy: subviewFrame.minY) + bounds = bounds.union(subviewBounds) + } + return bounds +} + private class StatusBarItemNode: ASDisplayNode { var statusBarStyle: StatusBarStyle var targetView: UIView + var rootView: UIView + private let contentNode: ASDisplayNode - init(statusBarStyle: StatusBarStyle, targetView: UIView) { + init(statusBarStyle: StatusBarStyle, targetView: UIView, rootView: UIView) { self.statusBarStyle = statusBarStyle self.targetView = targetView + self.rootView = rootView + self.contentNode = ASDisplayNode() + self.contentNode.isLayerBacked = true super.init() + + self.addSubnode(self.contentNode) } func update() { - let context = DrawingContext(size: self.targetView.frame.size, clear: true) + let containingBounds = maxSubviewBounds(self.targetView) + let context = DrawingContext(size: containingBounds.size, clear: true) if let contents = self.targetView.layer.contents, (self.targetView.layer.sublayers?.count ?? 0) == 0 && CFGetTypeID(contents as CFTypeRef) == CGImage.typeID && false { let image = contents as! CGImage @@ -86,13 +104,24 @@ private class StatusBarItemNode: ASDisplayNode { } } else { context.withContext { c in + c.translateBy(x: containingBounds.minX, y: -containingBounds.minY) UIGraphicsPushContext(c) self.targetView.layer.render(in: c) UIGraphicsPopContext() } } //dumpViews(self.targetView) - var type: StatusBarItemType = self.targetView.checkIsKind(of: batteryItemClass!) ? .Battery : .Generic + var type: StatusBarItemType = .Generic + if let batteryItemClass = batteryItemClass { + if self.targetView.checkIsKind(of: batteryItemClass) { + type = .Battery + } + } + if let batteryViewClass = batteryViewClass { + if self.targetView.checkIsKind(of: batteryViewClass) { + type = .Battery + } + } if case .Generic = type { var hasActivityBackground = false var hasText = false @@ -108,9 +137,11 @@ private class StatusBarItemNode: ASDisplayNode { } } tintStatusBarItem(context, type: type, style: statusBarStyle) - self.contents = context.generateImage()?.cgImage + self.contentNode.contents = context.generateImage()?.cgImage - self.frame = self.targetView.frame + let mappedFrame = self.targetView.convert(self.targetView.bounds, to: self.rootView) + self.frame = mappedFrame + self.contentNode.frame = containingBounds } } @@ -253,10 +284,10 @@ private func tintStatusBarItem(_ context: DrawingContext, type: StatusBarItemTyp let baseColor: UInt32 switch style { - case .Black, .Ignore, .Hide: - baseColor = 0x000000 - case .White: - baseColor = 0xffffff + case .Black, .Ignore, .Hide: + baseColor = 0x000000 + case .White: + baseColor = 0xffffff } let baseR = (baseColor >> 16) & 0xff @@ -285,6 +316,14 @@ private let foregroundClass: AnyClass? = { return NSClassFromString("_UI" + nameString) }() +private let foregroundClass2: AnyClass? = { + var nameString = "StatusBar" + if CFAbsoluteTimeGetCurrent() > 0 { + nameString += "ForegroundView" + } + return NSClassFromString("UI" + nameString) +}() + private let batteryItemClass: AnyClass? = { var nameString = "StatusBarBattery" if CFAbsoluteTimeGetCurrent() > 0 { @@ -293,6 +332,14 @@ private let batteryItemClass: AnyClass? = { return NSClassFromString("UI" + nameString) }() +private let batteryViewClass: AnyClass? = { + var nameString = "Battery" + if CFAbsoluteTimeGetCurrent() > 0 { + nameString += "View" + } + return NSClassFromString("_UI" + nameString) +}() + private let activityClass: AnyClass? = { var nameString = "StatusBarBackground" if CFAbsoluteTimeGetCurrent() > 0 { @@ -309,6 +356,18 @@ private let stringClass: AnyClass? = { return NSClassFromString("_UI" + nameString) }() +private func containsSubviewOfClass(view: UIView, of subviewClass: AnyClass?) -> Bool { + guard let subviewClass = subviewClass else { + return false + } + for subview in view.subviews { + if subview.checkIsKind(of: subviewClass) { + return true + } + } + return false +} + private class StatusBarProxyNodeTimerTarget: NSObject { let action: () -> Void @@ -321,6 +380,32 @@ private class StatusBarProxyNodeTimerTarget: NSObject { } } +private func forEachSubview(statusBar: UIView, _ f: (UIView, UIView) -> Bool) { + var rootView: UIView = statusBar + for subview in statusBar.subviews { + if let foregroundClass = foregroundClass, subview.checkIsKind(of: foregroundClass) { + rootView = subview + break + } else if let foregroundClass2 = foregroundClass2, subview.checkIsKind(of: foregroundClass2) { + rootView = subview + break + } + } + for subview in rootView.subviews { + if true || subview.subviews.isEmpty { + if !f(rootView, subview) { + break + } + } else { + for subSubview in subview.subviews { + if !f(rootView, subSubview) { + break + } + } + } + } +} + class StatusBarProxyNode: ASDisplayNode { private let statusBar: UIView @@ -369,19 +454,13 @@ class StatusBarProxyNode: ASDisplayNode { self.clipsToBounds = true //self.backgroundColor = UIColor.blueColor().colorWithAlphaComponent(0.2) - var rootView: UIView = statusBar - for subview in statusBar.subviews { - if let foregroundClass = foregroundClass, subview.checkIsKind(of: foregroundClass) { - rootView = subview - break - } - } - - for subview in rootView.subviews { - let itemNode = StatusBarItemNode(statusBarStyle: statusBarStyle, targetView: subview) + //dumpViews(statusBar) + forEachSubview(statusBar: statusBar, { rootView, subview in + let itemNode = StatusBarItemNode(statusBarStyle: statusBarStyle, targetView: subview, rootView: rootView) self.itemNodes.append(itemNode) self.addSubnode(itemNode) - } + return true + }) self.frame = statusBar.bounds } @@ -393,25 +472,17 @@ class StatusBarProxyNode: ASDisplayNode { private func updateItems() { let statusBar = self.statusBar - var rootView: UIView = statusBar - for subview in statusBar.subviews { - if let foregroundClass = foregroundClass, subview.checkIsKind(of: foregroundClass) { - rootView = subview - break - } - } - - //dumpViews(self.statusBar) - var i = 0 while i < self.itemNodes.count { var found = false - for subview in rootView.subviews { - if self.itemNodes[i].targetView == subview { + forEachSubview(statusBar: statusBar, { rootView, subview in + if self.itemNodes[i].rootView === rootView && self.itemNodes[i].targetView === subview { found = true - break + return false + } else { + return true } - } + }) if !found { self.itemNodes[i].removeFromSupernode() self.itemNodes.remove(at: i) @@ -422,7 +493,7 @@ class StatusBarProxyNode: ASDisplayNode { } } - for subview in rootView.subviews { + forEachSubview(statusBar: statusBar, { rootView, subview in var found = false for itemNode in self.itemNodes { if itemNode.targetView == subview { @@ -430,13 +501,13 @@ class StatusBarProxyNode: ASDisplayNode { break } } - if !found { - let itemNode = StatusBarItemNode(statusBarStyle: self.statusBarStyle, targetView: subview) + let itemNode = StatusBarItemNode(statusBarStyle: self.statusBarStyle, targetView: subview, rootView: rootView) itemNode.update() self.itemNodes.append(itemNode) self.addSubnode(itemNode) } - } + return true + }) } } diff --git a/Display/ViewController.swift b/Display/ViewController.swift index 3d7d13900a..d065c6564f 100644 --- a/Display/ViewController.swift +++ b/Display/ViewController.swift @@ -196,7 +196,7 @@ open class ViewControllerPresentationArguments { } transition.updateFrame(node: self.displayNode, frame: CGRect(origin: self.view.frame.origin, size: layout.size)) if let _ = layout.statusBarHeight { - self.statusBar.frame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: 40.0)) + self.statusBar.frame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: 60.0)) } let statusBarHeight: CGFloat = layout.statusBarHeight ?? 0.0 diff --git a/Display/WindowContent.swift b/Display/WindowContent.swift index d22af96f4f..866e63e072 100644 --- a/Display/WindowContent.swift +++ b/Display/WindowContent.swift @@ -653,7 +653,11 @@ public class Window1 { public var coveringView: WindowCoveringView? { didSet { if self.coveringView !== oldValue { - oldValue?.removeFromSuperview() + if let oldValue = oldValue { + oldValue.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak oldValue] _ in + oldValue?.removeFromSuperview() + }) + } if let coveringView = self.coveringView { self.hostView.view.insertSubview(coveringView, belowSubview: self.volumeControlStatusBarNode.view) if !self.windowLayout.size.width.isZero {