// PrivacyPolicyViewController.swift // Emoney Info import UIKit final class PrivacyPolicyViewController: 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 + 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 titleLabel = UILabel() titleLabel.text = L10n.aboutPrivacy titleLabel.font = Theme.Font.title(weight: .bold) titleLabel.textColor = Theme.Color.textPrimary titleLabel.translatesAutoresizingMaskIntoConstraints = false // MARK: Last Updated chip let updatedLabel = UILabel() updatedLabel.text = L10n.privacyLastUpdated updatedLabel.font = Theme.Font.caption(weight: .semibold) updatedLabel.textColor = Theme.Color.secondary updatedLabel.backgroundColor = Theme.Color.primary.withAlphaComponent(0.15) updatedLabel.layer.cornerRadius = 8 updatedLabel.clipsToBounds = true updatedLabel.textAlignment = .center updatedLabel.translatesAutoresizingMaskIntoConstraints = false // MARK: Sections card let sections = PrivacyData.sections let sectionsCard = makeCard() let sectionsStack = UIStackView() sectionsStack.axis = .vertical sectionsStack.spacing = 0 sectionsStack.translatesAutoresizingMaskIntoConstraints = false sectionsCard.addSubview(sectionsStack) for (i, section) in sections.enumerated() { let row = makeSectionRow(section: section) sectionsStack.addArrangedSubview(row) if i < sections.count - 1 { let sep = UIView() sep.backgroundColor = Theme.Color.background sep.translatesAutoresizingMaskIntoConstraints = false sep.heightAnchor.constraint(equalToConstant: 1).isActive = true sectionsStack.addArrangedSubview(sep) } } // MARK: Contact card (teal) let contactCard = UIView() contactCard.backgroundColor = Theme.Color.primary contactCard.layer.cornerRadius = 20 contactCard.translatesAutoresizingMaskIntoConstraints = false let contactTitle = UILabel() contactTitle.text = L10n.privacyContactTitle contactTitle.font = Theme.Font.subtitle(weight: .bold) contactTitle.textColor = .white contactTitle.numberOfLines = 0 contactTitle.translatesAutoresizingMaskIntoConstraints = false let contactDesc = UILabel() contactDesc.text = L10n.privacyContactDesc contactDesc.font = Theme.Font.caption(weight: .regular) contactDesc.textColor = UIColor.white.withAlphaComponent(0.85) contactDesc.numberOfLines = 0 contactDesc.translatesAutoresizingMaskIntoConstraints = false let contactButton = UIButton(type: .system) contactButton.setTitle(L10n.privacyContactButton, for: .normal) contactButton.titleLabel?.font = Theme.Font.body(weight: .semibold) contactButton.setTitleColor(Theme.Color.primary, for: .normal) contactButton.backgroundColor = .white contactButton.layer.cornerRadius = 14 contactButton.contentEdgeInsets = UIEdgeInsets(top: 12, left: 28, bottom: 12, right: 28) contactButton.addTarget(self, action: #selector(contactTapped), for: .touchUpInside) contactButton.translatesAutoresizingMaskIntoConstraints = false [contactTitle, contactDesc, contactButton].forEach { contactCard.addSubview($0) } // MARK: Footer let footerLabel = UILabel() footerLabel.text = L10n.footerCopyright footerLabel.font = Theme.Font.caption(weight: .regular) footerLabel.textColor = Theme.Color.textSecondary footerLabel.textAlignment = .center footerLabel.translatesAutoresizingMaskIntoConstraints = false [backButton, titleLabel, updatedLabel, sectionsCard, contactCard, footerLabel] .forEach { contentView.addSubview($0) } NSLayoutConstraint.activate([ // Nav 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), titleLabel.topAnchor.constraint(equalTo: backButton.bottomAnchor, constant: 20), titleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 24), updatedLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 10), updatedLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 24), updatedLabel.heightAnchor.constraint(equalToConstant: 26), updatedLabel.widthAnchor.constraint(greaterThanOrEqualToConstant: 80), // Sections card sectionsCard.topAnchor.constraint(equalTo: updatedLabel.bottomAnchor, constant: 24), sectionsCard.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20), sectionsCard.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20), sectionsStack.topAnchor.constraint(equalTo: sectionsCard.topAnchor, constant: 8), sectionsStack.leadingAnchor.constraint(equalTo: sectionsCard.leadingAnchor), sectionsStack.trailingAnchor.constraint(equalTo: sectionsCard.trailingAnchor), sectionsStack.bottomAnchor.constraint(equalTo: sectionsCard.bottomAnchor, constant: -8), // Contact card contactCard.topAnchor.constraint(equalTo: sectionsCard.bottomAnchor, constant: 24), contactCard.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20), contactCard.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20), contactTitle.topAnchor.constraint(equalTo: contactCard.topAnchor, constant: 24), contactTitle.leadingAnchor.constraint(equalTo: contactCard.leadingAnchor, constant: 20), contactTitle.trailingAnchor.constraint(equalTo: contactCard.trailingAnchor, constant: -20), contactDesc.topAnchor.constraint(equalTo: contactTitle.bottomAnchor, constant: 8), contactDesc.leadingAnchor.constraint(equalTo: contactCard.leadingAnchor, constant: 20), contactDesc.trailingAnchor.constraint(equalTo: contactCard.trailingAnchor, constant: -20), contactButton.topAnchor.constraint(equalTo: contactDesc.bottomAnchor, constant: 20), contactButton.leadingAnchor.constraint(equalTo: contactCard.leadingAnchor, constant: 20), contactButton.bottomAnchor.constraint(equalTo: contactCard.bottomAnchor, constant: -24), // Footer footerLabel.topAnchor.constraint(equalTo: contactCard.bottomAnchor, constant: 24), footerLabel.centerXAnchor.constraint(equalTo: contentView.centerXAnchor), footerLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -32), ]) // updatedLabel padding via insets workaround updatedLabel.setContentHuggingPriority(.required, for: .horizontal) } // 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 makeSectionRow(section: PrivacySection) -> UIView { let row = UIView() row.translatesAutoresizingMaskIntoConstraints = false // Icon container let iconContainer = UIView() iconContainer.backgroundColor = Theme.Color.primary.withAlphaComponent(0.12) iconContainer.layer.cornerRadius = 12 iconContainer.translatesAutoresizingMaskIntoConstraints = false let iconView = UIImageView(image: UIImage(systemName: section.icon, withConfiguration: UIImage.SymbolConfiguration(pointSize: 16, weight: .medium))) iconView.tintColor = Theme.Color.secondary iconView.contentMode = .scaleAspectFit iconView.translatesAutoresizingMaskIntoConstraints = false iconContainer.addSubview(iconView) // Text let titleLabel = UILabel() titleLabel.text = section.title titleLabel.font = Theme.Font.body(weight: .semibold) titleLabel.textColor = Theme.Color.textPrimary titleLabel.numberOfLines = 0 titleLabel.translatesAutoresizingMaskIntoConstraints = false let bodyLabel = UILabel() bodyLabel.text = section.body bodyLabel.font = Theme.Font.caption(weight: .regular) bodyLabel.textColor = Theme.Color.textSecondary bodyLabel.numberOfLines = 0 bodyLabel.translatesAutoresizingMaskIntoConstraints = false let textStack = UIStackView(arrangedSubviews: [titleLabel, bodyLabel]) textStack.axis = .vertical textStack.spacing = 4 textStack.translatesAutoresizingMaskIntoConstraints = false [iconContainer, textStack].forEach { row.addSubview($0) } NSLayoutConstraint.activate([ iconContainer.widthAnchor.constraint(equalToConstant: 44), iconContainer.heightAnchor.constraint(equalToConstant: 44), iconView.centerXAnchor.constraint(equalTo: iconContainer.centerXAnchor), iconView.centerYAnchor.constraint(equalTo: iconContainer.centerYAnchor), iconView.widthAnchor.constraint(equalToConstant: 20), iconView.heightAnchor.constraint(equalToConstant: 20), iconContainer.leadingAnchor.constraint(equalTo: row.leadingAnchor, constant: 16), iconContainer.topAnchor.constraint(equalTo: row.topAnchor, constant: 16), textStack.leadingAnchor.constraint(equalTo: iconContainer.trailingAnchor, constant: 14), textStack.trailingAnchor.constraint(equalTo: row.trailingAnchor, constant: -16), textStack.topAnchor.constraint(equalTo: row.topAnchor, constant: 16), textStack.bottomAnchor.constraint(equalTo: row.bottomAnchor, constant: -16), ]) return row } // MARK: - Actions @objc private func backTapped() { navigationController?.popViewController(animated: true) } @objc private func contactTapped() { let address = "apps@indonesiainyourhand.com" let subject = "Ask Support" let encoded = subject.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? subject if let url = URL(string: "mailto:\(address)?subject=\(encoded)") { UIApplication.shared.open(url) } } } // MARK: - Privacy Data Model struct PrivacySection { let icon: String let title: String let body: String } enum PrivacyData { static var sections: [PrivacySection] {[ PrivacySection( icon: "wave.3.right.circle.fill", title: L10n.privacySectionNfcTitle, body: L10n.privacySectionNfcBody ), PrivacySection( icon: "xmark.icloud.fill", title: L10n.privacySectionNoStorageTitle, body: L10n.privacySectionNoStorageBody ), PrivacySection( icon: "eye.fill", title: L10n.privacySectionReadOnlyTitle, body: L10n.privacySectionReadOnlyBody ), ]} }