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>
This commit is contained in:
2026-09-04 19:16:15 -03:00
parent 80185726af
commit 017f4c022a
3 changed files with 64 additions and 2 deletions

View File

@@ -12,6 +12,7 @@ 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
@@ -29,8 +30,14 @@ struct PlanSettingsView: View {
Button("OK") { dismiss() }
}
}
.task {
// 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.",
@@ -83,6 +90,9 @@ struct PlanSettingsView: View {
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
@@ -90,7 +100,6 @@ struct PlanSettingsView: View {
granted = (try? await ReadingReminderService.requestAuthorization()) ?? false
}
if granted {
plan.reminderEnabled = true
await ReadingReminderService.refresh(for: plan)
} else {
plan.reminderEnabled = false

View File

@@ -226,6 +226,7 @@ struct ReadingPlanView: View {
struct ReminderPromptSheet: View {
@Bindable var plan: ReadingPlanState
@Environment(\.dismiss) private var dismiss
@Environment(\.scenePhase) private var scenePhase
@State private var time: Date = Calendar.current.date(bySettingHour: 20, minute: 0, second: 0, of: .now) ?? .now
@State private var showDeniedInfo = false
@@ -277,6 +278,14 @@ struct ReminderPromptSheet: View {
.padding(.bottom, 16)
.presentationDetents([.large])
.interactiveDismissDisabled(false)
// Se o usuário foi aos Ajustes e habilitou as notificações,
// completa a ativação automaticamente ao voltar para o app.
.task(id: scenePhase) {
guard scenePhase == .active, showDeniedInfo else { return }
if await ReadingReminderService.authorizationStatus() == .authorized {
activateReminder()
}
}
}
private func activateReminder() {

View File

@@ -76,4 +76,48 @@ final class Bible_WeekUITests: XCTestCase {
// Pausa para inspeção visual/screenshot externo.
sleep(4)
}
/// Caminho alternativo: recusar o prompt inicial ("Agora não") e ligar o
/// lembrete depois, pelo toggle nas configurações do plano.
@MainActor
func testEnableReminderLaterFromSettingsToggle() throws {
let app = XCUIApplication()
app.launchArguments += ["--reset-plan"]
app.launch()
app.tabBars.buttons["Plano"].tap()
let startButton = app.buttons["Começar plano"]
XCTAssertTrue(startButton.waitForExistence(timeout: 10), "Botão de começar o plano não apareceu")
startButton.tap()
let laterButton = app.buttons["Agora não"]
XCTAssertTrue(laterButton.waitForExistence(timeout: 5), "Prompt do lembrete não apareceu")
laterButton.tap()
// Abre as configurações pela engrenagem.
let settingsButton = app.buttons["Configurações do plano"]
XCTAssertTrue(settingsButton.waitForExistence(timeout: 5), "Engrenagem não apareceu")
settingsButton.tap()
let toggle = app.switches["Lembrete diário"]
XCTAssertTrue(toggle.waitForExistence(timeout: 5), "Toggle do lembrete não apareceu")
toggle.switches.firstMatch.tap()
// Alerta de permissão do iOS (aparece se ainda não determinada).
let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")
let allowButton = springboard.buttons["Allow"].exists
? springboard.buttons["Allow"]
: springboard.buttons["Permitir"]
if allowButton.waitForExistence(timeout: 5) {
allowButton.tap()
}
// O toggle precisa ficar ligado.
let turnedOn = NSPredicate(format: "value == '1'")
let onExpectation = expectation(for: turnedOn, evaluatedWith: toggle)
wait(for: [onExpectation], timeout: 10)
// E o horário padrão deve aparecer.
XCTAssertTrue(app.staticTexts["Horário"].waitForExistence(timeout: 5), "Campo de horário não apareceu")
}
}