mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
322 lines
14 KiB
Swift
322 lines
14 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import SwiftSignalKit
|
|
import AsyncDisplayKit
|
|
import Display
|
|
|
|
private func traceDeceleratingScrollView(_ view: UIView, at point: CGPoint) -> Bool {
|
|
if view.bounds.contains(point), let view = view as? UIScrollView, view.isDecelerating {
|
|
return true
|
|
}
|
|
for subview in view.subviews {
|
|
let subviewPoint = view.convert(point, to: subview)
|
|
if traceDeceleratingScrollView(subview, at: subviewPoint) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
public final class PeekControllerGestureRecognizer: UIPanGestureRecognizer {
|
|
private let contentAtPoint: (CGPoint) -> Signal<(UIView, CGRect, PeekControllerContent)?, NoError>?
|
|
private let present: (PeekControllerContent, UIView, CGRect) -> ViewController?
|
|
private let updateContent: (PeekControllerContent?) -> Void
|
|
private let activateBySingleTap: Bool
|
|
public var longPressEnabled = true
|
|
public var checkSingleTapActivationAtPoint: ((CGPoint) -> Bool)?
|
|
|
|
private var tapLocation: CGPoint?
|
|
private var longTapTimer: SwiftSignalKit.Timer?
|
|
private var pressTimer: SwiftSignalKit.Timer?
|
|
|
|
private let candidateContentDisposable = MetaDisposable()
|
|
private var candidateContent: (UIView, CGRect, PeekControllerContent)? {
|
|
didSet {
|
|
self.updateContent(self.candidateContent?.2)
|
|
}
|
|
}
|
|
|
|
private var menuActivation: PeerControllerMenuActivation?
|
|
private weak var presentedController: PeekController?
|
|
|
|
public init(contentAtPoint: @escaping (CGPoint) -> Signal<(UIView, CGRect, PeekControllerContent)?, NoError>?, present: @escaping (PeekControllerContent, UIView, CGRect) -> ViewController?, updateContent: @escaping (PeekControllerContent?) -> Void = { _ in }, activateBySingleTap: Bool = false) {
|
|
self.contentAtPoint = contentAtPoint
|
|
self.present = present
|
|
self.updateContent = updateContent
|
|
self.activateBySingleTap = activateBySingleTap
|
|
|
|
super.init(target: nil, action: nil)
|
|
}
|
|
|
|
deinit {
|
|
self.longTapTimer?.invalidate()
|
|
self.pressTimer?.invalidate()
|
|
self.candidateContentDisposable.dispose()
|
|
}
|
|
|
|
private func startLongTapTimer() {
|
|
guard self.longPressEnabled else {
|
|
return
|
|
}
|
|
self.longTapTimer?.invalidate()
|
|
let longTapTimer = SwiftSignalKit.Timer(timeout: 0.4, repeat: false, completion: { [weak self] in
|
|
self?.longTapTimerFired()
|
|
}, queue: Queue.mainQueue())
|
|
self.longTapTimer = longTapTimer
|
|
longTapTimer.start()
|
|
}
|
|
|
|
private func startPressTimer() {
|
|
self.pressTimer?.invalidate()
|
|
let pressTimer = SwiftSignalKit.Timer(timeout: 1.0, repeat: false, completion: { [weak self] in
|
|
self?.pressTimerFired()
|
|
}, queue: Queue.mainQueue())
|
|
self.pressTimer = pressTimer
|
|
pressTimer.start()
|
|
}
|
|
|
|
private func stopLongTapTimer() {
|
|
self.longTapTimer?.invalidate()
|
|
self.longTapTimer = nil
|
|
}
|
|
|
|
private func stopPressTimer() {
|
|
self.pressTimer?.invalidate()
|
|
self.pressTimer = nil
|
|
}
|
|
|
|
override public func reset() {
|
|
super.reset()
|
|
|
|
self.stopLongTapTimer()
|
|
self.stopPressTimer()
|
|
self.tapLocation = nil
|
|
self.candidateContent = nil
|
|
self.menuActivation = nil
|
|
self.presentedController = nil
|
|
}
|
|
|
|
private func longTapTimerFired() {
|
|
guard let tapLocation = self.tapLocation else {
|
|
return
|
|
}
|
|
|
|
self.checkCandidateContent(at: tapLocation)
|
|
}
|
|
|
|
private func pressTimerFired() {
|
|
if let _ = self.tapLocation, let menuActivation = self.menuActivation, case .press = menuActivation {
|
|
if let presentedController = self.presentedController {
|
|
if presentedController.isNodeLoaded {
|
|
(presentedController.displayNode as? PeekControllerNode)?.activateMenu()
|
|
}
|
|
self.menuActivation = nil
|
|
// self.presentedController = nil
|
|
// self.state = .ended
|
|
}
|
|
}
|
|
}
|
|
|
|
override public func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
|
|
super.touchesBegan(touches, with: event)
|
|
|
|
if let view = self.view, let tapLocation = touches.first?.location(in: view) {
|
|
if traceDeceleratingScrollView(view, at: tapLocation) {
|
|
self.candidateContent = nil
|
|
self.state = .failed
|
|
} else {
|
|
self.tapLocation = tapLocation
|
|
self.startLongTapTimer()
|
|
}
|
|
}
|
|
}
|
|
|
|
override public func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
|
|
super.touchesEnded(touches, with: event)
|
|
|
|
var activateBySingleTap = self.activateBySingleTap
|
|
if !activateBySingleTap, let checkSingleTapActivationAtPoint = self.checkSingleTapActivationAtPoint, let tapLocation = self.tapLocation {
|
|
activateBySingleTap = checkSingleTapActivationAtPoint(tapLocation)
|
|
}
|
|
|
|
if activateBySingleTap, self.presentedController == nil {
|
|
self.longTapTimer?.invalidate()
|
|
self.pressTimer?.invalidate()
|
|
if let tapLocation = self.tapLocation {
|
|
self.checkCandidateContent(at: tapLocation, forceActivate: true)
|
|
}
|
|
self.state = .ended
|
|
} else {
|
|
if let presentedController = self.presentedController, presentedController.isNodeLoaded, let location = touches.first?.location(in: presentedController.view) {
|
|
(presentedController.displayNode as? PeekControllerNode)?.endDragging(location)
|
|
self.presentedController = nil
|
|
self.menuActivation = nil
|
|
}
|
|
|
|
self.tapLocation = nil
|
|
self.candidateContent = nil
|
|
self.longTapTimer?.invalidate()
|
|
self.pressTimer?.invalidate()
|
|
self.candidateContentDisposable.set(nil)
|
|
self.state = .failed
|
|
}
|
|
}
|
|
|
|
override public func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) {
|
|
super.touchesCancelled(touches, with: event)
|
|
|
|
self.tapLocation = nil
|
|
self.candidateContent = nil
|
|
self.state = .failed
|
|
|
|
if let presentedController = self.presentedController {
|
|
self.menuActivation = nil
|
|
self.presentedController = nil
|
|
presentedController.dismiss()
|
|
}
|
|
}
|
|
|
|
override public func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
|
|
super.touchesMoved(touches, with: event)
|
|
|
|
if let touch = touches.first, let initialTapLocation = self.tapLocation {
|
|
let touchLocation = touch.location(in: self.view)
|
|
if let presentedController = self.presentedController, self.menuActivation == nil {
|
|
if presentedController.isNodeLoaded {
|
|
let touchLocation = touch.location(in: presentedController.view)
|
|
(presentedController.displayNode as? PeekControllerNode)?.applyDraggingOffset(touchLocation)
|
|
}
|
|
} else if let menuActivation = self.menuActivation, let presentedController = self.presentedController {
|
|
switch menuActivation {
|
|
case .drag:
|
|
var offset = touchLocation.y - initialTapLocation.y
|
|
let delta = abs(offset)
|
|
let factor: CGFloat = 60.0
|
|
offset = (-((1.0 - (1.0 / (((delta) * 0.55 / (factor)) + 1.0))) * factor)) * (offset < 0.0 ? 1.0 : -1.0)
|
|
|
|
if presentedController.isNodeLoaded {
|
|
// (presentedController.displayNode as? PeekControllerNode)?.applyDraggingOffset(offset)
|
|
}
|
|
case .press:
|
|
if #available(iOSApplicationExtension 9.0, iOS 9.0, *) {
|
|
if touch.force >= 2.5 {
|
|
if presentedController.isNodeLoaded {
|
|
(presentedController.displayNode as? PeekControllerNode)?.activateMenu()
|
|
self.menuActivation = nil
|
|
self.presentedController = nil
|
|
self.candidateContent = nil
|
|
self.state = .ended
|
|
self.candidateContentDisposable.set(nil)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
if self.pressTimer != nil {
|
|
let dX = touchLocation.x - initialTapLocation.x
|
|
let dY = touchLocation.y - initialTapLocation.y
|
|
|
|
if dX * dX + dY * dY > 3.0 * 3.0 {
|
|
self.startPressTimer()
|
|
}
|
|
}
|
|
|
|
if self.presentedController != nil {
|
|
self.checkCandidateContent(at: touchLocation)
|
|
}
|
|
}
|
|
} else {
|
|
let dX = touchLocation.x - initialTapLocation.x
|
|
let dY = touchLocation.y - initialTapLocation.y
|
|
|
|
if dX * dX + dY * dY > 3.0 * 3.0 {
|
|
self.stopLongTapTimer()
|
|
self.tapLocation = nil
|
|
self.candidateContent = nil
|
|
self.state = .failed
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private func checkCandidateContent(at touchLocation: CGPoint, forceActivate: Bool = false) {
|
|
//print("check begin")
|
|
if let contentSignal = self.contentAtPoint(touchLocation) {
|
|
self.candidateContentDisposable.set((contentSignal
|
|
|> deliverOnMainQueue).start(next: { [weak self] result in
|
|
if let strongSelf = self {
|
|
let processResult: Bool
|
|
if forceActivate {
|
|
processResult = true
|
|
} else {
|
|
switch strongSelf.state {
|
|
case .possible, .changed:
|
|
processResult = true
|
|
default:
|
|
processResult = false
|
|
}
|
|
}
|
|
//print("check received, will process: \(processResult), force: \(forceActivate), state: \(strongSelf.state)")
|
|
if processResult {
|
|
if let (sourceView, sourceRect, content) = result {
|
|
if let currentContent = strongSelf.candidateContent {
|
|
if !currentContent.2.isEqual(to: content) {
|
|
strongSelf.tapLocation = touchLocation
|
|
strongSelf.candidateContent = (sourceView, sourceRect, content)
|
|
strongSelf.menuActivation = content.menuActivation()
|
|
if let presentedController = strongSelf.presentedController, presentedController.isNodeLoaded {
|
|
presentedController.sourceView = {
|
|
return (sourceView, sourceRect)
|
|
}
|
|
(presentedController.displayNode as? PeekControllerNode)?.updateContent(content: content)
|
|
}
|
|
}
|
|
} else {
|
|
if let presentedController = strongSelf.present(content, sourceView, sourceRect) {
|
|
if let presentedController = presentedController as? PeekController {
|
|
if forceActivate {
|
|
strongSelf.candidateContent = nil
|
|
if case .press = content.menuActivation() {
|
|
(presentedController.displayNode as? PeekControllerNode)?.activateMenu()
|
|
}
|
|
} else {
|
|
strongSelf.candidateContent = (sourceView, sourceRect, content)
|
|
strongSelf.menuActivation = content.menuActivation()
|
|
strongSelf.presentedController = presentedController
|
|
|
|
strongSelf.state = .began
|
|
|
|
switch content.menuActivation() {
|
|
case .drag:
|
|
break
|
|
case .press:
|
|
if #available(iOSApplicationExtension 9.0, iOS 9.0, *) {
|
|
if presentedController.traitCollection.forceTouchCapability != .available {
|
|
strongSelf.startPressTimer()
|
|
}
|
|
} else {
|
|
strongSelf.startPressTimer()
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
if strongSelf.state != .ended {
|
|
strongSelf.state = .ended
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else if strongSelf.presentedController == nil {
|
|
if strongSelf.state != .possible && strongSelf.state != .ended {
|
|
strongSelf.state = .failed
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}))
|
|
} else if self.presentedController == nil {
|
|
self.state = .failed
|
|
}
|
|
}
|
|
}
|