Swiftgram/TelegramUI/SearchDisplayController.swift
Ilya Laktyushin 10d5bf3d10 UI fixes
2019-01-15 00:55:31 +04:00

166 lines
8.3 KiB
Swift

import Foundation
import AsyncDisplayKit
import SwiftSignalKit
import Display
enum SearchDisplayControllerMode {
case list
case navigation
}
final class SearchDisplayController {
private let searchBar: SearchBarNode
private let mode: SearchDisplayControllerMode
let contentNode: SearchDisplayControllerContentNode
private var containerLayout: (ContainerViewLayout, CGFloat)?
var isDeactivating = false
private var isSearchingDisposable: Disposable?
init(presentationData: PresentationData, mode: SearchDisplayControllerMode = .navigation, contentNode: SearchDisplayControllerContentNode, cancel: @escaping () -> Void) {
self.searchBar = SearchBarNode(theme: SearchBarNodeTheme(theme: presentationData.theme, hasSeparator: false), strings: presentationData.strings, fieldStyle: .modern)
self.mode = mode
self.contentNode = contentNode
self.searchBar.textUpdated = { [weak contentNode] text in
contentNode?.searchTextUpdated(text: text)
}
self.searchBar.cancel = { [weak self] in
self?.isDeactivating = true
cancel()
}
self.searchBar.clearPrefix = {
}
self.contentNode.cancel = { [weak self] in
self?.isDeactivating = true
cancel()
}
self.contentNode.dismissInput = { [weak self] in
self?.searchBar.deactivate(clear: false)
}
self.contentNode.setQuery = { [weak self] query in
self?.searchBar.text = query
}
self.isSearchingDisposable = (contentNode.isSearching
|> deliverOnMainQueue).start(next: { [weak self] value in
self?.searchBar.activity = value
})
}
func updatePresentationData(_ presentationData: PresentationData) {
self.searchBar.updateThemeAndStrings(theme: SearchBarNodeTheme(theme: presentationData.theme, hasSeparator: false), strings: presentationData.strings)
self.contentNode.updatePresentationData(presentationData)
}
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
let statusBarHeight: CGFloat = layout.statusBarHeight ?? 0.0
let searchBarHeight: CGFloat = max(20.0, statusBarHeight) + 44.0
let navigationBarOffset: CGFloat
if statusBarHeight.isZero {
navigationBarOffset = -20.0
} else {
navigationBarOffset = 0.0
}
var navigationBarFrame = CGRect(origin: CGPoint(x: 0.0, y: navigationBarOffset), size: CGSize(width: layout.size.width, height: searchBarHeight))
if layout.statusBarHeight == nil {
navigationBarFrame.size.height = 64.0
}
navigationBarFrame.size.height += 10.0
let searchBarFrame: CGRect
if case .navigation = self.mode {
searchBarFrame = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: 54.0)
} else {
searchBarFrame = navigationBarFrame //CGRect(x: 0.0, y: navigationBarFrame.height - 54.0, width: layout.size.width, height: 54.0)
}
transition.updateFrame(node: self.searchBar, frame: searchBarFrame)
self.searchBar.updateLayout(boundingSize: searchBarFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: transition)
self.containerLayout = (layout, navigationBarFrame.maxY)
transition.updateFrame(node: self.contentNode, frame: CGRect(origin: CGPoint(), size: layout.size))
self.contentNode.containerLayoutUpdated(ContainerViewLayout(size: layout.size, metrics: LayoutMetrics(), intrinsicInsets: layout.intrinsicInsets, safeInsets: layout.safeInsets, statusBarHeight: nil, inputHeight: layout.inputHeight, standardInputHeight: layout.standardInputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging), navigationBarHeight: navigationBarFrame.maxY, transition: transition)
}
func activate(insertSubnode: (ASDisplayNode, Bool) -> Void, placeholder: SearchBarPlaceholderNode) {
guard let (layout, navigationBarHeight) = self.containerLayout else {
return
}
insertSubnode(self.contentNode, false)
self.contentNode.frame = CGRect(origin: CGPoint(), size: layout.size)
self.contentNode.containerLayoutUpdated(ContainerViewLayout(size: layout.size, metrics: LayoutMetrics(), intrinsicInsets: UIEdgeInsets(), safeInsets: layout.safeInsets, statusBarHeight: nil, inputHeight: nil, standardInputHeight: layout.standardInputHeight, inputHeightIsInteractivellyChanging: false), navigationBarHeight: navigationBarHeight, transition: .immediate)
let initialTextBackgroundFrame = placeholder.convert(placeholder.backgroundNode.frame, to: nil)//self.contentNode.supernode)
let contentNodePosition = self.contentNode.layer.position
self.contentNode.layer.animatePosition(from: CGPoint(x: contentNodePosition.x, y: contentNodePosition.y + (initialTextBackgroundFrame.maxY + 8.0 - navigationBarHeight)), to: contentNodePosition, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
self.contentNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionEaseOut)
self.searchBar.placeholderString = placeholder.placeholderString
let navigationBarFrame: CGRect
switch self.mode {
case .list:
let statusBarHeight: CGFloat = layout.statusBarHeight ?? 0.0
let searchBarHeight: CGFloat = max(20.0, statusBarHeight) + 44.0
let navigationBarOffset: CGFloat
if statusBarHeight.isZero {
navigationBarOffset = -20.0
} else {
navigationBarOffset = 0.0
}
var frame = CGRect(origin: CGPoint(x: 0.0, y: navigationBarOffset), size: CGSize(width: layout.size.width, height: searchBarHeight))
if layout.statusBarHeight == nil {
frame.size.height = 64.0
}
navigationBarFrame = frame
case .navigation:
navigationBarFrame = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: 54.0)
}
self.searchBar.frame = navigationBarFrame
insertSubnode(self.searchBar, true)
self.searchBar.layout()
self.searchBar.activate()
self.searchBar.animateIn(from: placeholder, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
}
func deactivate(placeholder: SearchBarPlaceholderNode?, animated: Bool = true) {
self.searchBar.deactivate()
if let placeholder = placeholder {
let searchBar = self.searchBar
searchBar.transitionOut(to: placeholder, transition: animated ? ContainedViewLayoutTransition.animated(duration: 0.5, curve: .spring) : ContainedViewLayoutTransition.immediate, completion: {
[weak searchBar] in
searchBar?.removeFromSupernode()
})
}
let contentNode = self.contentNode
if animated {
if let placeholder = placeholder, let (_, navigationBarHeight) = self.containerLayout {
let contentNodePosition = self.contentNode.layer.position
let targetTextBackgroundFrame = placeholder.convert(placeholder.backgroundNode.frame, to: nil) //self.contentNode.supernode)
self.contentNode.layer.animatePosition(from: contentNodePosition, to: CGPoint(x: contentNodePosition.x, y: contentNodePosition.y + (targetTextBackgroundFrame.maxY + 8.0 - navigationBarHeight)), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
}
contentNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak contentNode] _ in
contentNode?.removeFromSupernode()
})
} else {
contentNode.removeFromSupernode()
}
}
func previewViewAndActionAtLocation(_ location: CGPoint) -> (UIView, CGRect, Any)? {
return self.contentNode.previewViewAndActionAtLocation(location)
}
}