Files
bible/Bible-Week/PlanSettingsView.swift
Matheus A Silveira 017f4c022a Corrige toggle do lembrete diário que não ligava
- Re-verifica a permissão de notificações quando o app volta ao primeiro
  plano (scenePhase), refletindo mudanças feitas nos Ajustes do iPhone
- Toggle otimista: liga imediatamente ao toque e reverte apenas se a
  autorização for negada
- Prompt inicial completa a ativação sozinho ao voltar dos Ajustes com
  a permissão concedida
- Novo teste de UI: ativar o lembrete pelo toggle das configurações

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-04 19:16:15 -03:00

160 lines
5.9 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
}
.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: - 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
}
}
}
}