367 lines
17 KiB
Swift
367 lines
17 KiB
Swift
// AboutViewController.swift
|
|
// Emoney Info
|
|
|
|
import UIKit
|
|
|
|
class AboutViewController: UIViewController {
|
|
|
|
// MARK: - Lifecycle
|
|
|
|
override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
view.backgroundColor = Theme.Color.background
|
|
setupUI()
|
|
}
|
|
|
|
override func viewWillAppear(_ animated: Bool) {
|
|
super.viewWillAppear(animated)
|
|
navigationController?.setNavigationBarHidden(true, animated: animated)
|
|
(tabBarController as? MainTabBarController)?.setTabBarHidden(true, animated: animated)
|
|
}
|
|
|
|
override func viewWillDisappear(_ animated: Bool) {
|
|
super.viewWillDisappear(animated)
|
|
(tabBarController as? MainTabBarController)?.setTabBarHidden(false, animated: animated)
|
|
}
|
|
|
|
// MARK: - Setup
|
|
|
|
private func setupUI() {
|
|
let scrollView = UIScrollView()
|
|
let contentView = UIView()
|
|
scrollView.showsVerticalScrollIndicator = false
|
|
scrollView.translatesAutoresizingMaskIntoConstraints = false
|
|
contentView.translatesAutoresizingMaskIntoConstraints = false
|
|
view.addSubview(scrollView)
|
|
scrollView.addSubview(contentView)
|
|
|
|
NSLayoutConstraint.activate([
|
|
scrollView.topAnchor.constraint(equalTo: view.topAnchor),
|
|
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
|
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
|
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
|
|
|
contentView.topAnchor.constraint(equalTo: scrollView.topAnchor),
|
|
contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
|
|
contentView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
|
|
contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
|
|
contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor),
|
|
])
|
|
|
|
// MARK: Back button + nav title
|
|
let backButton = UIButton(type: .system)
|
|
let chevron = UIImage(systemName: "chevron.left",
|
|
withConfiguration: UIImage.SymbolConfiguration(pointSize: 16, weight: .semibold))
|
|
backButton.setImage(chevron, for: .normal)
|
|
backButton.tintColor = Theme.Color.textPrimary
|
|
backButton.addTarget(self, action: #selector(backTapped), for: .touchUpInside)
|
|
backButton.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
let navTitleLabel = UILabel()
|
|
navTitleLabel.text = L10n.aboutAppTitle
|
|
navTitleLabel.font = Theme.Font.caption(weight: .semibold)
|
|
navTitleLabel.textColor = Theme.Color.textSecondary
|
|
navTitleLabel.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
// MARK: App icon
|
|
let iconView = UIImageView(image: UIImage(named: "AppLogo"))
|
|
iconView.contentMode = .scaleAspectFit
|
|
iconView.layer.cornerRadius = 24
|
|
iconView.clipsToBounds = true
|
|
iconView.layer.shadowColor = UIColor.black.cgColor
|
|
iconView.layer.shadowOpacity = 0.12
|
|
iconView.layer.shadowOffset = CGSize(width: 0, height: 4)
|
|
iconView.layer.shadowRadius = 12
|
|
iconView.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
// MARK: App name + version
|
|
let appNameLabel = UILabel()
|
|
appNameLabel.text = "Emoney Info"
|
|
appNameLabel.font = .systemFont(ofSize: 28, weight: .bold)
|
|
appNameLabel.textColor = Theme.Color.textPrimary
|
|
appNameLabel.textAlignment = .center
|
|
appNameLabel.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? ""
|
|
let versionLabel = UILabel()
|
|
let versionText = "VERSI \(appVersion)"
|
|
let versionAttr = NSAttributedString(string: versionText, attributes: [
|
|
.kern: 1.5,
|
|
.font: Theme.Font.caption(weight: .semibold),
|
|
.foregroundColor: Theme.Color.textSecondary,
|
|
])
|
|
versionLabel.attributedText = versionAttr
|
|
versionLabel.textAlignment = .center
|
|
versionLabel.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
// MARK: Description
|
|
let descLabel = UILabel()
|
|
descLabel.text = L10n.aboutAppDescription
|
|
descLabel.font = Theme.Font.body(weight: .regular)
|
|
descLabel.textColor = Theme.Color.textSecondary
|
|
descLabel.numberOfLines = 0
|
|
descLabel.textAlignment = .center
|
|
descLabel.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
// MARK: Feature chips
|
|
let chipsStack = UIStackView(arrangedSubviews: [
|
|
makeFeatureChip(L10n.aboutChipNfc),
|
|
makeFeatureChip(L10n.aboutChipRealtime),
|
|
makeFeatureChip(L10n.aboutChipMulti),
|
|
])
|
|
chipsStack.axis = .horizontal
|
|
chipsStack.spacing = 10
|
|
chipsStack.alignment = .center
|
|
chipsStack.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
// MARK: Legal rows card
|
|
let legalCard = makeCard()
|
|
|
|
let termsRow = makeLegalRow(icon: "doc.text", title: L10n.aboutTerms)
|
|
termsRow.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(termsTapped)))
|
|
termsRow.isUserInteractionEnabled = true
|
|
|
|
let separator = UIView()
|
|
separator.backgroundColor = Theme.Color.background
|
|
separator.translatesAutoresizingMaskIntoConstraints = false
|
|
separator.heightAnchor.constraint(equalToConstant: 1).isActive = true
|
|
|
|
let privacyRow = makeLegalRow(icon: "lock.shield", title: L10n.aboutPrivacy)
|
|
privacyRow.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(privacyTapped)))
|
|
privacyRow.isUserInteractionEnabled = true
|
|
|
|
let legalStack = UIStackView(arrangedSubviews: [termsRow, separator, privacyRow])
|
|
legalStack.axis = .vertical
|
|
legalStack.spacing = 0
|
|
legalStack.translatesAutoresizingMaskIntoConstraints = false
|
|
legalCard.addSubview(legalStack)
|
|
|
|
// MARK: Connect card (teal gradient)
|
|
let connectCard = UIView()
|
|
connectCard.layer.cornerRadius = 20
|
|
connectCard.clipsToBounds = true
|
|
connectCard.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
let gradientLayer = CAGradientLayer()
|
|
gradientLayer.colors = [
|
|
Theme.Color.primary.cgColor,
|
|
Theme.Color.secondary.cgColor,
|
|
]
|
|
gradientLayer.startPoint = CGPoint(x: 0, y: 0)
|
|
gradientLayer.endPoint = CGPoint(x: 1, y: 1)
|
|
connectCard.layer.insertSublayer(gradientLayer, at: 0)
|
|
|
|
let connectIcon = UIImageView(image: UIImage(systemName: "wave.3.right.circle.fill",
|
|
withConfiguration: UIImage.SymbolConfiguration(pointSize: 32, weight: .medium)))
|
|
connectIcon.tintColor = UIColor.white.withAlphaComponent(0.35)
|
|
connectIcon.contentMode = .scaleAspectFit
|
|
connectIcon.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
let connectTitle = UILabel()
|
|
connectTitle.text = L10n.aboutConnectTitle
|
|
connectTitle.font = Theme.Font.subtitle(weight: .bold)
|
|
connectTitle.textColor = .white
|
|
connectTitle.numberOfLines = 0
|
|
connectTitle.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
let connectDesc = UILabel()
|
|
connectDesc.text = L10n.aboutConnectDesc
|
|
connectDesc.font = Theme.Font.caption(weight: .regular)
|
|
connectDesc.textColor = UIColor.white.withAlphaComponent(0.85)
|
|
connectDesc.numberOfLines = 0
|
|
connectDesc.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
[connectIcon, connectTitle, connectDesc].forEach { connectCard.addSubview($0) }
|
|
|
|
// MARK: Copyright
|
|
let copyrightLabel = UILabel()
|
|
copyrightLabel.text = L10n.footerCopyright
|
|
copyrightLabel.font = Theme.Font.caption(weight: .regular)
|
|
copyrightLabel.textColor = Theme.Color.textSecondary
|
|
copyrightLabel.textAlignment = .center
|
|
copyrightLabel.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
// MARK: Add to contentView
|
|
[backButton, navTitleLabel, iconView, appNameLabel, versionLabel,
|
|
descLabel, chipsStack, legalCard, connectCard, copyrightLabel]
|
|
.forEach { contentView.addSubview($0) }
|
|
|
|
NSLayoutConstraint.activate([
|
|
// Nav row
|
|
backButton.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 56),
|
|
backButton.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
|
|
backButton.widthAnchor.constraint(equalToConstant: 32),
|
|
backButton.heightAnchor.constraint(equalToConstant: 32),
|
|
|
|
navTitleLabel.centerYAnchor.constraint(equalTo: backButton.centerYAnchor),
|
|
navTitleLabel.leadingAnchor.constraint(equalTo: backButton.trailingAnchor, constant: 8),
|
|
|
|
// Icon
|
|
iconView.topAnchor.constraint(equalTo: backButton.bottomAnchor, constant: 28),
|
|
iconView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
|
|
iconView.widthAnchor.constraint(equalToConstant: 96),
|
|
iconView.heightAnchor.constraint(equalToConstant: 96),
|
|
|
|
// Name + version
|
|
appNameLabel.topAnchor.constraint(equalTo: iconView.bottomAnchor, constant: 16),
|
|
appNameLabel.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
|
|
|
|
versionLabel.topAnchor.constraint(equalTo: appNameLabel.bottomAnchor, constant: 6),
|
|
versionLabel.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
|
|
|
|
// Desc
|
|
descLabel.topAnchor.constraint(equalTo: versionLabel.bottomAnchor, constant: 16),
|
|
descLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 32),
|
|
descLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -32),
|
|
|
|
// Chips
|
|
chipsStack.topAnchor.constraint(equalTo: descLabel.bottomAnchor, constant: 20),
|
|
chipsStack.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
|
|
|
|
// Legal card
|
|
legalCard.topAnchor.constraint(equalTo: chipsStack.bottomAnchor, constant: 28),
|
|
legalCard.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 24),
|
|
legalCard.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -24),
|
|
|
|
legalStack.topAnchor.constraint(equalTo: legalCard.topAnchor, constant: 4),
|
|
legalStack.leadingAnchor.constraint(equalTo: legalCard.leadingAnchor),
|
|
legalStack.trailingAnchor.constraint(equalTo: legalCard.trailingAnchor),
|
|
legalStack.bottomAnchor.constraint(equalTo: legalCard.bottomAnchor, constant: -4),
|
|
|
|
// Connect card
|
|
connectCard.topAnchor.constraint(equalTo: legalCard.bottomAnchor, constant: 20),
|
|
connectCard.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 24),
|
|
connectCard.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -24),
|
|
|
|
connectIcon.topAnchor.constraint(equalTo: connectCard.topAnchor, constant: 20),
|
|
connectIcon.trailingAnchor.constraint(equalTo: connectCard.trailingAnchor, constant: -20),
|
|
connectIcon.widthAnchor.constraint(equalToConstant: 44),
|
|
connectIcon.heightAnchor.constraint(equalToConstant: 44),
|
|
|
|
connectTitle.topAnchor.constraint(equalTo: connectCard.topAnchor, constant: 24),
|
|
connectTitle.leadingAnchor.constraint(equalTo: connectCard.leadingAnchor, constant: 20),
|
|
connectTitle.trailingAnchor.constraint(equalTo: connectIcon.leadingAnchor, constant: -12),
|
|
|
|
connectDesc.topAnchor.constraint(equalTo: connectTitle.bottomAnchor, constant: 10),
|
|
connectDesc.leadingAnchor.constraint(equalTo: connectCard.leadingAnchor, constant: 20),
|
|
connectDesc.trailingAnchor.constraint(equalTo: connectCard.trailingAnchor, constant: -20),
|
|
connectDesc.bottomAnchor.constraint(equalTo: connectCard.bottomAnchor, constant: -24),
|
|
|
|
// Copyright
|
|
copyrightLabel.topAnchor.constraint(equalTo: connectCard.bottomAnchor, constant: 24),
|
|
copyrightLabel.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
|
|
copyrightLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -32),
|
|
])
|
|
|
|
// Gradient frame — set after layout
|
|
DispatchQueue.main.async {
|
|
gradientLayer.frame = connectCard.bounds
|
|
}
|
|
}
|
|
|
|
// MARK: - Helpers
|
|
|
|
private func makeCard() -> UIView {
|
|
let v = UIView()
|
|
v.backgroundColor = Theme.Color.card
|
|
v.layer.cornerRadius = 16
|
|
v.layer.shadowColor = UIColor.black.cgColor
|
|
v.layer.shadowOpacity = 0.06
|
|
v.layer.shadowOffset = CGSize(width: 0, height: 2)
|
|
v.layer.shadowRadius = 8
|
|
v.translatesAutoresizingMaskIntoConstraints = false
|
|
return v
|
|
}
|
|
|
|
private func makeFeatureChip(_ title: String) -> UIView {
|
|
let container = UIView()
|
|
container.backgroundColor = Theme.Color.primary.withAlphaComponent(0.12)
|
|
container.layer.cornerRadius = 12
|
|
container.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
let label = UILabel()
|
|
label.text = title
|
|
label.font = Theme.Font.caption(weight: .semibold)
|
|
label.textColor = Theme.Color.secondary
|
|
label.translatesAutoresizingMaskIntoConstraints = false
|
|
container.addSubview(label)
|
|
|
|
NSLayoutConstraint.activate([
|
|
label.topAnchor.constraint(equalTo: container.topAnchor, constant: 6),
|
|
label.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 12),
|
|
label.trailingAnchor.constraint(equalTo: container.trailingAnchor, constant: -12),
|
|
label.bottomAnchor.constraint(equalTo: container.bottomAnchor, constant: -6),
|
|
])
|
|
return container
|
|
}
|
|
|
|
private func makeLegalRow(icon: String, title: String) -> UIView {
|
|
let row = UIView()
|
|
row.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
let iconContainer = UIView()
|
|
iconContainer.backgroundColor = Theme.Color.background
|
|
iconContainer.layer.cornerRadius = 10
|
|
iconContainer.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
let iconView = UIImageView(image: UIImage(systemName: icon,
|
|
withConfiguration: UIImage.SymbolConfiguration(pointSize: 14, weight: .medium)))
|
|
iconView.tintColor = Theme.Color.secondary
|
|
iconView.contentMode = .scaleAspectFit
|
|
iconView.translatesAutoresizingMaskIntoConstraints = false
|
|
iconContainer.addSubview(iconView)
|
|
|
|
let titleLabel = UILabel()
|
|
titleLabel.text = title
|
|
titleLabel.font = Theme.Font.body(weight: .medium)
|
|
titleLabel.textColor = Theme.Color.textPrimary
|
|
titleLabel.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
let chevronImg = UIImage(systemName: "chevron.right",
|
|
withConfiguration: UIImage.SymbolConfiguration(pointSize: 12, weight: .semibold))
|
|
let chevron = UIImageView(image: chevronImg)
|
|
chevron.tintColor = Theme.Color.textSecondary
|
|
chevron.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
[iconContainer, titleLabel, chevron].forEach { row.addSubview($0) }
|
|
|
|
NSLayoutConstraint.activate([
|
|
iconContainer.widthAnchor.constraint(equalToConstant: 36),
|
|
iconContainer.heightAnchor.constraint(equalToConstant: 36),
|
|
iconView.centerXAnchor.constraint(equalTo: iconContainer.centerXAnchor),
|
|
iconView.centerYAnchor.constraint(equalTo: iconContainer.centerYAnchor),
|
|
iconView.widthAnchor.constraint(equalToConstant: 18),
|
|
iconView.heightAnchor.constraint(equalToConstant: 18),
|
|
|
|
iconContainer.leadingAnchor.constraint(equalTo: row.leadingAnchor, constant: 16),
|
|
iconContainer.centerYAnchor.constraint(equalTo: row.centerYAnchor),
|
|
|
|
titleLabel.leadingAnchor.constraint(equalTo: iconContainer.trailingAnchor, constant: 14),
|
|
titleLabel.centerYAnchor.constraint(equalTo: row.centerYAnchor),
|
|
titleLabel.trailingAnchor.constraint(lessThanOrEqualTo: chevron.leadingAnchor, constant: -8),
|
|
|
|
chevron.trailingAnchor.constraint(equalTo: row.trailingAnchor, constant: -16),
|
|
chevron.centerYAnchor.constraint(equalTo: row.centerYAnchor),
|
|
|
|
row.heightAnchor.constraint(equalToConstant: 60),
|
|
])
|
|
return row
|
|
}
|
|
|
|
// MARK: - Actions
|
|
|
|
@objc private func backTapped() {
|
|
navigationController?.popViewController(animated: true)
|
|
}
|
|
|
|
@objc private func termsTapped() {
|
|
let vc = TermsViewController()
|
|
navigationController?.pushViewController(vc, animated: true)
|
|
}
|
|
|
|
@objc private func privacyTapped() {
|
|
let vc = PrivacyPolicyViewController()
|
|
navigationController?.pushViewController(vc, animated: true)
|
|
}
|
|
}
|