Files
bible/Bible-Week/PlanSettingsView.swift
Matheus A Silveira f6d9c1891c Adiciona tela Sobre o App com Política de Privacidade e Termos e Condições
- LegalDocumentView: renderiza HTML do bundle usando WebView nativo do WebKit (iOS 26+) com suporte a dark mode
- AboutView: tela com identidade visual do app, links para os documentos legais e contato
- politicadeprivacidade.html / termosecondicoes.html: adicionados ao bundle com CSS para dark mode
- BooksView: botão ⓘ na barra de navegação abre AboutView como sheet
- PlanSettingsView: seção "Legal" com links diretos para os documentos

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-07 14:02:50 -03:00

184 lines
6.7 KiB
Swift

//
// PlanSettingsView.swift
// Bible-Week
//
// Configurações do Plano de 180 Dias: lembrete diário (horário, som),
// pausar/continuar e reiniciar.
//
import SwiftUI
import UserNotifications
struct PlanSettingsView: View {
@Bindable var plan: ReadingPlanState
@Environment(\.dismiss) private var dismiss
@Environment(\.scenePhase) private var scenePhase
@State private var authorizationStatus: UNAuthorizationStatus = .notDetermined
@State private var showRestartConfirmation = false
var body: some View {
NavigationStack {
Form {
remindersSection
planSection
legalSection
}
.navigationTitle("Plano de Leitura")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .confirmationAction) {
Button("OK") { dismiss() }
}
}
// Re-verifica a permissão sempre que o app volta ao primeiro plano
// essencial para refletir mudanças feitas nos Ajustes do iPhone.
.task(id: scenePhase) {
guard scenePhase == .active else { return }
authorizationStatus = await ReadingReminderService.authorizationStatus()
if authorizationStatus == .authorized, plan.reminderEnabled {
await ReadingReminderService.refresh(for: plan)
}
}
.confirmationDialog(
"Reiniciar o plano do dia 1? Seu progresso atual será apagado, mas a preferência de lembrete será mantida.",
isPresented: $showRestartConfirmation,
titleVisibility: .visible
) {
Button("Reiniciar plano", role: .destructive) {
plan.restart()
Task { await ReadingReminderService.refresh(for: plan) }
}
}
}
}
// MARK: - Lembretes
@ViewBuilder
private var remindersSection: some View {
Section("Lembretes") {
if authorizationStatus == .denied {
VStack(alignment: .leading, spacing: 8) {
Text("Notificações desativadas")
.font(.headline)
Text("Para receber lembretes de leitura, habilite as notificações nos Ajustes do iPhone.")
.font(.caption)
.foregroundStyle(.secondary)
Button("Abrir Ajustes") {
openNotificationSettings()
}
}
.padding(.vertical, 4)
} else {
Toggle("Lembrete diário", isOn: reminderEnabledBinding)
if plan.reminderEnabled {
DatePicker(
"Horário",
selection: reminderTimeBinding,
displayedComponents: .hourAndMinute
)
Toggle("Som", isOn: soundEnabledBinding)
}
}
}
.accessibilityElement(children: .contain)
}
/// Ligar pede autorização se necessário; desligar cancela o agendamento.
private var reminderEnabledBinding: Binding<Bool> {
Binding {
plan.reminderEnabled
} set: { newValue in
if newValue {
// Otimista: liga já, para o switch responder ao toque;
// reverte se a autorização for negada.
plan.reminderEnabled = true
Task {
let status = await ReadingReminderService.authorizationStatus()
var granted = status == .authorized || status == .provisional
if status == .notDetermined {
granted = (try? await ReadingReminderService.requestAuthorization()) ?? false
}
if granted {
await ReadingReminderService.refresh(for: plan)
} else {
plan.reminderEnabled = false
authorizationStatus = .denied
}
}
} else {
plan.reminderEnabled = false
ReadingReminderService.cancelDailyReminder()
}
}
}
/// Alterar o horário substitui o agendamento anterior (mesmo identificador,
/// nunca duplica) e persiste o novo valor.
private var reminderTimeBinding: Binding<Date> {
Binding {
Calendar.current.date(
bySettingHour: plan.reminderHour,
minute: plan.reminderMinute,
second: 0,
of: .now
) ?? .now
} set: { newValue in
let components = Calendar.current.dateComponents([.hour, .minute], from: newValue)
plan.reminderHour = components.hour ?? 20
plan.reminderMinute = components.minute ?? 0
Task { await ReadingReminderService.refresh(for: plan) }
}
}
private var soundEnabledBinding: Binding<Bool> {
Binding {
plan.reminderSoundEnabled
} set: { newValue in
plan.reminderSoundEnabled = newValue
Task { await ReadingReminderService.refresh(for: plan) }
}
}
// MARK: - Legal
private var legalSection: some View {
Section("Legal") {
NavigationLink {
LegalDocumentView(
title: "Política de Privacidade",
htmlFileName: "politicadeprivacidade"
)
} label: {
Label("Política de Privacidade", systemImage: "hand.raised.fill")
}
NavigationLink {
LegalDocumentView(
title: "Termos e Condições",
htmlFileName: "termosecondicoes"
)
} label: {
Label("Termos e Condições", systemImage: "doc.text.fill")
}
}
}
// MARK: - Plano
private var planSection: some View {
Section("Plano") {
LabeledContent("Progresso", value: "\(plan.completedDaysCount) de \(PlanSchedule.totalDays) dias")
Button(plan.isPaused ? "Continuar plano" : "Pausar plano") {
plan.isPaused.toggle()
// Pausar remove o agendamento sem apagar a preferência;
// continuar restaura automaticamente o horário salvo.
Task { await ReadingReminderService.refresh(for: plan) }
}
Button("Reiniciar plano", role: .destructive) {
showRestartConfirmation = true
}
}
}
}