- 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>
160 lines
5.9 KiB
Swift
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
|
|
}
|
|
}
|
|
}
|
|
}
|