// TermsViewController.swift // Emoney Info import UIKit final class TermsViewController: 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 + nav label 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 navLabel = UILabel() navLabel.text = L10n.aboutTerms navLabel.font = Theme.Font.caption(weight: .semibold) navLabel.textColor = Theme.Color.textSecondary navLabel.translatesAutoresizingMaskIntoConstraints = false // MARK: Last updated chip let updatedLabel = makePaddedLabel(text: L10n.termsLastUpdated, color: Theme.Color.secondary, bg: Theme.Color.primary.withAlphaComponent(0.15)) // MARK: Hero title (mixed weight) let heroLabel = UILabel() let heroAttr = NSMutableAttributedString( string: L10n.termsTitleRegular + " ", attributes: [ .font: UIFont.systemFont(ofSize: 32, weight: .regular), .foregroundColor: Theme.Color.textPrimary, ] ) heroAttr.append(NSAttributedString( string: L10n.termsTitleBold, attributes: [ .font: UIFont.systemFont(ofSize: 32, weight: .bold), .foregroundColor: Theme.Color.textPrimary, ] )) heroLabel.attributedText = heroAttr heroLabel.numberOfLines = 0 heroLabel.translatesAutoresizingMaskIntoConstraints = false // MARK: Subtitle let subtitleLabel = UILabel() subtitleLabel.text = L10n.termsSubtitle subtitleLabel.font = Theme.Font.body(weight: .regular) subtitleLabel.textColor = Theme.Color.textSecondary subtitleLabel.numberOfLines = 0 subtitleLabel.translatesAutoresizingMaskIntoConstraints = false // MARK: Terms sections let termsStack = UIStackView() termsStack.axis = .vertical termsStack.spacing = 16 termsStack.translatesAutoresizingMaskIntoConstraints = false for (index, section) in TermsData.sections.enumerated() { let row = makeSectionRow(number: index + 1, section: section) termsStack.addArrangedSubview(row) } // 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.termsContactTitle contactTitle.font = Theme.Font.subtitle(weight: .bold) contactTitle.textColor = .white contactTitle.numberOfLines = 0 contactTitle.translatesAutoresizingMaskIntoConstraints = false let contactDesc = UILabel() contactDesc.text = L10n.termsContactDesc 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.termsContactButton, 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, navLabel, updatedLabel, heroLabel, subtitleLabel, termsStack, contactCard, footerLabel].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), navLabel.centerYAnchor.constraint(equalTo: backButton.centerYAnchor), navLabel.leadingAnchor.constraint(equalTo: backButton.trailingAnchor, constant: 8), // Chip updatedLabel.topAnchor.constraint(equalTo: backButton.bottomAnchor, constant: 20), updatedLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 24), // Hero heroLabel.topAnchor.constraint(equalTo: updatedLabel.bottomAnchor, constant: 12), heroLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 24), heroLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -24), // Subtitle subtitleLabel.topAnchor.constraint(equalTo: heroLabel.bottomAnchor, constant: 12), subtitleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 24), subtitleLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -24), // Terms stack termsStack.topAnchor.constraint(equalTo: subtitleLabel.bottomAnchor, constant: 28), termsStack.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20), termsStack.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20), // Contact card contactCard.topAnchor.constraint(equalTo: termsStack.bottomAnchor, constant: 28), 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), ]) } // MARK: - Helpers private func makePaddedLabel(text: String, color: UIColor, bg: UIColor) -> UILabel { let label = UILabel() label.text = text label.font = Theme.Font.caption(weight: .semibold) label.textColor = color label.backgroundColor = bg label.layer.cornerRadius = 8 label.clipsToBounds = true label.textAlignment = .center label.translatesAutoresizingMaskIntoConstraints = false label.heightAnchor.constraint(equalToConstant: 26).isActive = true return label } private func makeSectionRow(number: Int, section: TermsSection) -> UIView { let card = UIView() card.backgroundColor = Theme.Color.card card.layer.cornerRadius = 16 card.layer.shadowColor = UIColor.black.cgColor card.layer.shadowOpacity = 0.06 card.layer.shadowOffset = CGSize(width: 0, height: 2) card.layer.shadowRadius = 8 card.translatesAutoresizingMaskIntoConstraints = false // Number badge let badge = UILabel() badge.text = "\(number)." badge.font = Theme.Font.body(weight: .bold) badge.textColor = Theme.Color.secondary badge.translatesAutoresizingMaskIntoConstraints = false // Title 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 // Header row let headerStack = UIStackView(arrangedSubviews: [badge, titleLabel]) headerStack.axis = .horizontal headerStack.spacing = 8 headerStack.alignment = .top headerStack.translatesAutoresizingMaskIntoConstraints = false // Body 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 // Bullet points let bulletStack = UIStackView() bulletStack.axis = .vertical bulletStack.spacing = 6 bulletStack.translatesAutoresizingMaskIntoConstraints = false for bullet in section.bullets { let bulletRow = UIStackView() bulletRow.axis = .horizontal bulletRow.spacing = 8 bulletRow.alignment = .top let dot = UILabel() dot.text = "•" dot.font = Theme.Font.caption(weight: .semibold) dot.textColor = Theme.Color.secondary let bulletLabel = UILabel() bulletLabel.text = bullet bulletLabel.font = Theme.Font.caption(weight: .regular) bulletLabel.textColor = Theme.Color.textSecondary bulletLabel.numberOfLines = 0 bulletRow.addArrangedSubview(dot) bulletRow.addArrangedSubview(bulletLabel) bulletStack.addArrangedSubview(bulletRow) } let mainStack = UIStackView(arrangedSubviews: [headerStack, bodyLabel]) if !section.bullets.isEmpty { mainStack.addArrangedSubview(bulletStack) } mainStack.axis = .vertical mainStack.spacing = 8 mainStack.translatesAutoresizingMaskIntoConstraints = false card.addSubview(mainStack) NSLayoutConstraint.activate([ badge.widthAnchor.constraint(equalToConstant: 24), mainStack.topAnchor.constraint(equalTo: card.topAnchor, constant: 16), mainStack.leadingAnchor.constraint(equalTo: card.leadingAnchor, constant: 16), mainStack.trailingAnchor.constraint(equalTo: card.trailingAnchor, constant: -16), mainStack.bottomAnchor.constraint(equalTo: card.bottomAnchor, constant: -16), ]) return card } // 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: - Terms Data Model struct TermsSection { let title: String let body: String let bullets: [String] } enum TermsData { static var sections: [TermsSection] {[ TermsSection( title: L10n.termsSec1Title, body: L10n.termsSec1Body, bullets: [] ), TermsSection( title: L10n.termsSec2Title, body: L10n.termsSec2Body, bullets: [ L10n.termsSec2Bullet1, L10n.termsSec2Bullet2, L10n.termsSec2Bullet3, ] ), TermsSection( title: L10n.termsSec3Title, body: L10n.termsSec3Body, bullets: [ L10n.termsSec3Bullet1, L10n.termsSec3Bullet2, ] ), ]} }