557 lines
23 KiB
Swift
557 lines
23 KiB
Swift
// FAQViewController.swift
|
|
// Emoney Info
|
|
//
|
|
// Halaman Pusat Bantuan / FAQ
|
|
// - Search bar berfungsi (filter pertanyaan real-time)
|
|
// - Filter chip per kategori berfungsi
|
|
// - Accordion: tap pertanyaan untuk expand/collapse jawaban
|
|
|
|
import UIKit
|
|
|
|
final class FAQViewController: UIViewController {
|
|
|
|
// MARK: - State
|
|
|
|
private var allCategories: [FAQCategory] = FAQData.all
|
|
private var activeFilter: String? = nil // nil = semua kategori
|
|
private var searchText: String = ""
|
|
private var expandedItems: Set<String> = [] // key = "catID|qIndex"
|
|
|
|
// Computed: filtered categories based on search + filter chip
|
|
private var displayedCategories: [FAQCategory] {
|
|
var source = allCategories
|
|
|
|
// 1. Filter by category chip
|
|
if let filter = activeFilter {
|
|
source = source.filter { $0.id == filter }
|
|
}
|
|
|
|
// 2. Filter by search text
|
|
if !searchText.isEmpty {
|
|
let q = searchText.lowercased()
|
|
source = source.compactMap { cat in
|
|
let filtered = cat.items.filter {
|
|
$0.question.lowercased().contains(q) || $0.answer.lowercased().contains(q)
|
|
}
|
|
if filtered.isEmpty { return nil }
|
|
return FAQCategory(id: cat.id, icon: cat.icon, title: cat.title, items: filtered)
|
|
}
|
|
}
|
|
|
|
return source
|
|
}
|
|
|
|
// MARK: - UI
|
|
|
|
private let scrollView = UIScrollView()
|
|
private let contentView = UIView()
|
|
|
|
private let headerView = UIView()
|
|
private let backButton = UIButton(type: .system)
|
|
private let titleLabel = UILabel()
|
|
private let subtitleLabel = UILabel()
|
|
private let searchBar = UISearchBar()
|
|
|
|
// Category filter chips (horizontal scroll)
|
|
private let chipScrollView = UIScrollView()
|
|
private let chipStack = UIStackView()
|
|
|
|
// FAQ accordion container
|
|
private let faqContainerStack = UIStackView()
|
|
|
|
// "Still need help?" footer card
|
|
private let helpCard = UIView()
|
|
|
|
// MARK: - Lifecycle
|
|
|
|
override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
view.backgroundColor = Theme.Color.background
|
|
setupScrollView()
|
|
setupHeader()
|
|
setupChips()
|
|
setupFAQSection()
|
|
setupHelpCard()
|
|
reloadFAQ()
|
|
}
|
|
|
|
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: ScrollView
|
|
|
|
private func setupScrollView() {
|
|
scrollView.showsVerticalScrollIndicator = false
|
|
scrollView.keyboardDismissMode = .onDrag
|
|
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: - Setup: Header
|
|
|
|
private func setupHeader() {
|
|
// Teal rounded-bottom header card
|
|
headerView.backgroundColor = Theme.Color.primary
|
|
headerView.layer.cornerRadius = 24
|
|
headerView.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner]
|
|
headerView.translatesAutoresizingMaskIntoConstraints = false
|
|
contentView.addSubview(headerView)
|
|
|
|
// Back button
|
|
let chevron = UIImage(systemName: "chevron.left",
|
|
withConfiguration: UIImage.SymbolConfiguration(pointSize: 16, weight: .semibold))
|
|
backButton.setImage(chevron, for: .normal)
|
|
backButton.tintColor = .white
|
|
backButton.addTarget(self, action: #selector(backTapped), for: .touchUpInside)
|
|
backButton.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
// Header nav label "Help Center"
|
|
let navLabel = UILabel()
|
|
navLabel.text = L10n.helpCenterTitle
|
|
navLabel.font = Theme.Font.caption(weight: .semibold)
|
|
navLabel.textColor = UIColor.white.withAlphaComponent(0.85)
|
|
navLabel.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
// Title
|
|
titleLabel.text = L10n.faqHeaderTitle
|
|
titleLabel.font = Theme.Font.title(weight: .bold)
|
|
titleLabel.textColor = .white
|
|
titleLabel.numberOfLines = 0
|
|
titleLabel.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
// Search bar
|
|
searchBar.placeholder = L10n.faqSearchPlaceholder
|
|
searchBar.searchBarStyle = .minimal
|
|
searchBar.backgroundColor = .white
|
|
searchBar.layer.cornerRadius = 12
|
|
searchBar.clipsToBounds = true
|
|
searchBar.delegate = self
|
|
searchBar.translatesAutoresizingMaskIntoConstraints = false
|
|
if let tf = searchBar.value(forKey: "searchField") as? UITextField {
|
|
tf.backgroundColor = .white
|
|
tf.font = Theme.Font.body(weight: .regular)
|
|
}
|
|
|
|
[backButton, navLabel, titleLabel, searchBar].forEach { headerView.addSubview($0) }
|
|
|
|
NSLayoutConstraint.activate([
|
|
headerView.topAnchor.constraint(equalTo: contentView.topAnchor),
|
|
headerView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
|
|
headerView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
|
|
|
|
backButton.topAnchor.constraint(equalTo: headerView.safeAreaLayoutGuide.topAnchor, constant: 16),
|
|
backButton.leadingAnchor.constraint(equalTo: headerView.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),
|
|
|
|
titleLabel.topAnchor.constraint(equalTo: backButton.bottomAnchor, constant: 20),
|
|
titleLabel.leadingAnchor.constraint(equalTo: headerView.leadingAnchor, constant: 24),
|
|
titleLabel.trailingAnchor.constraint(equalTo: headerView.trailingAnchor, constant: -24),
|
|
|
|
searchBar.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 16),
|
|
searchBar.leadingAnchor.constraint(equalTo: headerView.leadingAnchor, constant: 16),
|
|
searchBar.trailingAnchor.constraint(equalTo: headerView.trailingAnchor, constant: -16),
|
|
searchBar.bottomAnchor.constraint(equalTo: headerView.bottomAnchor, constant: -20),
|
|
searchBar.heightAnchor.constraint(equalToConstant: 44),
|
|
])
|
|
}
|
|
|
|
// MARK: - Setup: Category Chips
|
|
|
|
private func setupChips() {
|
|
chipScrollView.showsHorizontalScrollIndicator = false
|
|
chipScrollView.translatesAutoresizingMaskIntoConstraints = false
|
|
contentView.addSubview(chipScrollView)
|
|
|
|
chipStack.axis = .horizontal
|
|
chipStack.spacing = 10
|
|
chipStack.alignment = .center
|
|
chipStack.translatesAutoresizingMaskIntoConstraints = false
|
|
chipScrollView.addSubview(chipStack)
|
|
|
|
NSLayoutConstraint.activate([
|
|
chipScrollView.topAnchor.constraint(equalTo: headerView.bottomAnchor, constant: 20),
|
|
chipScrollView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
|
|
chipScrollView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
|
|
chipScrollView.heightAnchor.constraint(equalToConstant: 44),
|
|
|
|
chipStack.centerYAnchor.constraint(equalTo: chipScrollView.centerYAnchor),
|
|
chipStack.leadingAnchor.constraint(equalTo: chipScrollView.leadingAnchor, constant: 20),
|
|
chipStack.trailingAnchor.constraint(equalTo: chipScrollView.trailingAnchor, constant: -20),
|
|
])
|
|
|
|
// "All" chip + one per category
|
|
buildChips()
|
|
}
|
|
|
|
private func buildChips() {
|
|
chipStack.arrangedSubviews.forEach { $0.removeFromSuperview() }
|
|
|
|
let allChip = makeChip(id: nil, icon: "square.grid.2x2", title: L10n.faqFilterAll)
|
|
chipStack.addArrangedSubview(allChip)
|
|
|
|
for cat in allCategories {
|
|
let chip = makeChip(id: cat.id, icon: cat.icon, title: cat.title)
|
|
chipStack.addArrangedSubview(chip)
|
|
}
|
|
}
|
|
|
|
private func makeChip(id: String?, icon: String, title: String) -> UIView {
|
|
let isActive = (id == activeFilter)
|
|
|
|
let container = UIControl()
|
|
container.backgroundColor = isActive ? Theme.Color.primary : .white
|
|
container.layer.cornerRadius = 18
|
|
container.layer.borderWidth = isActive ? 0 : 1
|
|
container.layer.borderColor = UIColor.systemGray5.cgColor
|
|
container.layer.shadowColor = UIColor.black.cgColor
|
|
container.layer.shadowOpacity = isActive ? 0.08 : 0.04
|
|
container.layer.shadowOffset = CGSize(width: 0, height: 2)
|
|
container.layer.shadowRadius = 4
|
|
|
|
let iconView = UIImageView(image: UIImage(systemName: icon,
|
|
withConfiguration: UIImage.SymbolConfiguration(pointSize: 12, weight: .medium)))
|
|
iconView.tintColor = isActive ? .white : Theme.Color.secondary
|
|
iconView.contentMode = .scaleAspectFit
|
|
iconView.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
let label = UILabel()
|
|
label.text = title
|
|
label.font = Theme.Font.caption(weight: .semibold)
|
|
label.textColor = isActive ? .white : Theme.Color.textPrimary
|
|
label.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
let stack = UIStackView(arrangedSubviews: [iconView, label])
|
|
stack.axis = .horizontal
|
|
stack.spacing = 5
|
|
stack.alignment = .center
|
|
stack.isUserInteractionEnabled = false
|
|
stack.translatesAutoresizingMaskIntoConstraints = false
|
|
container.addSubview(stack)
|
|
|
|
NSLayoutConstraint.activate([
|
|
iconView.widthAnchor.constraint(equalToConstant: 14),
|
|
iconView.heightAnchor.constraint(equalToConstant: 14),
|
|
stack.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 14),
|
|
stack.trailingAnchor.constraint(equalTo: container.trailingAnchor, constant: -14),
|
|
stack.centerYAnchor.constraint(equalTo: container.centerYAnchor),
|
|
container.heightAnchor.constraint(equalToConstant: 36),
|
|
])
|
|
|
|
// Store filter ID in accessibilityIdentifier for lookup
|
|
container.accessibilityIdentifier = id ?? "__all__"
|
|
container.addTarget(self, action: #selector(chipTapped(_:)), for: .touchUpInside)
|
|
|
|
return container
|
|
}
|
|
|
|
// MARK: - Setup: FAQ Accordion Section
|
|
|
|
private func setupFAQSection() {
|
|
faqContainerStack.axis = .vertical
|
|
faqContainerStack.spacing = 12
|
|
faqContainerStack.translatesAutoresizingMaskIntoConstraints = false
|
|
contentView.addSubview(faqContainerStack)
|
|
|
|
NSLayoutConstraint.activate([
|
|
faqContainerStack.topAnchor.constraint(equalTo: chipScrollView.bottomAnchor, constant: 20),
|
|
faqContainerStack.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
|
|
faqContainerStack.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20),
|
|
])
|
|
}
|
|
|
|
// MARK: - Setup: Help Card Footer
|
|
|
|
private func setupHelpCard() {
|
|
helpCard.backgroundColor = Theme.Color.primary
|
|
helpCard.layer.cornerRadius = 20
|
|
helpCard.translatesAutoresizingMaskIntoConstraints = false
|
|
contentView.addSubview(helpCard)
|
|
|
|
let iconView = UIImageView(image: UIImage(systemName: "questionmark.bubble.fill",
|
|
withConfiguration: UIImage.SymbolConfiguration(pointSize: 28, weight: .medium)))
|
|
iconView.tintColor = .white
|
|
iconView.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
let helpTitle = UILabel()
|
|
helpTitle.text = L10n.faqHelpCardTitle
|
|
helpTitle.font = Theme.Font.subtitle(weight: .bold)
|
|
helpTitle.textColor = .white
|
|
helpTitle.numberOfLines = 0
|
|
helpTitle.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
let helpDesc = UILabel()
|
|
helpDesc.text = L10n.faqHelpCardDesc
|
|
helpDesc.font = Theme.Font.caption(weight: .regular)
|
|
helpDesc.textColor = UIColor.white.withAlphaComponent(0.85)
|
|
helpDesc.numberOfLines = 0
|
|
helpDesc.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
let emailButton = UIButton(type: .system)
|
|
emailButton.setTitle(L10n.faqEmailSupport, for: .normal)
|
|
emailButton.setImage(UIImage(systemName: "envelope.fill",
|
|
withConfiguration: UIImage.SymbolConfiguration(pointSize: 14, weight: .medium)), for: .normal)
|
|
emailButton.tintColor = Theme.Color.primary
|
|
emailButton.backgroundColor = .white
|
|
emailButton.layer.cornerRadius = 14
|
|
emailButton.titleLabel?.font = Theme.Font.body(weight: .semibold)
|
|
emailButton.contentEdgeInsets = UIEdgeInsets(top: 12, left: 20, bottom: 12, right: 20)
|
|
emailButton.imageEdgeInsets = UIEdgeInsets(top: 0, left: -6, bottom: 0, right: 0)
|
|
emailButton.addTarget(self, action: #selector(emailTapped), for: .touchUpInside)
|
|
emailButton.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
[iconView, helpTitle, helpDesc, emailButton].forEach { helpCard.addSubview($0) }
|
|
|
|
NSLayoutConstraint.activate([
|
|
helpCard.topAnchor.constraint(equalTo: faqContainerStack.bottomAnchor, constant: 28),
|
|
helpCard.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
|
|
helpCard.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20),
|
|
helpCard.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -140),
|
|
|
|
iconView.topAnchor.constraint(equalTo: helpCard.topAnchor, constant: 24),
|
|
iconView.leadingAnchor.constraint(equalTo: helpCard.leadingAnchor, constant: 24),
|
|
iconView.widthAnchor.constraint(equalToConstant: 36),
|
|
iconView.heightAnchor.constraint(equalToConstant: 36),
|
|
|
|
helpTitle.topAnchor.constraint(equalTo: iconView.bottomAnchor, constant: 12),
|
|
helpTitle.leadingAnchor.constraint(equalTo: helpCard.leadingAnchor, constant: 24),
|
|
helpTitle.trailingAnchor.constraint(equalTo: helpCard.trailingAnchor, constant: -24),
|
|
|
|
helpDesc.topAnchor.constraint(equalTo: helpTitle.bottomAnchor, constant: 8),
|
|
helpDesc.leadingAnchor.constraint(equalTo: helpCard.leadingAnchor, constant: 24),
|
|
helpDesc.trailingAnchor.constraint(equalTo: helpCard.trailingAnchor, constant: -24),
|
|
|
|
emailButton.topAnchor.constraint(equalTo: helpDesc.bottomAnchor, constant: 20),
|
|
emailButton.leadingAnchor.constraint(equalTo: helpCard.leadingAnchor, constant: 24),
|
|
emailButton.bottomAnchor.constraint(equalTo: helpCard.bottomAnchor, constant: -24),
|
|
])
|
|
}
|
|
|
|
// MARK: - Reload FAQ Accordion
|
|
|
|
private func reloadFAQ() {
|
|
faqContainerStack.arrangedSubviews.forEach { $0.removeFromSuperview() }
|
|
|
|
let categories = displayedCategories
|
|
|
|
if categories.isEmpty {
|
|
let emptyLabel = UILabel()
|
|
emptyLabel.text = L10n.faqNoResults
|
|
emptyLabel.font = Theme.Font.body(weight: .regular)
|
|
emptyLabel.textColor = Theme.Color.textSecondary
|
|
emptyLabel.textAlignment = .center
|
|
emptyLabel.numberOfLines = 0
|
|
faqContainerStack.addArrangedSubview(emptyLabel)
|
|
return
|
|
}
|
|
|
|
for cat in categories {
|
|
// Category header label
|
|
let catLabel = UILabel()
|
|
catLabel.text = cat.title.uppercased()
|
|
catLabel.font = Theme.Font.caption(weight: .semibold)
|
|
catLabel.textColor = Theme.Color.textSecondary
|
|
faqContainerStack.addArrangedSubview(catLabel)
|
|
faqContainerStack.setCustomSpacing(8, after: catLabel)
|
|
|
|
// White card holding all items for this category
|
|
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
|
|
|
|
let itemStack = UIStackView()
|
|
itemStack.axis = .vertical
|
|
itemStack.spacing = 0
|
|
itemStack.translatesAutoresizingMaskIntoConstraints = false
|
|
card.addSubview(itemStack)
|
|
|
|
NSLayoutConstraint.activate([
|
|
itemStack.topAnchor.constraint(equalTo: card.topAnchor, constant: 4),
|
|
itemStack.leadingAnchor.constraint(equalTo: card.leadingAnchor),
|
|
itemStack.trailingAnchor.constraint(equalTo: card.trailingAnchor),
|
|
itemStack.bottomAnchor.constraint(equalTo: card.bottomAnchor, constant: -4),
|
|
])
|
|
|
|
for (qIndex, item) in cat.items.enumerated() {
|
|
let key = "\(cat.id)|\(qIndex)"
|
|
let isExpanded = expandedItems.contains(key)
|
|
|
|
let row = FAQItemRow(
|
|
item: item,
|
|
isExpanded: isExpanded,
|
|
onToggle: { [weak self] in
|
|
self?.toggleItem(key: key)
|
|
}
|
|
)
|
|
itemStack.addArrangedSubview(row)
|
|
|
|
if qIndex < cat.items.count - 1 {
|
|
let sep = UIView()
|
|
sep.backgroundColor = Theme.Color.background
|
|
sep.translatesAutoresizingMaskIntoConstraints = false
|
|
sep.heightAnchor.constraint(equalToConstant: 1).isActive = true
|
|
itemStack.addArrangedSubview(sep)
|
|
}
|
|
}
|
|
|
|
faqContainerStack.addArrangedSubview(card)
|
|
faqContainerStack.setCustomSpacing(20, after: card)
|
|
}
|
|
}
|
|
|
|
// MARK: - Toggle accordion item
|
|
|
|
private func toggleItem(key: String) {
|
|
if expandedItems.contains(key) {
|
|
expandedItems.remove(key)
|
|
} else {
|
|
expandedItems.insert(key)
|
|
}
|
|
UIView.animate(withDuration: 0.25, delay: 0,
|
|
usingSpringWithDamping: 0.85, initialSpringVelocity: 0) {
|
|
self.reloadFAQ()
|
|
self.view.layoutIfNeeded()
|
|
}
|
|
}
|
|
|
|
// MARK: - Actions
|
|
|
|
@objc private func backTapped() {
|
|
navigationController?.popViewController(animated: true)
|
|
}
|
|
|
|
@objc private func chipTapped(_ sender: UIControl) {
|
|
let rawID = sender.accessibilityIdentifier
|
|
activeFilter = (rawID == "__all__") ? nil : rawID
|
|
expandedItems.removeAll()
|
|
buildChips()
|
|
reloadFAQ()
|
|
}
|
|
|
|
@objc private func emailTapped() {
|
|
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: - UISearchBarDelegate
|
|
|
|
extension FAQViewController: UISearchBarDelegate {
|
|
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
|
|
self.searchText = searchText
|
|
expandedItems.removeAll()
|
|
reloadFAQ()
|
|
}
|
|
|
|
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
|
|
searchBar.resignFirstResponder()
|
|
}
|
|
}
|
|
|
|
// MARK: - FAQItemRow (Accordion Row)
|
|
|
|
private final class FAQItemRow: UIView {
|
|
|
|
private let questionLabel = UILabel()
|
|
private let answerLabel = UILabel()
|
|
private let chevron = UIImageView()
|
|
|
|
var onToggle: (() -> Void)?
|
|
|
|
init(item: FAQItem, isExpanded: Bool, onToggle: @escaping () -> Void) {
|
|
self.onToggle = onToggle
|
|
super.init(frame: .zero)
|
|
setup(item: item, isExpanded: isExpanded)
|
|
}
|
|
|
|
required init?(coder: NSCoder) { fatalError() }
|
|
|
|
private func setup(item: FAQItem, isExpanded: Bool) {
|
|
// Chevron icon
|
|
let chevronConfig = UIImage.SymbolConfiguration(pointSize: 12, weight: .semibold)
|
|
chevron.image = UIImage(systemName: "chevron.down", withConfiguration: chevronConfig)
|
|
chevron.tintColor = Theme.Color.textSecondary
|
|
chevron.contentMode = .scaleAspectFit
|
|
chevron.transform = isExpanded
|
|
? CGAffineTransform(rotationAngle: .pi)
|
|
: .identity
|
|
chevron.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
// Question
|
|
questionLabel.text = item.question
|
|
questionLabel.font = Theme.Font.body(weight: .medium)
|
|
questionLabel.textColor = Theme.Color.textPrimary
|
|
questionLabel.numberOfLines = 0
|
|
|
|
// Answer
|
|
answerLabel.text = item.answer
|
|
answerLabel.font = Theme.Font.caption(weight: .regular)
|
|
answerLabel.textColor = Theme.Color.textSecondary
|
|
answerLabel.numberOfLines = 0
|
|
answerLabel.isHidden = !isExpanded
|
|
|
|
let questionStack = UIStackView(arrangedSubviews: [questionLabel, chevron])
|
|
questionStack.axis = .horizontal
|
|
questionStack.spacing = 12
|
|
questionStack.alignment = .top
|
|
questionStack.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
let mainStack = UIStackView(arrangedSubviews: [questionStack, answerLabel])
|
|
mainStack.axis = .vertical
|
|
mainStack.spacing = 10
|
|
mainStack.translatesAutoresizingMaskIntoConstraints = false
|
|
addSubview(mainStack)
|
|
|
|
NSLayoutConstraint.activate([
|
|
chevron.widthAnchor.constraint(equalToConstant: 16),
|
|
chevron.heightAnchor.constraint(equalToConstant: 16),
|
|
|
|
mainStack.topAnchor.constraint(equalTo: topAnchor, constant: 16),
|
|
mainStack.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16),
|
|
mainStack.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16),
|
|
mainStack.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -16),
|
|
])
|
|
|
|
let tap = UITapGestureRecognizer(target: self, action: #selector(rowTapped))
|
|
addGestureRecognizer(tap)
|
|
isUserInteractionEnabled = true
|
|
}
|
|
|
|
@objc private func rowTapped() { onToggle?() }
|
|
}
|