Swiftgram/submodules/TelegramUI/Components/CameraScreen/Sources/FlashTintControlComponent.swift
Ilya Laktyushin fc8b77d946 Update scheme
2023-10-17 13:52:47 +04:00

434 lines
18 KiB
Swift

import Foundation
import UIKit
import Display
import ComponentFlow
import RoundedRectWithTailPath
private final class FlashColorComponent: Component {
let tint: CameraState.FlashTint?
let isSelected: Bool
let action: () -> Void
init(
tint: CameraState.FlashTint?,
isSelected: Bool,
action: @escaping () -> Void
) {
self.tint = tint
self.isSelected = isSelected
self.action = action
}
static func == (lhs: FlashColorComponent, rhs: FlashColorComponent) -> Bool {
return lhs.tint == rhs.tint && lhs.isSelected == rhs.isSelected
}
final class View: UIButton {
private var component: FlashColorComponent?
private var contentView: UIView
private let circleLayer: SimpleShapeLayer
private var ringLayer: CALayer?
private var iconLayer: CALayer?
private var currentIsHighlighted: Bool = false {
didSet {
if self.currentIsHighlighted != oldValue {
self.contentView.alpha = self.currentIsHighlighted ? 0.6 : 1.0
}
}
}
override init(frame: CGRect) {
self.contentView = UIView(frame: CGRect(origin: .zero, size: frame.size))
self.contentView.isUserInteractionEnabled = false
self.circleLayer = SimpleShapeLayer()
super.init(frame: frame)
self.addSubview(self.contentView)
self.contentView.layer.addSublayer(self.circleLayer)
self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc private func pressed() {
self.component?.action()
}
override public func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
self.currentIsHighlighted = true
return super.beginTracking(touch, with: event)
}
override public func endTracking(_ touch: UITouch?, with event: UIEvent?) {
self.currentIsHighlighted = false
super.endTracking(touch, with: event)
}
override public func cancelTracking(with event: UIEvent?) {
self.currentIsHighlighted = false
super.cancelTracking(with: event)
}
func update(component: FlashColorComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
self.component = component
let contentSize = CGSize(width: 30.0, height: 30.0)
self.contentView.frame = CGRect(origin: .zero, size: contentSize)
let bounds = CGRect(origin: .zero, size: contentSize)
self.layer.allowsGroupOpacity = true
self.contentView.layer.allowsGroupOpacity = true
self.circleLayer.frame = bounds
if self.ringLayer == nil {
let ringLayer = SimpleLayer()
ringLayer.backgroundColor = UIColor.clear.cgColor
ringLayer.cornerRadius = contentSize.width / 2.0
ringLayer.borderWidth = 1.0 + UIScreenPixel
ringLayer.frame = CGRect(origin: .zero, size: contentSize)
self.contentView.layer.insertSublayer(ringLayer, at: 0)
self.ringLayer = ringLayer
}
if component.isSelected {
transition.setShapeLayerPath(layer: self.circleLayer, path: CGPath(ellipseIn: bounds.insetBy(dx: 3.0 - UIScreenPixel, dy: 3.0 - UIScreenPixel), transform: nil))
} else {
transition.setShapeLayerPath(layer: self.circleLayer, path: CGPath(ellipseIn: bounds, transform: nil))
}
if let color = component.tint?.color {
self.circleLayer.fillColor = color.cgColor
self.ringLayer?.borderColor = color.cgColor
} else {
if self.iconLayer == nil {
let iconLayer = SimpleLayer()
iconLayer.contents = UIImage(bundleImageName: "Camera/FlashOffIcon")?.cgImage
iconLayer.contentsGravity = .resizeAspect
iconLayer.frame = bounds.insetBy(dx: -4.0, dy: -4.0)
self.contentView.layer.addSublayer(iconLayer)
self.iconLayer = iconLayer
}
self.circleLayer.fillColor = UIColor(rgb: 0xffffff, alpha: 0.1).cgColor
self.ringLayer?.borderColor = UIColor.clear.cgColor
}
return contentSize
}
}
public func makeView() -> View {
return View(frame: CGRect())
}
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}
final class FlashTintControlComponent: Component {
let position: CGPoint
let tint: CameraState.FlashTint
let size: CGFloat
let update: (CameraState.FlashTint?) -> Void
let updateSize: (CGFloat) -> Void
let dismiss: () -> Void
init(
position: CGPoint,
tint: CameraState.FlashTint,
size: CGFloat,
update: @escaping (CameraState.FlashTint?) -> Void,
updateSize: @escaping (CGFloat) -> Void,
dismiss: @escaping () -> Void
) {
self.position = position
self.tint = tint
self.size = size
self.update = update
self.updateSize = updateSize
self.dismiss = dismiss
}
static func == (lhs: FlashTintControlComponent, rhs: FlashTintControlComponent) -> Bool {
return lhs.position == rhs.position && lhs.tint == rhs.tint && lhs.size == rhs.size
}
final class View: UIButton {
private var component: FlashTintControlComponent?
private let dismissView = UIView()
private let containerView = UIView()
private let effectView: UIVisualEffectView
private let maskLayer = CAShapeLayer()
private let swatches = ComponentView<Empty>()
private let sliderView: SliderView
override init(frame: CGRect) {
self.effectView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
var sizeUpdateImpl: ((CGFloat) -> Void)?
self.sliderView = SliderView(minValue: 0.0, maxValue: 1.0, value: 1.0, valueChanged: { value , _ in
sizeUpdateImpl?(value)
})
super.init(frame: frame)
self.containerView.layer.anchorPoint = CGPoint(x: 0.8, y: 0.0)
self.addSubview(self.dismissView)
self.addSubview(self.containerView)
self.containerView.addSubview(self.effectView)
self.containerView.addSubview(self.sliderView)
self.dismissView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dismissTapped)))
sizeUpdateImpl = { [weak self] size in
if let self, let component {
component.updateSize(size)
}
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc private func dismissTapped() {
self.component?.dismiss()
}
func update(component: FlashTintControlComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
let isFirstTime = self.component == nil
self.component = component
let size = CGSize(width: 184.0, height: 92.0)
self.sliderView.frame = CGRect(origin: CGPoint(x: 8.0, y: size.height - 38.0), size: CGSize(width: size.width - 16.0, height: 30.0))
if isFirstTime {
self.sliderView.value = component.size
self.maskLayer.path = generateRoundedRectWithTailPath(rectSize: size, cornerRadius: 10.0, tailSize: CGSize(width: 18, height: 7.0), tailRadius: 1.0, tailPosition: 0.8, transformTail: false).cgPath
self.maskLayer.frame = CGRect(origin: .zero, size: CGSize(width: size.width, height: size.height + 7.0))
self.effectView.layer.mask = self.maskLayer
}
let swatchesSize = self.swatches.update(
transition: transition,
component: AnyComponent(
HStack(
[
AnyComponentWithIdentity(
id: "off",
component: AnyComponent(
FlashColorComponent(
tint: nil,
isSelected: false,
action: {
component.update(nil)
component.dismiss()
}
)
)
),
AnyComponentWithIdentity(
id: "white",
component: AnyComponent(
FlashColorComponent(
tint: .white,
isSelected: component.tint == .white,
action: {
component.update(.white)
}
)
)
),
AnyComponentWithIdentity(
id: "yellow",
component: AnyComponent(
FlashColorComponent(
tint: .yellow,
isSelected: component.tint == .yellow,
action: {
component.update(.yellow)
}
)
)
),
AnyComponentWithIdentity(
id: "blue",
component: AnyComponent(
FlashColorComponent(
tint: .blue,
isSelected: component.tint == .blue,
action: {
component.update(.blue)
}
)
)
)
],
spacing: 16.0
)
),
environment: {},
containerSize: availableSize
)
if let view = self.swatches.view {
if view.superview == nil {
self.containerView.addSubview(view)
}
view.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - swatchesSize.width) / 2.0), y: 8.0), size: swatchesSize)
}
self.dismissView.frame = CGRect(origin: .zero, size: availableSize)
self.containerView.bounds = CGRect(origin: .zero, size: size)
self.containerView.center = component.position
self.effectView.frame = CGRect(origin: CGPoint(x: 0.0, y: -7.0), size: CGSize(width: size.width, height: size.height + 7.0))
if isFirstTime {
self.containerView.layer.animateScale(from: 0.0, to: 1.0, duration: 0.35, timingFunction: kCAMediaTimingFunctionSpring)
self.containerView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
return availableSize
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if !self.containerView.frame.contains(point) {
return self.dismissView
}
return super.hitTest(point, with: event)
}
}
public func makeView() -> View {
return View(frame: CGRect())
}
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}
private final class SliderView: UIView {
private let foregroundView: UIView
private let knobView: UIImageView
let minValue: CGFloat
let maxValue: CGFloat
var value: CGFloat = 1.0 {
didSet {
self.updateValue()
}
}
private let valueChanged: (CGFloat, Bool) -> Void
private let hapticFeedback = HapticFeedback()
init(minValue: CGFloat, maxValue: CGFloat, value: CGFloat, valueChanged: @escaping (CGFloat, Bool) -> Void) {
self.minValue = minValue
self.maxValue = maxValue
self.value = value
self.valueChanged = valueChanged
self.foregroundView = UIView()
self.foregroundView.backgroundColor = UIColor(rgb: 0x8b8b8a)
self.knobView = UIImageView(image: generateFilledCircleImage(diameter: 30.0, color: .white))
super.init(frame: .zero)
self.backgroundColor = UIColor(rgb: 0x3e3e3e)
self.clipsToBounds = true
self.layer.cornerRadius = 15.0
self.foregroundView.isUserInteractionEnabled = false
self.knobView.isUserInteractionEnabled = false
self.addSubview(self.foregroundView)
self.addSubview(self.knobView)
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:)))
self.addGestureRecognizer(panGestureRecognizer)
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))
self.addGestureRecognizer(tapGestureRecognizer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func updateValue(transition: Transition = .immediate) {
let width = self.frame.width
let range = self.maxValue - self.minValue
let value = (self.value - self.minValue) / range
transition.setFrame(view: self.foregroundView, frame: CGRect(origin: CGPoint(), size: CGSize(width: 15.0 + value * (width - 30.0), height: 30.0)))
transition.setFrame(view: self.knobView, frame: CGRect(origin: CGPoint(x: (width - 30.0) * value, y: 0.0), size: CGSize(width: 30.0, height: 30.0)))
}
@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 == 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.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() {
}
}