357 lines
15 KiB
Swift
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,
|
|
]
|
|
),
|
|
]}
|
|
}
|