Rework down state of payment custom keyboard.

This commit is contained in:
Matthew Chen 2021-03-05 18:07:11 -03:00
parent afd2aeef5c
commit aa66fb7ec3
2 changed files with 22 additions and 179 deletions

View File

@ -277,7 +277,6 @@ public class SendPaymentViewController: OWSViewController {
let keyboardHSpacing: CGFloat = 25
let buttonFont = UIFont.ows_dynamicTypeTitle1Clamped
func buildAmountKeyboardButton(title: String, block: @escaping () -> Void) -> OWSButton {
// PAYMENTS TODO: Highlight down state?
let button = OWSButton(block: block)
let label = UILabel()
@ -290,7 +289,6 @@ public class SendPaymentViewController: OWSViewController {
return button
}
func buildAmountKeyboardButton(imageName: String, block: @escaping () -> Void) -> OWSButton {
// PAYMENTS TODO: Highlight down state?
let button = OWSButton(imageName: imageName,
tintColor: Theme.primaryTextColor,
block: block)
@ -304,21 +302,27 @@ public class SendPaymentViewController: OWSViewController {
let buttonSize = buttonFont.lineHeight * 2.0
button.autoSetDimension(.height, toSize: buttonSize)
let downStateView = OWSLayerView.circleView()
button.downStateView = downStateView
button.sendSubviewToBack(downStateView)
downStateView.autoPinEdgesToSuperviewEdges()
downStateView.backgroundColor = (Theme.isDarkThemeEnabled
? UIColor.ows_gray90
: UIColor.ows_gray02)
let downStateColor = (Theme.isDarkThemeEnabled
? UIColor.ows_gray90
: UIColor.ows_gray02)
let downStateImage = UIImage(color: downStateColor,
size: CGSize(width: 1, height: 1))
button.setBackgroundImage(downStateImage, for: .highlighted)
// We clip the button to a circle so that the
// down state is circular.
let buttonClipView = OWSLayerView.circleView()
buttonClipView.addSubview(button)
button.autoPinEdgesToSuperviewEdges()
buttonClipView.clipsToBounds = true
let buttonWrapper = UIView.container()
buttonWrapper.addSubview(button)
button.autoPinEdge(toSuperviewEdge: .top)
button.autoPinEdge(toSuperviewEdge: .bottom)
button.autoHCenterInSuperview()
button.autoPinEdge(toSuperviewMargin: .leading, relation: .greaterThanOrEqual)
button.autoPinEdge(toSuperviewMargin: .trailing, relation: .greaterThanOrEqual)
buttonWrapper.addSubview(buttonClipView)
buttonClipView.autoPinEdge(toSuperviewEdge: .top)
buttonClipView.autoPinEdge(toSuperviewEdge: .bottom)
buttonClipView.autoHCenterInSuperview()
buttonClipView.autoPinEdge(toSuperviewMargin: .leading, relation: .greaterThanOrEqual)
buttonClipView.autoPinEdge(toSuperviewMargin: .trailing, relation: .greaterThanOrEqual)
return buttonWrapper
}

View File

@ -10,26 +10,6 @@ public class OWSButton: UIButton {
@objc
public var block: () -> Void = { }
public var downStateView: UIView? {
didSet {
oldValue?.removeFromSuperview()
if let downStateView = downStateView {
addSubview(downStateView)
ensureDownGestureRecognizer()
}
applyDownState()
}
}
private var isDown: Bool = false {
didSet {
applyDownState()
}
}
// MARK: -
@objc
@ -37,7 +17,7 @@ public class OWSButton: UIButton {
super.init(frame: .zero)
self.block = block
ensureTapGestureRecognizer()
addTarget(self, action: #selector(didTap), for: .touchUpInside)
}
@objc
@ -45,7 +25,7 @@ public class OWSButton: UIButton {
super.init(frame: .zero)
self.block = block
ensureTapGestureRecognizer()
addTarget(self, action: #selector(didTap), for: .touchUpInside)
setTitle(title, for: .normal)
}
@ -56,7 +36,7 @@ public class OWSButton: UIButton {
super.init(frame: .zero)
self.block = block
ensureTapGestureRecognizer()
addTarget(self, action: #selector(didTap), for: .touchUpInside)
setImage(imageName: imageName)
self.tintColor = tintColor
@ -79,41 +59,6 @@ public class OWSButton: UIButton {
fatalError("init(coder:) has not been implemented")
}
private func applyDownState() {
guard let downStateView = downStateView else {
return
}
downStateView.isHidden = !isDown
}
private var downGestureRecognizer: DownStateGestureRecognizer?
private func ensureDownGestureRecognizer() {
if downGestureRecognizer != nil {
return
}
let downGestureRecognizer = DownStateGestureRecognizer { [weak self] in
guard let self = self,
let downGestureRecognizer = self.downGestureRecognizer else {
return
}
self.isDown = downGestureRecognizer.hasValidTouch
}
addGestureRecognizer(downGestureRecognizer)
self.downGestureRecognizer = downGestureRecognizer
}
private var tapGestureRecognizer: UIGestureRecognizer?
private func ensureTapGestureRecognizer() {
if tapGestureRecognizer != nil {
return
}
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(didTap))
addGestureRecognizer(tapGestureRecognizer)
self.tapGestureRecognizer = tapGestureRecognizer
}
// MARK: - Common Style Reuse
@objc
@ -162,109 +107,3 @@ public class OWSButton: UIButton {
block()
}
}
// MARK: -
// A GR used by OWSButton to detect "down state".
//
// This GR is unusual; it must not interfere with its views other GRs.
// It can't "recognize" without blocking other GRs from recognizing.
// Therefore it never changes its own state. It uses a callback block
// to notify its view of changes to down state.
//
// A button must be tapped. This GR is a bit more permissive than that.
// It will remain down for a long press even though that isn't a valid tap.
// Although it is not "down" if a long press leaves the views bounds, it
// will re-enter "down" if the long press re-enters the views bounds.
// This gives a sense of physicality to the the button.
public class DownStateGestureRecognizer: UIGestureRecognizer {
public typealias Callback = () -> Void
private final let callback: Callback
public required init(callback: @escaping Callback) {
self.callback = callback
super.init(target: nil, action: nil)
}
public var hasValidTouch = false {
didSet {
if hasValidTouch != oldValue {
callback()
}
}
}
@objc
public override func canPrevent(_ preventedGestureRecognizer: UIGestureRecognizer) -> Bool {
return false
}
@objc
public override func canBePrevented(by preventingGestureRecognizer: UIGestureRecognizer) -> Bool {
return false
}
@objc
public override func shouldRequireFailure(of otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return false
}
@objc
public override func shouldBeRequiredToFail(by otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return false
}
@objc
public override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
handle(event: event)
}
@objc
public override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
handle(event: event)
}
@objc
public override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
handle(event: event)
}
@objc
public override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) {
handle(event: event)
}
private func handle(event: UIEvent) {
// Consider the button down if there is a a single active
// touch within the views bounds.
self.hasValidTouch = { () -> Bool in
guard let allTouches = event.allTouches,
allTouches.count == 1,
let touch = allTouches.first else {
return false
}
guard let view = self.view else {
return false
}
let location = touch.location(in: view)
guard view.bounds.contains(location) else {
return false
}
switch touch.phase {
case .began, .moved, .stationary:
break
case .ended, .cancelled:
return false
case .regionEntered, .regionMoved, .regionExited:
return false
@unknown default:
return false
}
return true
}()
}
}