// 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) } }