Merge branch 'nt/volume-button-shutter-controls'
This commit is contained in:
commit
700336ca22
@ -536,6 +536,7 @@
|
||||
768A1A2B17FC9CD300E00ED8 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 768A1A2A17FC9CD300E00ED8 /* libz.dylib */; };
|
||||
76C87F19181EFCE600C4ACAB /* MediaPlayer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 76C87F18181EFCE600C4ACAB /* MediaPlayer.framework */; };
|
||||
76EB054018170B33006006FC /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB03C318170B33006006FC /* AppDelegate.m */; };
|
||||
8811CF842295D8DA00FF6549 /* VolumeButtons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8811CF832295D8DA00FF6549 /* VolumeButtons.swift */; };
|
||||
954AEE6A1DF33E01002E5410 /* ContactsPickerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 954AEE681DF33D32002E5410 /* ContactsPickerTest.swift */; };
|
||||
A10FDF79184FB4BB007FF963 /* MediaPlayer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 76C87F18181EFCE600C4ACAB /* MediaPlayer.framework */; };
|
||||
A11CD70D17FA230600A2D1B1 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A11CD70C17FA230600A2D1B1 /* QuartzCore.framework */; };
|
||||
@ -1317,6 +1318,7 @@
|
||||
76C87F18181EFCE600C4ACAB /* MediaPlayer.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MediaPlayer.framework; path = System/Library/Frameworks/MediaPlayer.framework; sourceTree = SDKROOT; };
|
||||
76EB03C218170B33006006FC /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
|
||||
76EB03C318170B33006006FC /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
|
||||
8811CF832295D8DA00FF6549 /* VolumeButtons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VolumeButtons.swift; sourceTree = "<group>"; };
|
||||
8981C8F64D94D3C52EB67A2C /* Pods-SignalTests.test.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SignalTests.test.xcconfig"; path = "Pods/Target Support Files/Pods-SignalTests/Pods-SignalTests.test.xcconfig"; sourceTree = "<group>"; };
|
||||
8EEE74B0753448C085B48721 /* Pods-SignalMessaging.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SignalMessaging.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-SignalMessaging/Pods-SignalMessaging.app store release.xcconfig"; sourceTree = "<group>"; };
|
||||
948239851C08032C842937CC /* Pods-SignalMessaging.test.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SignalMessaging.test.xcconfig"; path = "Pods/Target Support Files/Pods-SignalMessaging/Pods-SignalMessaging.test.xcconfig"; sourceTree = "<group>"; };
|
||||
@ -2511,6 +2513,7 @@
|
||||
34E5DC8020D8050D00C08145 /* RegistrationUtils.h */,
|
||||
34E5DC8120D8050D00C08145 /* RegistrationUtils.m */,
|
||||
4521C3BF1F59F3BA00B4C582 /* TextFieldHelper.swift */,
|
||||
8811CF832295D8DA00FF6549 /* VolumeButtons.swift */,
|
||||
FCFA64B11A24F29E0007FB87 /* UI Categories */,
|
||||
);
|
||||
path = util;
|
||||
@ -3701,6 +3704,7 @@
|
||||
450D19131F85236600970622 /* RemoteVideoView.m in Sources */,
|
||||
34129B8621EF877A005457A8 /* LinkPreviewView.swift in Sources */,
|
||||
34386A54207D271D009F5D9C /* NeverClearView.swift in Sources */,
|
||||
8811CF842295D8DA00FF6549 /* VolumeButtons.swift in Sources */,
|
||||
45DF5DF21DDB843F00C936C7 /* CompareSafetyNumbersActivity.swift in Sources */,
|
||||
451166C01FD86B98000739BA /* AccountManager.swift in Sources */,
|
||||
3430FE181F7751D4000EC51B /* GiphyAPI.swift in Sources */,
|
||||
|
||||
@ -25,6 +25,9 @@ protocol PhotoCaptureDelegate: AnyObject {
|
||||
func photoCaptureDidTryToCaptureTooMany(_ photoCapture: PhotoCapture)
|
||||
var zoomScaleReferenceHeight: CGFloat? { get }
|
||||
var captureOrientation: AVCaptureVideoOrientation { get }
|
||||
|
||||
func beginCaptureButtonAnimation(_ duration: TimeInterval)
|
||||
func endCaptureButtonAnimation(_ duration: TimeInterval)
|
||||
}
|
||||
|
||||
class PhotoCapture: NSObject {
|
||||
@ -332,15 +335,10 @@ class PhotoCapture: NSObject {
|
||||
private func clampZoom(_ factor: CGFloat, device: AVCaptureDevice) -> CGFloat {
|
||||
return min(factor.clamp(minimumZoom, maximumZoom), device.activeFormat.videoMaxZoomFactor)
|
||||
}
|
||||
}
|
||||
|
||||
extension PhotoCapture: CaptureButtonDelegate {
|
||||
|
||||
// MARK: - Photo
|
||||
|
||||
func didTapCaptureButton(_ captureButton: CaptureButton) {
|
||||
private func handleTap() {
|
||||
Logger.verbose("")
|
||||
|
||||
guard let delegate = delegate else { return }
|
||||
guard delegate.photoCaptureCanCaptureMoreItems(self) else {
|
||||
delegate.photoCaptureDidTryToCaptureTooMany(self)
|
||||
@ -355,7 +353,7 @@ extension PhotoCapture: CaptureButtonDelegate {
|
||||
|
||||
// MARK: - Video
|
||||
|
||||
func didBeginLongPressCaptureButton(_ captureButton: CaptureButton) {
|
||||
private func handleLongPressBegin() {
|
||||
AssertIsOnMainThread()
|
||||
Logger.verbose("")
|
||||
|
||||
@ -375,7 +373,7 @@ extension PhotoCapture: CaptureButtonDelegate {
|
||||
}.retainUntilComplete()
|
||||
}
|
||||
|
||||
func didCompleteLongPressCaptureButton(_ captureButton: CaptureButton) {
|
||||
private func handleLongPressComplete() {
|
||||
Logger.verbose("")
|
||||
sessionQueue.async {
|
||||
self.captureOutput.completeVideo(delegate: self)
|
||||
@ -386,7 +384,7 @@ extension PhotoCapture: CaptureButtonDelegate {
|
||||
delegate?.photoCaptureDidCompleteVideo(self)
|
||||
}
|
||||
|
||||
func didCancelLongPressCaptureButton(_ captureButton: CaptureButton) {
|
||||
private func handleLongPressCancel() {
|
||||
Logger.verbose("")
|
||||
AssertIsOnMainThread()
|
||||
sessionQueue.async {
|
||||
@ -394,6 +392,50 @@ extension PhotoCapture: CaptureButtonDelegate {
|
||||
}
|
||||
delegate?.photoCaptureDidCancelVideo(self)
|
||||
}
|
||||
}
|
||||
|
||||
extension PhotoCapture: VolumeButtonObserver {
|
||||
func didPressVolumeButton(with identifier: VolumeButtons.Identifier) {
|
||||
delegate?.beginCaptureButtonAnimation(0.5)
|
||||
}
|
||||
|
||||
func didReleaseVolumeButton(with identifier: VolumeButtons.Identifier) {
|
||||
delegate?.endCaptureButtonAnimation(0.2)
|
||||
}
|
||||
|
||||
func didTapVolumeButton(with identifier: VolumeButtons.Identifier) {
|
||||
handleTap()
|
||||
}
|
||||
|
||||
func didBeginLongPressVolumeButton(with identifier: VolumeButtons.Identifier) {
|
||||
handleLongPressBegin()
|
||||
}
|
||||
|
||||
func didCompleteLongPressVolumeButton(with identifier: VolumeButtons.Identifier) {
|
||||
handleLongPressComplete()
|
||||
}
|
||||
|
||||
func didCancelLongPressVolumeButton(with identifier: VolumeButtons.Identifier) {
|
||||
handleLongPressCancel()
|
||||
}
|
||||
}
|
||||
|
||||
extension PhotoCapture: CaptureButtonDelegate {
|
||||
func didTapCaptureButton(_ captureButton: CaptureButton) {
|
||||
handleTap()
|
||||
}
|
||||
|
||||
func didBeginLongPressCaptureButton(_ captureButton: CaptureButton) {
|
||||
handleLongPressBegin()
|
||||
}
|
||||
|
||||
func didCompleteLongPressCaptureButton(_ captureButton: CaptureButton) {
|
||||
handleLongPressComplete()
|
||||
}
|
||||
|
||||
func didCancelLongPressCaptureButton(_ captureButton: CaptureButton) {
|
||||
handleLongPressCancel()
|
||||
}
|
||||
|
||||
var zoomScaleReferenceHeight: CGFloat? {
|
||||
return delegate?.zoomScaleReferenceHeight
|
||||
|
||||
@ -70,7 +70,6 @@ class PhotoCaptureViewController: OWSViewController {
|
||||
view.addGestureRecognizer(doubleTapToSwitchCameraGesture)
|
||||
|
||||
tapToFocusGesture.require(toFail: doubleTapToSwitchCameraGesture)
|
||||
doubleTapToSwitchCameraGesture.require(toFail: captureButton.tapGesture)
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
@ -82,8 +81,14 @@ class PhotoCaptureViewController: OWSViewController {
|
||||
super.viewDidAppear(animated)
|
||||
if hasCaptureStarted {
|
||||
BenchEventComplete(eventId: "Show-Camera")
|
||||
VolumeButtons.shared?.addObserver(observer: photoCapture)
|
||||
}
|
||||
}
|
||||
|
||||
override func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
VolumeButtons.shared?.removeObserver(photoCapture)
|
||||
}
|
||||
|
||||
override var prefersStatusBarHidden: Bool {
|
||||
guard !OWSWindowManager.shared().hasCall() else {
|
||||
@ -333,6 +338,8 @@ class PhotoCaptureViewController: OWSViewController {
|
||||
view.addSubview(captureButton)
|
||||
captureButton.autoHCenterInSuperview()
|
||||
captureButton.centerYAnchor.constraint(equalTo: view.layoutMarginsGuide.bottomAnchor, constant: SendMediaNavigationController.bottomButtonsCenterOffset).isActive = true
|
||||
|
||||
VolumeButtons.shared?.addObserver(observer: photoCapture)
|
||||
}
|
||||
|
||||
private func showFailureUI(error: Error) {
|
||||
@ -368,6 +375,13 @@ extension PhotoCaptureViewController: PhotoCaptureDelegate {
|
||||
captureFeedbackView.backgroundColor = .black
|
||||
view.insertSubview(captureFeedbackView, aboveSubview: previewView)
|
||||
captureFeedbackView.autoPinEdgesToSuperviewEdges()
|
||||
|
||||
// Ensure the capture feedback is laid out before we remove it,
|
||||
// depending on where we're coming from a layout pass might not
|
||||
// trigger in 0.05 seconds otherwise.
|
||||
view.setNeedsLayout()
|
||||
view.layoutIfNeeded()
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
|
||||
captureFeedbackView.removeFromSuperview()
|
||||
}
|
||||
@ -419,6 +433,14 @@ extension PhotoCaptureViewController: PhotoCaptureDelegate {
|
||||
var captureOrientation: AVCaptureVideoOrientation {
|
||||
return lastKnownCaptureOrientation
|
||||
}
|
||||
|
||||
func beginCaptureButtonAnimation(_ duration: TimeInterval) {
|
||||
captureButton.beginRecordingAnimation(duration: duration)
|
||||
}
|
||||
|
||||
func endCaptureButtonAnimation(_ duration: TimeInterval) {
|
||||
captureButton.endRecordingAnimation(duration: duration)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Views
|
||||
@ -440,8 +462,6 @@ class CaptureButton: UIView {
|
||||
|
||||
let innerButton = CircleView()
|
||||
|
||||
var tapGesture: UITapGestureRecognizer!
|
||||
|
||||
var longPressGesture: UILongPressGestureRecognizer!
|
||||
let longPressDuration = 0.5
|
||||
|
||||
@ -457,11 +477,10 @@ class CaptureButton: UIView {
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTap))
|
||||
innerButton.addGestureRecognizer(tapGesture)
|
||||
|
||||
// The long press handles both the tap and the hold interaction, as well as the animation
|
||||
// the presents as the user begins to hold (and the button begins to grow prior to recording)
|
||||
longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(didLongPress))
|
||||
longPressGesture.minimumPressDuration = longPressDuration
|
||||
longPressGesture.minimumPressDuration = 0
|
||||
innerButton.addGestureRecognizer(longPressGesture)
|
||||
|
||||
addSubview(innerButton)
|
||||
@ -485,14 +504,39 @@ class CaptureButton: UIView {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
// MARK: - Gestures
|
||||
|
||||
@objc
|
||||
func didTap(_ gesture: UITapGestureRecognizer) {
|
||||
delegate?.didTapCaptureButton(self)
|
||||
func beginRecordingAnimation(duration: TimeInterval, delay: TimeInterval = 0) {
|
||||
UIView.animate(
|
||||
withDuration: duration,
|
||||
delay: delay,
|
||||
options: [.beginFromCurrentState, .curveLinear],
|
||||
animations: {
|
||||
self.innerButtonSizeConstraints.forEach { $0.constant = type(of: self).recordingDiameter }
|
||||
self.zoomIndicatorSizeConstraints.forEach { $0.constant = type(of: self).recordingDiameter }
|
||||
self.superview?.layoutIfNeeded()
|
||||
},
|
||||
completion: nil
|
||||
)
|
||||
}
|
||||
|
||||
func endRecordingAnimation(duration: TimeInterval, delay: TimeInterval = 0) {
|
||||
UIView.animate(
|
||||
withDuration: duration,
|
||||
delay: delay,
|
||||
options: [.beginFromCurrentState, .curveEaseIn],
|
||||
animations: {
|
||||
self.innerButtonSizeConstraints.forEach { $0.constant = self.defaultDiameter }
|
||||
self.zoomIndicatorSizeConstraints.forEach { $0.constant = self.defaultDiameter }
|
||||
self.superview?.layoutIfNeeded()
|
||||
},
|
||||
completion: nil
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: - Gestures
|
||||
|
||||
var initialTouchLocation: CGPoint?
|
||||
var touchTimer: Timer?
|
||||
var isLongPressing = false
|
||||
|
||||
@objc
|
||||
func didLongPress(_ gesture: UILongPressGestureRecognizer) {
|
||||
@ -507,13 +551,24 @@ class CaptureButton: UIView {
|
||||
case .possible: break
|
||||
case .began:
|
||||
initialTouchLocation = gesture.location(in: gesture.view)
|
||||
delegate?.didBeginLongPressCaptureButton(self)
|
||||
UIView.animate(withDuration: 0.2) {
|
||||
self.innerButtonSizeConstraints.forEach { $0.constant = type(of: self).recordingDiameter }
|
||||
self.zoomIndicatorSizeConstraints.forEach { $0.constant = type(of: self).recordingDiameter }
|
||||
self.superview?.layoutIfNeeded()
|
||||
beginRecordingAnimation(duration: 0.4, delay: 0.1)
|
||||
|
||||
isLongPressing = false
|
||||
|
||||
touchTimer?.invalidate()
|
||||
touchTimer = WeakTimer.scheduledTimer(
|
||||
timeInterval: longPressDuration,
|
||||
target: self,
|
||||
userInfo: nil,
|
||||
repeats: false
|
||||
) { [weak self] _ in
|
||||
guard let `self` = self else { return }
|
||||
self.isLongPressing = true
|
||||
self.delegate?.didBeginLongPressCaptureButton(self)
|
||||
}
|
||||
case .changed:
|
||||
guard isLongPressing else { break }
|
||||
|
||||
guard let referenceHeight = delegate?.zoomScaleReferenceHeight else {
|
||||
owsFailDebug("referenceHeight was unexpectedly nil")
|
||||
return
|
||||
@ -545,22 +600,25 @@ class CaptureButton: UIView {
|
||||
|
||||
delegate?.longPressCaptureButton(self, didUpdateZoomAlpha: alpha)
|
||||
case .ended:
|
||||
UIView.animate(withDuration: 0.2) {
|
||||
self.innerButtonSizeConstraints.forEach { $0.constant = self.defaultDiameter }
|
||||
self.zoomIndicatorSizeConstraints.forEach { $0.constant = self.defaultDiameter }
|
||||
endRecordingAnimation(duration: 0.2)
|
||||
|
||||
self.superview?.layoutIfNeeded()
|
||||
if isLongPressing {
|
||||
delegate?.didCompleteLongPressCaptureButton(self)
|
||||
} else {
|
||||
delegate?.didTapCaptureButton(self)
|
||||
}
|
||||
delegate?.didCompleteLongPressCaptureButton(self)
|
||||
|
||||
touchTimer?.invalidate()
|
||||
touchTimer = nil
|
||||
case .cancelled, .failed:
|
||||
endRecordingAnimation(duration: 0.2)
|
||||
|
||||
UIView.animate(withDuration: 0.2) {
|
||||
self.innerButtonSizeConstraints.forEach { $0.constant = self.defaultDiameter }
|
||||
self.zoomIndicatorSizeConstraints.forEach { $0.constant = self.defaultDiameter }
|
||||
|
||||
self.superview?.layoutIfNeeded()
|
||||
if isLongPressing {
|
||||
delegate?.didCancelLongPressCaptureButton(self)
|
||||
}
|
||||
delegate?.didCancelLongPressCaptureButton(self)
|
||||
|
||||
touchTimer?.invalidate()
|
||||
touchTimer = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -672,6 +730,7 @@ class RecordingTimerView: UIView {
|
||||
UIView.animate(withDuration: 0.4) {
|
||||
self.icon.alpha = 0
|
||||
}
|
||||
label.text = nil
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
247
Signal/src/util/VolumeButtons.swift
Normal file
247
Signal/src/util/VolumeButtons.swift
Normal file
@ -0,0 +1,247 @@
|
||||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
protocol VolumeButtonObserver: class {
|
||||
func didPressVolumeButton(with identifier: VolumeButtons.Identifier)
|
||||
func didReleaseVolumeButton(with identifier: VolumeButtons.Identifier)
|
||||
|
||||
func didTapVolumeButton(with identifier: VolumeButtons.Identifier)
|
||||
|
||||
func didBeginLongPressVolumeButton(with identifier: VolumeButtons.Identifier)
|
||||
func didCompleteLongPressVolumeButton(with identifier: VolumeButtons.Identifier)
|
||||
func didCancelLongPressVolumeButton(with identifier: VolumeButtons.Identifier)
|
||||
}
|
||||
|
||||
class VolumeButtons {
|
||||
static let shared = VolumeButtons()
|
||||
|
||||
enum Identifier {
|
||||
case up, down
|
||||
}
|
||||
|
||||
private init?() {
|
||||
// If for some reason the API we’re using goes away (for example, in
|
||||
// a future iOS version) this class will never instantiate.
|
||||
guard VolumeButtons.supportsListeningToEvents else { return nil }
|
||||
}
|
||||
|
||||
deinit {
|
||||
stopObservation()
|
||||
}
|
||||
|
||||
// MARK: Observer Management
|
||||
|
||||
private var observers: [Weak<VolumeButtonObserver>] = []
|
||||
func addObserver(observer: VolumeButtonObserver) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
if observers.firstIndex(where: { $0.value === observer }) == nil {
|
||||
observers.append(Weak(value: observer))
|
||||
}
|
||||
|
||||
guard !observers.isEmpty else { return }
|
||||
startObservation()
|
||||
}
|
||||
|
||||
func removeObserver(_ observer: VolumeButtonObserver) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
observers = observers.filter { $0.value !== observer }
|
||||
|
||||
guard observers.isEmpty else { return }
|
||||
stopObservation()
|
||||
}
|
||||
|
||||
func removeAllObservers() {
|
||||
AssertIsOnMainThread()
|
||||
observers = []
|
||||
stopObservation()
|
||||
}
|
||||
|
||||
private func startObservation() {
|
||||
guard !VolumeButtons.isRegisteredForEvents else { return }
|
||||
VolumeButtons.isRegisteredForEvents = true
|
||||
registerForNotifications()
|
||||
}
|
||||
|
||||
private func stopObservation() {
|
||||
VolumeButtons.isRegisteredForEvents = false
|
||||
unregisterForNotifications()
|
||||
|
||||
defer { resetLongPress() }
|
||||
guard let longPressingButton = longPressingButton else { return }
|
||||
notifyObserversOfCancelLongPress(with: longPressingButton)
|
||||
}
|
||||
|
||||
private func notifyObserversOfTap(with identifier: Identifier) {
|
||||
observers.forEach { observer in
|
||||
observer.value?.didTapVolumeButton(with: identifier)
|
||||
}
|
||||
}
|
||||
|
||||
private func notifyObserversOfBeginLongPress(with identifier: Identifier) {
|
||||
observers.forEach { observer in
|
||||
observer.value?.didBeginLongPressVolumeButton(with: identifier)
|
||||
}
|
||||
}
|
||||
|
||||
private func notifyObserversOfCompleteLongPress(with identifier: Identifier) {
|
||||
observers.forEach { observer in
|
||||
observer.value?.didCompleteLongPressVolumeButton(with: identifier)
|
||||
}
|
||||
}
|
||||
|
||||
private func notifyObserversOfCancelLongPress(with identifier: Identifier) {
|
||||
observers.forEach { observer in
|
||||
observer.value?.didCancelLongPressVolumeButton(with: identifier)
|
||||
}
|
||||
}
|
||||
|
||||
private func notifyObserversOfPress(with identifier: Identifier) {
|
||||
observers.forEach { observer in
|
||||
observer.value?.didPressVolumeButton(with: identifier)
|
||||
}
|
||||
}
|
||||
|
||||
private func notifyObserversOfRelease(with identifier: Identifier) {
|
||||
observers.forEach { observer in
|
||||
observer.value?.didReleaseVolumeButton(with: identifier)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Tap / long press handling
|
||||
|
||||
private var longPressTimer: Timer?
|
||||
private var longPressingButton: Identifier?
|
||||
|
||||
// It's not possible for up and down to be pressed simulataneously
|
||||
// (if you press the second button, the OS will end the press on
|
||||
// the first), so it allows for simplified handling here.
|
||||
private func didPressButton(with identifier: Identifier) {
|
||||
longPressingButton = nil
|
||||
|
||||
longPressTimer?.invalidate()
|
||||
longPressTimer = WeakTimer.scheduledTimer(
|
||||
timeInterval: longPressDuration,
|
||||
target: self,
|
||||
userInfo: nil,
|
||||
repeats: false
|
||||
) { [weak self] _ in
|
||||
self?.longPressingButton = identifier
|
||||
self?.notifyObserversOfBeginLongPress(with: identifier)
|
||||
self?.longPressTimer?.invalidate()
|
||||
self?.longPressTimer = nil
|
||||
}
|
||||
|
||||
notifyObserversOfPress(with: identifier)
|
||||
}
|
||||
|
||||
private func didReleaseButton(with identifier: Identifier) {
|
||||
if longPressingButton == identifier {
|
||||
notifyObserversOfCompleteLongPress(with: identifier)
|
||||
} else {
|
||||
notifyObserversOfTap(with: identifier)
|
||||
}
|
||||
|
||||
resetLongPress()
|
||||
|
||||
notifyObserversOfRelease(with: identifier)
|
||||
}
|
||||
|
||||
private func resetLongPress() {
|
||||
longPressTimer?.invalidate()
|
||||
longPressTimer = nil
|
||||
longPressingButton = nil
|
||||
}
|
||||
|
||||
// MARK: Volume Event Registration
|
||||
|
||||
// let encodedSelectorString = "setWantsVolumeButtonEvents:".encodedForSelector
|
||||
private static let volumeEventsSelector = Selector("BXYGaHIABgVnAX0HfnZTBwYGAQBWCHYABgVL".decodedForSelector!)
|
||||
|
||||
private(set) static var isRegisteredForEvents = false {
|
||||
didSet {
|
||||
setEventRegistration(isRegisteredForEvents)
|
||||
}
|
||||
}
|
||||
|
||||
private static func setEventRegistration(_ active: Bool) {
|
||||
typealias Type = @convention(c) (AnyObject, Selector, Bool) -> Void
|
||||
let implementation = class_getMethodImplementation(UIApplication.self, volumeEventsSelector)
|
||||
let setRegistration = unsafeBitCast(implementation, to: Type.self)
|
||||
setRegistration(UIApplication.shared, volumeEventsSelector, active)
|
||||
}
|
||||
|
||||
private static var supportsListeningToEvents: Bool {
|
||||
return UIApplication.shared.responds(to: volumeEventsSelector)
|
||||
}
|
||||
|
||||
// MARK: Notification Handling
|
||||
|
||||
// let encodedDownDownNotificationName = "_UIApplicationVolumeDownButtonDownNotification".encodedForSelector
|
||||
private let downDownNotificationName = Notification.Name("cGZaUgICfXp0cgZ6AQBnAX0HfnZVAQkAUwcGBgEAVQEJAF8BBnp3enRyBnoBAA==".decodedForSelector!)
|
||||
|
||||
// let encodedDownUpNotificationName = "_UIApplicationVolumeDownButtonUpNotification".encodedForSelector
|
||||
private let downUpNotificationName = Notification.Name("cGZaUgICfXp0cgZ6AQBnAX0HfnZVAQkAUwcGBgEAZgJfAQZ6d3p0cgZ6AQA=".decodedForSelector!)
|
||||
|
||||
// let encodedUpDownNotificationName = "_UIApplicationVolumeUpButtonDownNotification".encodedForSelector
|
||||
private let upDownNotificationName = Notification.Name("cGZaUgICfXp0cgZ6AQBnAX0HfnZmAlMHBgYBAFUBCQBfAQZ6d3p0cgZ6AQA=".decodedForSelector!)
|
||||
|
||||
// let encodedUpUpNotificationName = "_UIApplicationVolumeUpButtonUpNotification".encodedForSelector
|
||||
private let upUpNotificationName = Notification.Name("cGZaUgICfXp0cgZ6AQBnAX0HfnZmAlMHBgYBAGYCXwEGend6dHIGegEA".decodedForSelector!)
|
||||
|
||||
private let longPressDuration: TimeInterval = 0.5
|
||||
|
||||
private func registerForNotifications() {
|
||||
NotificationCenter.default.addObserver(
|
||||
self,
|
||||
selector: #selector(didPressVolumeUp),
|
||||
name: upDownNotificationName,
|
||||
object: nil
|
||||
)
|
||||
|
||||
NotificationCenter.default.addObserver(
|
||||
self,
|
||||
selector: #selector(didReleaseVolumeUp),
|
||||
name: upUpNotificationName,
|
||||
object: nil
|
||||
)
|
||||
|
||||
NotificationCenter.default.addObserver(
|
||||
self,
|
||||
selector: #selector(didPressVolumeDown),
|
||||
name: downDownNotificationName,
|
||||
object: nil
|
||||
)
|
||||
|
||||
NotificationCenter.default.addObserver(
|
||||
self,
|
||||
selector: #selector(didReleaseVolumeDown),
|
||||
name: downUpNotificationName,
|
||||
object: nil
|
||||
)
|
||||
}
|
||||
|
||||
private func unregisterForNotifications() {
|
||||
NotificationCenter.default.removeObserver(self)
|
||||
}
|
||||
|
||||
@objc func didPressVolumeUp(_ notification: Notification) {
|
||||
didPressButton(with: .up)
|
||||
}
|
||||
|
||||
@objc func didReleaseVolumeUp(_ notification: Notification) {
|
||||
didReleaseButton(with: .up)
|
||||
}
|
||||
|
||||
@objc func didPressVolumeDown(_ notification: Notification) {
|
||||
didPressButton(with: .down)
|
||||
}
|
||||
|
||||
@objc func didReleaseVolumeDown(_ notification: Notification) {
|
||||
didReleaseButton(with: .down)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user