mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-15 21:45:19 +00:00
244 lines
8.4 KiB
Swift
244 lines
8.4 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import AsyncDisplayKit
|
|
|
|
public enum ContextGestureTransition {
|
|
case begin
|
|
case update
|
|
case ended(CGFloat)
|
|
}
|
|
|
|
private class TimerTargetWrapper: NSObject {
|
|
let f: () -> Void
|
|
|
|
init(_ f: @escaping () -> Void) {
|
|
self.f = f
|
|
}
|
|
|
|
@objc func timerEvent() {
|
|
self.f()
|
|
}
|
|
}
|
|
|
|
private let beginDelay: Double = 0.1
|
|
|
|
private func cancelParentGestures(view: UIView) {
|
|
if let gestureRecognizers = view.gestureRecognizers {
|
|
for recognizer in gestureRecognizers {
|
|
recognizer.state = .failed
|
|
}
|
|
}
|
|
if let node = (view as? ListViewBackingView)?.target {
|
|
node.cancelSelection()
|
|
}
|
|
if let superview = view.superview {
|
|
cancelParentGestures(view: superview)
|
|
}
|
|
}
|
|
|
|
private func cancelOtherGestures(gesture: ContextGesture, view: UIView) {
|
|
if let gestureRecognizers = view.gestureRecognizers {
|
|
for recognizer in gestureRecognizers {
|
|
if let recognizer = recognizer as? ContextGesture, recognizer !== gesture {
|
|
recognizer.cancel()
|
|
}
|
|
}
|
|
}
|
|
for subview in view.subviews {
|
|
cancelOtherGestures(gesture: gesture, view: subview)
|
|
}
|
|
}
|
|
|
|
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 shouldBegin: ((CGPoint) -> Bool)?
|
|
public var activationProgress: ((CGFloat, ContextGestureTransition) -> Void)?
|
|
public var activated: ((ContextGesture) -> Void)?
|
|
public var externalUpdated: ((UIView?, CGPoint) -> Void)?
|
|
public var externalEnded: (((UIView?, CGPoint)?) -> Void)?
|
|
|
|
override public init(target: Any?, action: Selector?) {
|
|
super.init(target: target, action: action)
|
|
|
|
self.delegate = self
|
|
}
|
|
|
|
override public func reset() {
|
|
super.reset()
|
|
|
|
self.endPressedAppearance()
|
|
|
|
self.currentProgress = 0.0
|
|
self.delayTimer?.invalidate()
|
|
self.delayTimer = nil
|
|
self.isValidated = false
|
|
self.externalUpdated = nil
|
|
self.externalEnded = nil
|
|
self.animator?.invalidate()
|
|
self.animator = nil
|
|
}
|
|
|
|
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
|
if otherGestureRecognizer is UIPanGestureRecognizer {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
override public func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
|
|
super.touchesBegan(touches, with: event)
|
|
|
|
guard let touch = touches.first else {
|
|
return
|
|
}
|
|
|
|
if let shouldBegin = self.shouldBegin {
|
|
if !shouldBegin(touch.location(in: self.view)) {
|
|
self.state = .failed
|
|
return
|
|
}
|
|
}
|
|
|
|
let windowLocation = touch.location(in: nil)
|
|
if windowLocation.x < 8.0 {
|
|
self.state = .failed
|
|
return
|
|
}
|
|
|
|
if self.delayTimer == nil {
|
|
let delayTimer = Timer(timeInterval: beginDelay, target: TimerTargetWrapper { [weak self] in
|
|
guard let strongSelf = self, let _ = strongSelf.delayTimer else {
|
|
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 {
|
|
if let window = view.window {
|
|
cancelOtherGestures(gesture: strongSelf, view: window)
|
|
}
|
|
cancelParentGestures(view: view)
|
|
}
|
|
strongSelf.state = .began
|
|
default:
|
|
break
|
|
}
|
|
})
|
|
}
|
|
strongSelf.activationProgress?(strongSelf.currentProgress, .begin)
|
|
}, selector: #selector(TimerTargetWrapper.timerEvent), userInfo: nil, repeats: false)
|
|
self.delayTimer = delayTimer
|
|
RunLoop.main.add(delayTimer, forMode: .common)
|
|
}
|
|
}
|
|
|
|
override public func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
|
|
super.touchesMoved(touches, with: event)
|
|
|
|
if let touch = touches.first {
|
|
if #available(iOS 9.0, *) {
|
|
let maxForce: CGFloat = max(2.5, min(3.0, touch.maximumPossibleForce))
|
|
if touch.force >= maxForce {
|
|
if !self.isValidated {
|
|
self.isValidated = true
|
|
}
|
|
|
|
switch self.state {
|
|
case .possible:
|
|
self.delayTimer?.invalidate()
|
|
self.animator?.invalidate()
|
|
self.activated?(self)
|
|
if let view = self.view?.superview {
|
|
if let window = view.window {
|
|
cancelOtherGestures(gesture: self, view: window)
|
|
}
|
|
cancelParentGestures(view: view)
|
|
}
|
|
self.state = .began
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
self.externalUpdated?(self.view, touch.location(in: self.view))
|
|
}
|
|
}
|
|
|
|
override public func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
|
|
super.touchesEnded(touches, with: event)
|
|
|
|
if let touch = touches.first {
|
|
if !self.currentProgress.isZero, self.isValidated {
|
|
self.currentProgress = 0.0
|
|
self.activationProgress?(0.0, .ended(self.currentProgress))
|
|
}
|
|
|
|
self.externalEnded?((self.view, touch.location(in: self.view)))
|
|
}
|
|
|
|
self.delayTimer?.invalidate()
|
|
self.animator?.invalidate()
|
|
|
|
self.state = .failed
|
|
}
|
|
|
|
override public func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) {
|
|
super.touchesCancelled(touches, with: event)
|
|
|
|
if let _ = touches.first, !self.currentProgress.isZero, self.isValidated {
|
|
let previousProgress = self.currentProgress
|
|
self.currentProgress = 0.0
|
|
self.activationProgress?(0.0, .ended(previousProgress))
|
|
}
|
|
|
|
self.delayTimer?.invalidate()
|
|
self.animator?.invalidate()
|
|
|
|
self.state = .failed
|
|
}
|
|
|
|
public func cancel() {
|
|
if !self.currentProgress.isZero, self.isValidated {
|
|
let previousProgress = self.currentProgress
|
|
self.currentProgress = 0.0
|
|
self.activationProgress?(0.0, .ended(previousProgress))
|
|
|
|
self.delayTimer?.invalidate()
|
|
self.animator?.invalidate()
|
|
self.state = .failed
|
|
}
|
|
}
|
|
|
|
public func endPressedAppearance() {
|
|
if !self.currentProgress.isZero, self.isValidated {
|
|
let previousProgress = self.currentProgress
|
|
self.currentProgress = 0.0
|
|
self.delayTimer?.invalidate()
|
|
self.animator?.invalidate()
|
|
self.isValidated = false
|
|
self.activationProgress?(0.0, .ended(previousProgress))
|
|
}
|
|
}
|
|
}
|