Merge commit '7621e2f8dec938cf48181c8b10afc9b01f444e68' into beta

This commit is contained in:
Ilya Laktyushin
2025-12-06 02:17:48 +04:00
commit 8344b97e03
28070 changed files with 7995182 additions and 0 deletions
@@ -0,0 +1,23 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "VolumeSliderContextItem",
module_name = "VolumeSliderContextItem",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
"//submodules/Display:Display",
"//submodules/ContextUI:ContextUI",
"//submodules/TelegramPresentationData:TelegramPresentationData",
"//submodules/AnimatedCountLabelNode:AnimatedCountLabelNode",
],
visibility = [
"//visibility:public",
],
)
@@ -0,0 +1,382 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
private final class VoiceChatSpeakerNodeDrawingState: NSObject {
let color: UIColor
let transition: CGFloat
let reverse: Bool
init(color: UIColor, transition: CGFloat, reverse: Bool) {
self.color = color
self.transition = transition
self.reverse = reverse
super.init()
}
}
private func generateWaveImage(color: UIColor, num: Int) -> UIImage? {
return generateImage(CGSize(width: 36.0, height: 36.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setStrokeColor(color.cgColor)
context.setLineWidth(1.0 + UIScreenPixel)
context.setLineCap(.round)
context.translateBy(x: 6.0, y: 6.0)
switch num {
case 1:
let _ = try? drawSvgPath(context, path: "M15,9 C15.6666667,9.95023099 16,10.9487504 16,11.9955581 C16,13.0423659 15.6666667,14.0438465 15,15 S ")
case 2:
let _ = try? drawSvgPath(context, path: "M17.5,6.5 C18.8724771,8.24209014 19.5587156,10.072709 19.5587156,11.9918565 C19.5587156,13.9110041 18.8724771,15.7470519 17.5,17.5 S ")
case 3:
let _ = try? drawSvgPath(context, path: "M20,3.5 C22,6.19232113 23,9.02145934 23,11.9874146 C23,14.9533699 22,17.7908984 20,20.5 S ")
default:
break
}
})
}
final class VoiceChatSpeakerNode: ASDisplayNode {
class State: Equatable {
enum Value: Equatable {
case muted
case low
case medium
case high
}
let value: Value
let color: UIColor
init(value: Value, color: UIColor) {
self.value = value
self.color = color
}
static func ==(lhs: State, rhs: State) -> Bool {
if lhs.value != rhs.value {
return false
}
if lhs.color.argb != rhs.color.argb {
return false
}
return true
}
}
private var hasState = false
private var state: State = State(value: .medium, color: .black)
private let iconNode: IconNode
private let waveNode1: ASImageNode
private let waveNode2: ASImageNode
private let waveNode3: ASImageNode
override init() {
self.iconNode = IconNode()
self.waveNode1 = ASImageNode()
self.waveNode1.displaysAsynchronously = false
self.waveNode1.displayWithoutProcessing = true
self.waveNode2 = ASImageNode()
self.waveNode2.displaysAsynchronously = false
self.waveNode2.displayWithoutProcessing = true
self.waveNode3 = ASImageNode()
self.waveNode3.displaysAsynchronously = false
self.waveNode3.displayWithoutProcessing = true
super.init()
self.addSubnode(self.iconNode)
self.addSubnode(self.waveNode1)
self.addSubnode(self.waveNode2)
self.addSubnode(self.waveNode3)
}
private var animating = false
func update(state: State, animated: Bool, force: Bool = false) {
var animated = animated
if !self.hasState {
self.hasState = true
animated = false
}
if self.state != state || force {
let previousState = self.state
self.state = state
if animated && self.animating {
return
}
if previousState.color != state.color {
self.waveNode1.image = generateWaveImage(color: state.color, num: 1)
self.waveNode2.image = generateWaveImage(color: state.color, num: 2)
self.waveNode3.image = generateWaveImage(color: state.color, num: 3)
}
self.update(transition: animated ? .animated(duration: 0.2, curve: .easeInOut) : .immediate, completion: {
if self.state != state {
self.update(state: self.state, animated: animated, force: true)
}
})
}
}
private func update(transition: ContainedViewLayoutTransition, completion: @escaping () -> Void = {}) {
self.animating = transition.isAnimated
self.iconNode.update(state: IconNode.State(muted: self.state.value == .muted, color: self.state.color), animated: transition.isAnimated)
let bounds = self.bounds
let center = CGPoint(x: bounds.width / 2.0, y: bounds.height / 2.0)
self.iconNode.bounds = CGRect(origin: CGPoint(), size: bounds.size)
self.waveNode1.bounds = CGRect(origin: CGPoint(), size: bounds.size)
self.waveNode2.bounds = CGRect(origin: CGPoint(), size: bounds.size)
self.waveNode3.bounds = CGRect(origin: CGPoint(), size: bounds.size)
let iconPosition: CGPoint
let wave1Position: CGPoint
var wave1Alpha: CGFloat = 1.0
let wave2Position: CGPoint
var wave2Alpha: CGFloat = 1.0
let wave3Position: CGPoint
var wave3Alpha: CGFloat = 1.0
switch self.state.value {
case .muted:
iconPosition = CGPoint(x: center.x, y: center.y)
wave1Position = CGPoint(x: center.x + 4.0, y: center.y)
wave2Position = CGPoint(x: center.x + 4.0, y: center.y)
wave3Position = CGPoint(x: center.x + 4.0, y: center.y)
wave1Alpha = 0.0
wave2Alpha = 0.0
wave3Alpha = 0.0
case .low:
iconPosition = CGPoint(x: center.x - 1.0, y: center.y)
wave1Position = CGPoint(x: center.x + 3.0, y: center.y)
wave2Position = CGPoint(x: center.x + 3.0, y: center.y)
wave3Position = CGPoint(x: center.x + 3.0, y: center.y)
wave2Alpha = 0.0
wave3Alpha = 0.0
case .medium:
iconPosition = CGPoint(x: center.x - 3.0, y: center.y)
wave1Position = CGPoint(x: center.x + 1.0, y: center.y)
wave2Position = CGPoint(x: center.x + 1.0, y: center.y)
wave3Position = CGPoint(x: center.x + 1.0, y: center.y)
wave3Alpha = 0.0
case .high:
iconPosition = CGPoint(x: center.x - 4.0, y: center.y)
wave1Position = CGPoint(x: center.x, y: center.y)
wave2Position = CGPoint(x: center.x, y: center.y)
wave3Position = CGPoint(x: center.x, y: center.y)
}
transition.updatePosition(node: self.iconNode, position: iconPosition) { _ in
self.animating = false
completion()
}
transition.updatePosition(node: self.waveNode1, position: wave1Position)
transition.updatePosition(node: self.waveNode2, position: wave2Position)
transition.updatePosition(node: self.waveNode3, position: wave3Position)
transition.updateAlpha(node: self.waveNode1, alpha: wave1Alpha)
transition.updateAlpha(node: self.waveNode2, alpha: wave2Alpha)
transition.updateAlpha(node: self.waveNode3, alpha: wave3Alpha)
}
}
private class IconNode: ASDisplayNode {
class State: Equatable {
let muted: Bool
let color: UIColor
init(muted: Bool, color: UIColor) {
self.muted = muted
self.color = color
}
static func ==(lhs: State, rhs: State) -> Bool {
if lhs.muted != rhs.muted {
return false
}
if lhs.color.argb != rhs.color.argb {
return false
}
return true
}
}
private class TransitionContext {
let startTime: Double
let duration: Double
let previousState: State
init(startTime: Double, duration: Double, previousState: State) {
self.startTime = startTime
self.duration = duration
self.previousState = previousState
}
}
private var animator: ConstantDisplayLinkAnimator?
private var hasState = false
private var state: State = State(muted: false, color: .black)
private var transitionContext: TransitionContext?
override init() {
super.init()
self.isOpaque = false
}
func update(state: State, animated: Bool) {
var animated = animated
if !self.hasState {
self.hasState = true
animated = false
}
if self.state != state {
let previousState = self.state
self.state = state
if animated {
self.transitionContext = TransitionContext(startTime: CACurrentMediaTime(), duration: 0.18, previousState: previousState)
}
self.updateAnimations()
self.setNeedsDisplay()
}
}
private func updateAnimations() {
var animate = false
let timestamp = CACurrentMediaTime()
if let transitionContext = self.transitionContext {
if transitionContext.startTime + transitionContext.duration < timestamp {
self.transitionContext = nil
} else {
animate = true
}
}
if animate {
let animator: ConstantDisplayLinkAnimator
if let current = self.animator {
animator = current
} else {
animator = ConstantDisplayLinkAnimator(update: { [weak self] in
self?.updateAnimations()
})
self.animator = animator
}
animator.isPaused = false
} else {
self.animator?.isPaused = true
}
self.setNeedsDisplay()
}
override public func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? {
var transitionFraction: CGFloat = self.state.muted ? 1.0 : 0.0
var color = self.state.color
var reverse = false
if let transitionContext = self.transitionContext {
let timestamp = CACurrentMediaTime()
var t = CGFloat((timestamp - transitionContext.startTime) / transitionContext.duration)
t = min(1.0, max(0.0, t))
if transitionContext.previousState.muted != self.state.muted {
transitionFraction = self.state.muted ? t : 1.0 - t
reverse = transitionContext.previousState.muted
}
if transitionContext.previousState.color.rgb != color.rgb {
color = transitionContext.previousState.color.interpolateTo(color, fraction: t)!
}
}
return VoiceChatSpeakerNodeDrawingState(color: color, transition: transitionFraction, reverse: reverse)
}
@objc override public class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) {
let context = UIGraphicsGetCurrentContext()!
if !isRasterizing {
context.setBlendMode(.copy)
context.setFillColor(UIColor.clear.cgColor)
context.fill(bounds)
}
guard let parameters = parameters as? VoiceChatSpeakerNodeDrawingState else {
return
}
let clearLineWidth: CGFloat = 4.0
let lineWidth: CGFloat = 1.0 + UIScreenPixel
context.setFillColor(parameters.color.cgColor)
context.setStrokeColor(parameters.color.cgColor)
context.setLineWidth(lineWidth)
context.translateBy(x: 7.0, y: 6.0)
let _ = try? drawSvgPath(context, path: "M7,9 L10,9 L13.6080479,5.03114726 C13.9052535,4.70422117 14.4112121,4.6801279 14.7381382,4.97733344 C14.9049178,5.12895118 15,5.34388952 15,5.5692855 L15,18.4307145 C15,18.8725423 14.6418278,19.2307145 14.2,19.2307145 C13.974604,19.2307145 13.7596657,19.1356323 13.6080479,18.9688527 L10,15 L7,15 C6.44771525,15 6,14.5522847 6,14 L6,10 C6,9.44771525 6.44771525,9 7,9 S ")
context.translateBy(x: -7.0, y: -6.0)
if parameters.transition > 0.0 {
let startPoint: CGPoint
let endPoint: CGPoint
let origin: CGPoint
let length: CGFloat
if bounds.width > 30.0 {
origin = CGPoint(x: 9.0, y: 10.0 - UIScreenPixel)
length = 17.0
} else {
origin = CGPoint(x: 5.0 + UIScreenPixel, y: 4.0 + UIScreenPixel)
length = 15.0
}
if parameters.reverse {
startPoint = CGPoint(x: origin.x + length * (1.0 - parameters.transition), y: origin.y + length * (1.0 - parameters.transition))
endPoint = CGPoint(x: origin.x + length, y: origin.y + length)
} else {
startPoint = origin
endPoint = CGPoint(x: origin.x + length * parameters.transition, y: origin.y + length * parameters.transition)
}
context.setBlendMode(.clear)
context.setLineWidth(clearLineWidth)
context.move(to: startPoint)
context.addLine(to: endPoint)
context.strokePath()
context.setBlendMode(.normal)
context.setStrokeColor(parameters.color.cgColor)
context.setLineWidth(lineWidth)
context.setLineCap(.round)
context.setLineJoin(.round)
context.move(to: startPoint)
context.addLine(to: endPoint)
context.strokePath()
}
}
}
@@ -0,0 +1,307 @@
import Foundation
import UIKit
import Display
import AsyncDisplayKit
import SwiftSignalKit
import TelegramPresentationData
import AppBundle
import ContextUI
import AnimatedCountLabelNode
public final class VolumeSliderContextItem: ContextMenuCustomItem {
private let minValue: CGFloat
private let maxValue: CGFloat
private let value: CGFloat
private let valueChanged: (CGFloat, Bool) -> Void
public init(minValue: CGFloat, maxValue: CGFloat = 1.0, value: CGFloat, valueChanged: @escaping (CGFloat, Bool) -> Void) {
self.minValue = minValue
self.maxValue = maxValue
self.value = value
self.valueChanged = valueChanged
}
public func node(presentationData: PresentationData, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) -> ContextMenuCustomNode {
return VolumeSliderContextItemNode(presentationData: presentationData, getController: getController, minValue: self.minValue, maxValue: self.maxValue, value: self.value, valueChanged: self.valueChanged)
}
}
private let textFont = Font.with(size: 17.0, design: .regular, traits: .monospacedNumbers)
private final class VolumeSliderContextItemNode: ASDisplayNode, ContextMenuCustomNode {
private var presentationData: PresentationData
private(set) var vibrancyEffectView: UIVisualEffectView?
private let backgroundIconNode: VoiceChatSpeakerNode
private let backgroundTextNode: ImmediateAnimatedCountLabelNode
private let dimBackgroundTextNode: ImmediateAnimatedCountLabelNode
private let foregroundNode: ASDisplayNode
private let foregroundIconNode: VoiceChatSpeakerNode
private let foregroundTextNode: ImmediateAnimatedCountLabelNode
let minValue: CGFloat
let maxValue: CGFloat
var value: CGFloat = 1.0 {
didSet {
self.updateValue(transition: .animated(duration: 0.2, curve: .spring))
}
}
private let valueChanged: (CGFloat, Bool) -> Void
private let hapticFeedback = HapticFeedback()
init(presentationData: PresentationData, getController: @escaping () -> ContextControllerProtocol?, minValue: CGFloat, maxValue: CGFloat, value: CGFloat, valueChanged: @escaping (CGFloat, Bool) -> Void) {
self.presentationData = presentationData
self.minValue = minValue
self.maxValue = maxValue
self.value = value
self.valueChanged = valueChanged
self.backgroundIconNode = VoiceChatSpeakerNode()
self.backgroundTextNode = ImmediateAnimatedCountLabelNode()
self.backgroundTextNode.alwaysOneDirection = true
self.dimBackgroundTextNode = ImmediateAnimatedCountLabelNode()
self.dimBackgroundTextNode.alwaysOneDirection = true
self.foregroundNode = ASDisplayNode()
self.foregroundNode.clipsToBounds = true
self.foregroundNode.isAccessibilityElement = false
self.foregroundNode.backgroundColor = UIColor(rgb: 0xffffff)
self.foregroundNode.isUserInteractionEnabled = false
self.foregroundIconNode = VoiceChatSpeakerNode()
self.foregroundTextNode = ImmediateAnimatedCountLabelNode()
self.foregroundTextNode.alwaysOneDirection = true
super.init()
self.isUserInteractionEnabled = true
if presentationData.theme.overallDarkAppearance {
} else {
let style: UIBlurEffect.Style
style = .extraLight
let blurEffect = UIBlurEffect(style: style)
let vibrancyEffect = UIVibrancyEffect(blurEffect: blurEffect)
let vibrancyEffectView = UIVisualEffectView(effect: vibrancyEffect)
self.vibrancyEffectView = vibrancyEffectView
}
self.addSubnode(self.backgroundIconNode)
self.addSubnode(self.backgroundTextNode)
self.addSubnode(self.dimBackgroundTextNode)
self.addSubnode(self.foregroundNode)
self.foregroundNode.addSubnode(self.foregroundIconNode)
self.foregroundNode.addSubnode(self.foregroundTextNode)
let stringValue = "100%"
let dimBackgroundTextColor = self.vibrancyEffectView != nil ? UIColor(white: 0.0, alpha: 0.15) : .clear
let backgroundTextColor = self.vibrancyEffectView != nil ? UIColor(white: 1.0, alpha: 0.7) : self.presentationData.theme.contextMenu.secondaryColor
let foregroundTextColor = UIColor.black
var dimBackgroundSegments: [AnimatedCountLabelNode.Segment] = []
var backgroundSegments: [AnimatedCountLabelNode.Segment] = []
var foregroundSegments: [AnimatedCountLabelNode.Segment] = []
var textCount = 0
for char in stringValue {
if let intValue = Int(String(char)) {
dimBackgroundSegments.append(.number(intValue, NSAttributedString(string: String(char), font: textFont, textColor: dimBackgroundTextColor)))
backgroundSegments.append(.number(intValue, NSAttributedString(string: String(char), font: textFont, textColor: backgroundTextColor)))
foregroundSegments.append(.number(intValue, NSAttributedString(string: String(char), font: textFont, textColor: foregroundTextColor)))
} else {
dimBackgroundSegments.append(.text(textCount, NSAttributedString(string: String(char), font: textFont, textColor: dimBackgroundTextColor)))
backgroundSegments.append(.text(textCount, NSAttributedString(string: String(char), font: textFont, textColor: backgroundTextColor)))
foregroundSegments.append(.text(textCount, NSAttributedString(string: String(char), font: textFont, textColor: foregroundTextColor)))
textCount += 1
}
}
self.dimBackgroundTextNode.segments = dimBackgroundSegments
self.backgroundTextNode.segments = backgroundSegments
self.foregroundTextNode.segments = foregroundSegments
}
override func didLoad() {
super.didLoad()
if let vibrancyEffectView = self.vibrancyEffectView {
Queue.mainQueue().after(0.05) {
if let effectNode = findEffectNode(node: self.supernode) {
effectNode.effectView?.contentView.insertSubview(vibrancyEffectView, at: 0)
vibrancyEffectView.contentView.addSubnode(self.backgroundTextNode)
}
}
}
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:)))
self.view.addGestureRecognizer(panGestureRecognizer)
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))
self.view.addGestureRecognizer(tapGestureRecognizer)
}
func updateTheme(presentationData: PresentationData) {
self.presentationData = presentationData
self.updateValue()
}
private func updateValue(transition: ContainedViewLayoutTransition = .immediate) {
let width = self.frame.width
let range = self.maxValue - self.minValue
let value = self.value
transition.updateFrameAdditive(node: self.foregroundNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: value / range * width, height: self.frame.height)))
let stringValue = "\(Int(self.value * 100.0))%"
let dimBackgroundTextColor = self.vibrancyEffectView != nil ? UIColor(white: 0.0, alpha: 0.15) : .clear
let backgroundTextColor = self.vibrancyEffectView != nil ? UIColor(white: 1.0, alpha: 0.7) : self.presentationData.theme.contextMenu.secondaryColor
let foregroundTextColor = UIColor.black
var dimBackgroundSegments: [AnimatedCountLabelNode.Segment] = []
var backgroundSegments: [AnimatedCountLabelNode.Segment] = []
var foregroundSegments: [AnimatedCountLabelNode.Segment] = []
var textCount = 0
for char in stringValue {
if let intValue = Int(String(char)) {
dimBackgroundSegments.append(.number(intValue, NSAttributedString(string: String(char), font: textFont, textColor: dimBackgroundTextColor)))
backgroundSegments.append(.number(intValue, NSAttributedString(string: String(char), font: textFont, textColor: backgroundTextColor)))
foregroundSegments.append(.number(intValue, NSAttributedString(string: String(char), font: textFont, textColor: foregroundTextColor)))
} else {
dimBackgroundSegments.append(.text(textCount, NSAttributedString(string: String(char), font: textFont, textColor: dimBackgroundTextColor)))
backgroundSegments.append(.text(textCount, NSAttributedString(string: String(char), font: textFont, textColor: backgroundTextColor)))
foregroundSegments.append(.text(textCount, NSAttributedString(string: String(char), font: textFont, textColor: foregroundTextColor)))
textCount += 1
}
}
self.dimBackgroundTextNode.segments = dimBackgroundSegments
self.backgroundTextNode.segments = backgroundSegments
self.foregroundTextNode.segments = foregroundSegments
let _ = self.dimBackgroundTextNode.updateLayout(size: CGSize(width: 70.0, height: .greatestFiniteMagnitude), animated: transition.isAnimated)
let _ = self.backgroundTextNode.updateLayout(size: CGSize(width: 70.0, height: .greatestFiniteMagnitude), animated: transition.isAnimated)
let _ = self.foregroundTextNode.updateLayout(size: CGSize(width: 70.0, height: .greatestFiniteMagnitude), animated: transition.isAnimated)
let iconValue: VoiceChatSpeakerNode.State.Value
if value == 0.0 {
iconValue = .muted
} else if value < 0.33 {
iconValue = .low
} else if value < 0.66 {
iconValue = .medium
} else {
iconValue = .high
}
self.backgroundIconNode.update(state: VoiceChatSpeakerNode.State(value: iconValue, color: UIColor(rgb: 0xffffff)), animated: true)
self.foregroundIconNode.update(state: VoiceChatSpeakerNode.State(value: iconValue, color: UIColor(rgb: 0x000000)), animated: true)
}
func updateLayout(constrainedWidth: CGFloat, constrainedHeight: CGFloat) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void) {
let valueWidth: CGFloat = 70.0
let height: CGFloat = 45.0
var backgroundTextSize = self.backgroundTextNode.updateLayout(size: CGSize(width: 70.0, height: .greatestFiniteMagnitude), animated: true)
backgroundTextSize.width = valueWidth
return (CGSize(width: height * 3.0, height: height), { size, transition in
let leftInset: CGFloat = 17.0
self.vibrancyEffectView?.frame = CGRect(origin: .zero, size: size)
let textFrame = CGRect(origin: CGPoint(x: leftInset, y: floor((height - backgroundTextSize.height) / 2.0)), size: backgroundTextSize)
transition.updateFrameAdditive(node: self.dimBackgroundTextNode, frame: textFrame)
transition.updateFrameAdditive(node: self.backgroundTextNode, frame: textFrame)
transition.updateFrameAdditive(node: self.foregroundTextNode, frame: textFrame)
let iconSize = CGSize(width: 36.0, height: 36.0)
let iconFrame = CGRect(origin: CGPoint(x: size.width - iconSize.width - 10.0, y: floor((size.height - iconSize.height) / 2.0)), size: iconSize)
self.backgroundIconNode.frame = iconFrame
self.foregroundIconNode.frame = iconFrame
self.updateValue(transition: transition)
})
}
@objc private func panGesture(_ gestureRecognizer: UIPanGestureRecognizer) {
switch gestureRecognizer.state {
case .began:
break
case .changed:
let previousValue = self.value
let translation: CGFloat = gestureRecognizer.translation(in: gestureRecognizer.view).x
let delta = translation / self.bounds.width
self.value = max(self.minValue, min(self.maxValue, self.value + delta))
gestureRecognizer.setTranslation(CGPoint(), in: gestureRecognizer.view)
if self.value == self.maxValue && previousValue != self.maxValue {
self.backgroundIconNode.layer.animateScale(from: 1.0, to: 1.1, duration: 0.16, removeOnCompletion: false, completion: { [weak self] _ in
if let strongSelf = self {
strongSelf.backgroundIconNode.layer.animateScale(from: 1.1, to: 1.0, duration: 0.16)
}
})
self.foregroundIconNode.layer.animateScale(from: 1.0, to: 1.1, duration: 0.16, removeOnCompletion: false, completion: { [weak self] _ in
if let strongSelf = self {
strongSelf.foregroundIconNode.layer.animateScale(from: 1.1, to: 1.0, duration: 0.16)
}
})
self.hapticFeedback.impact(.soft)
} else if self.maxValue != 1.0 && self.value == 1.0 && previousValue != 1.0 {
self.hapticFeedback.impact(.soft)
} else if self.value == 0.0 && previousValue != 0.0 {
self.hapticFeedback.impact(.soft)
}
if abs(previousValue - self.value) >= 0.01 {
self.valueChanged(self.value, false)
}
case .ended:
let translation: CGFloat = gestureRecognizer.translation(in: gestureRecognizer.view).x
let delta = translation / self.bounds.width
self.value = max(self.minValue, min(self.maxValue, self.value + delta))
self.valueChanged(self.value, true)
default:
break
}
}
@objc private func tapGesture(_ gestureRecognizer: UITapGestureRecognizer) {
let location = gestureRecognizer.location(in: gestureRecognizer.view)
self.value = max(self.minValue, min(self.maxValue, location.x / self.bounds.width))
self.valueChanged(self.value, true)
}
func canBeHighlighted() -> Bool {
return false
}
func updateIsHighlighted(isHighlighted: Bool) {
}
func performAction() {
}
}
private func findEffectNode(node: ASDisplayNode?) -> NavigationBackgroundNode? {
if let node = node {
if let subnodes = node.subnodes {
for node in subnodes {
if let effectNode = node as? NavigationBackgroundNode {
return effectNode
}
}
}
return findEffectNode(node: node.supernode)
} else {
return nil
}
}