/
sberpay
/
AgeGate-iOS
Обзор
Документация
Войти
/
sberpay
/
AgeGate-iOS
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
develop
AgeGateSDK/KeyGen/Generation/AGTKeyGenVC.swift
186 строк
7 KB
SergeyGladkiy
[0.2.0] БДСЧ сценарий
30 июл 2026, 21:57
30 июл 2026, 21:57
cbcdfcb
Код
Авторство
О чём код?
// // AGTKeyGenVC.swift // AgeGateSDK // // Created by Гладкий Сергей Игоревич on 24.07.2026. // import UIKit @MainActor protocol AGTKeyGenViewing: AnyObject { func setProgress(_ value: Double, animated: Bool) func setHint(_ text: String) func acceptTap(at point: CGPoint) func rejectTap(at point: CGPoint) func setDismissEnabled(_ value: Bool) } /// Экраны «БДСЧ 0% / 38% / 89%». На всю высоту шторки, сплошная заливка. /// Поверх основного фона лежит слой посветлее, который поднимается вверх /// по мере роста процента. Крупный процент — в правом нижнем углу. final class AGTKeyGenVC: AGTContentVC, AGTKeyGenViewing { private let presenter: AGTKeyGenPresenting private let feedback = UIImpactFeedbackGenerator(style: .light) /// Слой прогресса. Его верхняя граница едет вверх: 0% — у самого низа, /// 100% — под верхом экрана. private lazy var fillView: UIView = { let view = UIView() view.backgroundColor = .entropyFill view.translatesAutoresizingMaskIntoConstraints = false return view }() private var fillHeightConstraint: NSLayoutConstraint? private lazy var percentLabel: UILabel = { let view = UILabel() view.font = .entropyPercent // SB Sans Display semibold 150 view.textColor = Asset.Palette.secondGrayDisabled.color view.text = "0%" view.numberOfLines = 1 view.textAlignment = .right view.adjustsFontSizeToFitWidth = true // подстраховка на узких экранах view.minimumScaleFactor = 0.6 view.translatesAutoresizingMaskIntoConstraints = false return view }() private lazy var hintLabel: UILabel = { let view = UILabel() view.font = .entropyHint // SB Sans Display semibold 19 view.textColor = Asset.Palette.secondGrayDisabled.color view.numberOfLines = 4 // максимум 4 строки view.textAlignment = .center view.translatesAutoresizingMaskIntoConstraints = false return view }() init(presenter: AGTKeyGenPresenting) { self.presenter = presenter super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() setupUI() feedback.prepare() presenter.viewDidLoad() } /// Прячем стик и общий фон контейнера: этот экран занимает всю шторку. override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) setStickHidden(true) contentContainer?.setBackgroundHidden(true) } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) contentContainer?.setBackgroundHidden(false) } /// Касания ловим здесь: нужны координаты, сила и точное время нажатия. override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { super.touchesBegan(touches, with: event) guard let touch = touches.first else { return } presenter.didTap(at: touch.location(in: view), force: touch.maximumPossibleForce > 0 ? touch.force : 0, time: CACurrentMediaTime()) } // MARK: - AGTKeyGenViewing func setProgress(_ value: Double, animated: Bool) { percentLabel.text = "\(Int(value * 100))%" /// Высота слоя прогресса = доля от высоты экрана. fillHeightConstraint?.constant = view.bounds.height * CGFloat(value) guard animated else { view.layoutIfNeeded() return } UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.85, initialSpringVelocity: 0.4) { self.view.layoutIfNeeded() } UIView.animate(withDuration: 0.12) { self.percentLabel.transform = .init(scaleX: 1.05, y: 1.05) } completion: { _ in UIView.animate(withDuration: 0.12) { self.percentLabel.transform = .identity } } } func setHint(_ text: String) { guard hintLabel.text != text else { return } UIView.transition(with: hintLabel, duration: 0.2, options: .transitionCrossDissolve) { self.hintLabel.text = text } } func acceptTap(at point: CGPoint) { feedback.impactOccurred() showRipple(at: point, color: .white.withAlphaComponent(0.45)) } func rejectTap(at point: CGPoint) { showRipple(at: point, color: .white.withAlphaComponent(0.12)) } func setDismissEnabled(_ value: Bool) { contentContainer?.isDismissEnabled = value } // MARK: - Внутреннее private func showRipple(at point: CGPoint, color: UIColor) { let ripple = UIView(frame: CGRect(x: 0, y: 0, width: 24, height: 24)) ripple.center = point ripple.layer.cornerRadius = 12 ripple.backgroundColor = color ripple.isUserInteractionEnabled = false view.addSubview(ripple) UIView.animate(withDuration: 0.4) { ripple.transform = .init(scaleX: 3, y: 3) ripple.alpha = 0 } completion: { _ in ripple.removeFromSuperview() } } private func setupUI() { view.backgroundColor = .entropyBackground view.addSubview(fillView) view.addSubview(percentLabel) view.addSubview(hintLabel) let fillHeight = fillView.heightAnchor.constraint(equalToConstant: 0) fillHeightConstraint = fillHeight NSLayoutConstraint.activate([ fillView.leadingAnchor.constraint(equalTo: view.leadingAnchor), fillView.trailingAnchor.constraint(equalTo: view.trailingAnchor), fillView.bottomAnchor.constraint(equalTo: view.bottomAnchor), fillHeight, // Процент прибит к правому и нижнему краю. Отступ справа .margin // одинаков для 0% и 100%, потому что выравнивание по правому краю. percentLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -.margin), percentLabel.leadingAnchor.constraint(greaterThanOrEqualTo: view.leadingAnchor, constant: .margin), percentLabel.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor), // Подсказка чисто по центру, по бокам 16. hintLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor), hintLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor), hintLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: .margin), hintLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -.margin) ]) } }