mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
275 lines
13 KiB
Swift
275 lines
13 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import Display
|
|
import AsyncDisplayKit
|
|
import SwiftSignalKit
|
|
import ContextUI
|
|
import TelegramPresentationData
|
|
import AnimatedCountLabelNode
|
|
|
|
public final class SliderContextItem: 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, 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 SliderContextItemNode(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 SliderContextItemNode: ASDisplayNode, ContextMenuCustomNode, UIGestureRecognizerDelegate {
|
|
private var presentationData: PresentationData
|
|
|
|
private(set) var vibrancyEffectView: UIVisualEffectView?
|
|
private let backgroundTextNode: ImmediateAnimatedCountLabelNode
|
|
private let dimBackgroundTextNode: ImmediateAnimatedCountLabelNode
|
|
|
|
private let foregroundNode: ASDisplayNode
|
|
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.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.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.backgroundTextNode)
|
|
self.addSubnode(self.dimBackgroundTextNode)
|
|
self.addSubnode(self.foregroundNode)
|
|
self.foregroundNode.addSubnode(self.foregroundTextNode)
|
|
|
|
let stringValue = "1.0x"
|
|
|
|
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()
|
|
|
|
self.view.disablesInteractiveTransitionGestureRecognizer = true
|
|
|
|
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(_:)))
|
|
panGestureRecognizer.delegate = self
|
|
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 - self.minValue) / range
|
|
transition.updateFrameAdditive(node: self.foregroundNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: value * width, height: self.frame.height)))
|
|
|
|
let stringValue = String(format: "%.1fx", self.value)
|
|
|
|
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)
|
|
}
|
|
|
|
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)
|
|
|
|
self.updateValue(transition: transition)
|
|
})
|
|
}
|
|
|
|
@objc private func panGesture(_ gestureRecognizer: UIPanGestureRecognizer) {
|
|
let range = self.maxValue - self.minValue
|
|
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 * range
|
|
self.value = max(self.minValue, min(self.maxValue, self.value + delta))
|
|
gestureRecognizer.setTranslation(CGPoint(), in: gestureRecognizer.view)
|
|
|
|
if self.value == 2.0 && previousValue != 2.0 {
|
|
self.hapticFeedback.impact(.soft)
|
|
} else if self.value == 1.0 && previousValue != 1.0 {
|
|
self.hapticFeedback.impact(.soft)
|
|
} else if self.value == 2.5 && previousValue != 2.5 {
|
|
self.hapticFeedback.impact(.soft)
|
|
} else if self.value == 0.05 && previousValue != 0.05 {
|
|
self.hapticFeedback.impact(.soft)
|
|
}
|
|
if abs(previousValue - self.value) >= 0.001 {
|
|
self.valueChanged(self.value, false)
|
|
}
|
|
case .ended:
|
|
let translation: CGFloat = gestureRecognizer.translation(in: gestureRecognizer.view).x
|
|
let delta = translation / self.bounds.width * range
|
|
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 range = self.maxValue - self.minValue
|
|
let location = gestureRecognizer.location(in: gestureRecognizer.view)
|
|
self.value = max(self.minValue, min(self.maxValue, self.minValue + location.x / self.bounds.width * range))
|
|
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
|
|
}
|
|
}
|