Files
Gotcha/ios/Sources/SettingsViewController.swift
2026-08-15 20:06:52 +02:00

161 lines
6.9 KiB
Swift

import UIKit
@MainActor
final class SettingsViewController: UITableViewController {
private let context: AppContext
private let appearanceControl = UISegmentedControl(items: ["Auto", "Light", "Dark"])
private let notificationSwitch = UISwitch()
private var notificationStatus = "Managed by iOS"
init(context: AppContext) {
self.context = context
super.init(style: .insetGrouped)
title = "Settings"
}
@available(*, unavailable)
required init?(coder: NSCoder) { fatalError("init(coder:) is not supported") }
override func viewDidLoad() {
super.viewDidLoad()
let settings = context.core.settings()
appearanceControl.selectedSegmentIndex = Int(settings.appearance)
appearanceControl.addTarget(self, action: #selector(appearanceChanged), for: .valueChanged)
notificationSwitch.isOn = settings.notificationsEnabled
notificationSwitch.accessibilityLabel = "Background notifications"
notificationSwitch.addTarget(self, action: #selector(notificationsChanged), for: .valueChanged)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
notificationSwitch.isOn = context.core.settings().notificationsEnabled
tableView.reloadSections(IndexSet(integer: 1), with: .none)
Task {
notificationStatus = await context.notifications.authorizationDescription()
updateNotificationSettingsCell()
}
}
override func numberOfSections(in tableView: UITableView) -> Int { 3 }
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
section == 2 ? 2 : 1
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
["Appearance", "Navigation", "Notifications"][section]
}
override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
if section == 0 {
return "Follow iOS automatically or choose a fixed appearance."
}
if section == 1 {
return "Home is always present. Choose and order up to four additional destinations."
}
return "Gotcha checks periodically for server notifications. iOS decides when background refresh runs; delivery style, sounds, Focus, and summaries remain under your control in iOS Settings."
}
override func tableView(
_ tableView: UITableView,
cellForRowAt indexPath: IndexPath
) -> UITableViewCell {
if indexPath.section == 1 {
let cell = UITableViewCell(style: .value1, reuseIdentifier: nil)
var content = cell.defaultContentConfiguration()
content.text = "Primary Destinations"
content.secondaryText = "\(context.primaryDestinations.count) selected"
cell.contentConfiguration = content
cell.accessoryType = .disclosureIndicator
return cell
}
guard indexPath.section == 0 else {
if indexPath.row == 0 {
let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
var content = cell.defaultContentConfiguration()
content.text = "Background notifications"
cell.contentConfiguration = content
cell.accessoryView = notificationSwitch
cell.selectionStyle = .none
return cell
}
let cell = UITableViewCell(style: .value1, reuseIdentifier: nil)
var content = cell.defaultContentConfiguration()
content.text = "Notification Settings"
content.secondaryText = notificationStatus
cell.contentConfiguration = content
cell.accessoryType = .disclosureIndicator
return cell
}
let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
appearanceControl.translatesAutoresizingMaskIntoConstraints = false
cell.contentView.addSubview(appearanceControl)
NSLayoutConstraint.activate([
appearanceControl.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor, constant: 16),
appearanceControl.trailingAnchor.constraint(equalTo: cell.contentView.trailingAnchor, constant: -16),
appearanceControl.topAnchor.constraint(equalTo: cell.contentView.topAnchor, constant: 8),
appearanceControl.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor, constant: -8),
])
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
if indexPath.section == 1 {
navigationController?.pushViewController(
NavigationSettingsViewController(context: context),
animated: true
)
} else if indexPath.section == 2, indexPath.row == 1 {
context.notifications.openSystemSettings()
}
}
@objc private func appearanceChanged() {
do {
try context.core.setAppearance(index: UInt32(appearanceControl.selectedSegmentIndex))
context.applyAppearance()
} catch {
show(error: error)
}
}
@objc private func notificationsChanged() {
let requested = notificationSwitch.isOn
notificationSwitch.isEnabled = false
Task {
do {
let enabled = try await context.notifications.setEnabled(requested)
notificationSwitch.isOn = enabled
if requested && !enabled { showNotificationsDisabledAlert() }
} catch {
notificationSwitch.isOn = context.core.settings().notificationsEnabled
show(error: error)
}
notificationSwitch.isEnabled = true
notificationStatus = await context.notifications.authorizationDescription()
updateNotificationSettingsCell()
}
}
private func updateNotificationSettingsCell() {
guard let cell = tableView.cellForRow(at: IndexPath(row: 1, section: 2)) else { return }
var content = cell.defaultContentConfiguration()
content.text = "Notification Settings"
content.secondaryText = notificationStatus
cell.contentConfiguration = content
}
private func showNotificationsDisabledAlert() {
let alert = UIAlertController(
title: "Notifications Are Disabled",
message: "Allow notifications in iOS Settings, then turn Background notifications on again.",
preferredStyle: .alert
)
alert.addAction(UIAlertAction(title: "Not Now", style: .cancel))
alert.addAction(UIAlertAction(title: "Open Settings", style: .default) { [weak self] _ in
self?.context.notifications.openSystemSettings()
})
present(alert, animated: true)
}
}