2022-07-02 00:04:43 +02:00

260 lines
8.8 KiB
Swift

import Foundation
import UIKit
import Display
import AsyncDisplayKit
import PagerComponent
private func traceScrollView(view: UIView, point: CGPoint) -> (UIScrollView?, Bool) {
for subview in view.subviews.reversed() {
let subviewPoint = view.convert(point, to: subview)
if subview.frame.contains(point) {
let (result, shouldContinue) = traceScrollView(view: subview, point: subviewPoint)
if let result = result {
return (result, false)
} else if subview.backgroundColor != nil {
return (nil, false)
} else if !shouldContinue{
return (nil, false)
}
}
}
if let scrollView = view as? UIScrollView {
if scrollView is ListViewScroller || scrollView is GridNodeScrollerView {
return (nil, false)
}
return (scrollView, false)
}
return (nil, true)
}
private final class ExpansionPanRecognizer: UIGestureRecognizer, UIGestureRecognizerDelegate {
enum LockDirection {
case up
case down
}
var requiredLockDirection: LockDirection = .up
private var beginPosition = CGPoint()
private var currentTranslation = CGPoint()
override public init(target: Any?, action: Selector?) {
super.init(target: target, action: action)
self.delegate = self
}
override public func reset() {
super.reset()
self.state = .possible
self.currentTranslation = CGPoint()
}
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if let _ = otherGestureRecognizer.view as? PagerExpandableScrollView {
return true
}
if let _ = gestureRecognizer as? PagerPanGestureRecognizer {
return true
}
return true
}
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if let _ = otherGestureRecognizer.view as? PagerExpandableScrollView {
return true
}
if otherGestureRecognizer is UIPanGestureRecognizer {
return true
}
return false
}
override public func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesBegan(touches, with: event)
guard let touch = touches.first, let view = self.view else {
self.state = .failed
return
}
var found = false
let point = touch.location(in: self.view)
if let _ = view.hitTest(point, with: event) as? UIButton {
} else if let scrollView = traceScrollView(view: view, point: point).0 {
let contentOffset = scrollView.contentOffset
let contentInset = scrollView.contentInset
if contentOffset.y.isLessThanOrEqualTo(contentInset.top) {
found = true
}
}
if found {
self.beginPosition = point
} else {
self.state = .failed
}
}
override public func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesMoved(touches, with: event)
guard let touch = touches.first, let view = self.view else {
self.state = .failed
return
}
let point = touch.location(in: self.view)
let translation = CGPoint(x: point.x - self.beginPosition.x, y: point.y - self.beginPosition.y)
self.currentTranslation = translation
if self.state == .possible {
if abs(translation.x) > 8.0 {
self.state = .failed
return
}
var lockDirection: LockDirection?
let point = touch.location(in: self.view)
if let scrollView = traceScrollView(view: view, point: point).0 {
let contentOffset = scrollView.contentOffset
let contentInset = scrollView.contentInset
if contentOffset.y <= contentInset.top {
lockDirection = self.requiredLockDirection
}
}
if let lockDirection = lockDirection {
if abs(translation.y) > 2.0 {
switch lockDirection {
case .up:
if translation.y < 0.0 {
self.state = .began
} else {
self.state = .failed
}
case .down:
if translation.y > 0.0 {
self.state = .began
} else {
self.state = .failed
}
}
}
} else {
self.state = .failed
}
} else {
self.state = .changed
}
}
override public func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesEnded(touches, with: event)
self.state = .ended
}
override public func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesCancelled(touches, with: event)
self.state = .cancelled
}
func translation() -> CGPoint {
return self.currentTranslation
}
func velocity() -> CGPoint {
return CGPoint()
}
}
public final class ChatInputPanelContainer: SparseNode, UIScrollViewDelegate {
public var expansionUpdated: ((ContainedViewLayoutTransition) -> Void)?
private var expansionRecognizer: ExpansionPanRecognizer?
private var scrollableDistance: CGFloat?
public private(set) var initialExpansionFraction: CGFloat = 0.0
public private(set) var expansionFraction: CGFloat = 0.0
override public init() {
super.init()
let expansionRecognizer = ExpansionPanRecognizer(target: self, action: #selector(self.panGesture(_:)))
self.expansionRecognizer = expansionRecognizer
self.view.addGestureRecognizer(expansionRecognizer)
expansionRecognizer.isEnabled = false
}
@objc private func panGesture(_ recognizer: ExpansionPanRecognizer) {
switch recognizer.state {
case .began:
guard let _ = self.scrollableDistance else {
return
}
self.initialExpansionFraction = self.expansionFraction
case .changed:
guard let scrollableDistance = self.scrollableDistance else {
return
}
let delta = -recognizer.translation().y / scrollableDistance
self.expansionFraction = max(0.0, min(1.0, self.initialExpansionFraction + delta))
self.expansionUpdated?(.immediate)
case .ended, .cancelled:
guard let _ = self.scrollableDistance else {
return
}
let velocity = recognizer.velocity()
if abs(self.initialExpansionFraction - self.expansionFraction) > 0.25 {
if self.initialExpansionFraction < 0.5 {
self.expansionFraction = 1.0
} else {
self.expansionFraction = 0.0
}
} else if abs(velocity.y) > 100.0 {
if velocity.y < 0.0 {
self.expansionFraction = 1.0
} else {
self.expansionFraction = 0.0
}
} else {
if self.initialExpansionFraction < 0.5 {
self.expansionFraction = 0.0
} else {
self.expansionFraction = 1.0
}
}
if let expansionRecognizer = self.expansionRecognizer {
expansionRecognizer.requiredLockDirection = self.expansionFraction == 0.0 ? .up : .down
}
self.expansionUpdated?(.animated(duration: 0.4, curve: .spring))
default:
break
}
}
public func update(size: CGSize, scrollableDistance: CGFloat, isExpansionEnabled: Bool, transition: ContainedViewLayoutTransition) {
self.expansionRecognizer?.isEnabled = isExpansionEnabled
self.scrollableDistance = scrollableDistance
}
public func expand() {
self.expansionFraction = 1.0
self.expansionRecognizer?.requiredLockDirection = self.expansionFraction == 0.0 ? .up : .down
}
public func collapse() {
self.expansionFraction = 0.0
self.expansionRecognizer?.requiredLockDirection = self.expansionFraction == 0.0 ? .up : .down
}
}