Files
Emoney-Info---IOS/Emoney Info/TermsViewController.swift
Wira Basalamah 8f0b001501 Initial commit
2026-04-24 04:55:24 +07:00

357 lines
15 KiB
Swift

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