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

310 lines
14 KiB
Swift

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