From 017f4c022aac38023b3f132e11d0644df2266326 Mon Sep 17 00:00:00 2001 From: Matheus A Silveira Date: Fri, 4 Sep 2026 19:16:15 -0300 Subject: [PATCH] =?UTF-8?q?Corrige=20toggle=20do=20lembrete=20di=C3=A1rio?= =?UTF-8?q?=20que=20n=C3=A3o=20ligava?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- Bible-Week/PlanSettingsView.swift | 13 +++++-- Bible-Week/ReadingPlanView.swift | 9 +++++ Bible-WeekUITests/Bible_WeekUITests.swift | 44 +++++++++++++++++++++++ 3 files changed, 64 insertions(+), 2 deletions(-) diff --git a/Bible-Week/PlanSettingsView.swift b/Bible-Week/PlanSettingsView.swift index 1014c91..24169b7 100644 --- a/Bible-Week/PlanSettingsView.swift +++ b/Bible-Week/PlanSettingsView.swift @@ -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 diff --git a/Bible-Week/ReadingPlanView.swift b/Bible-Week/ReadingPlanView.swift index bc627b7..ce9d5d5 100644 --- a/Bible-Week/ReadingPlanView.swift +++ b/Bible-Week/ReadingPlanView.swift @@ -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() { diff --git a/Bible-WeekUITests/Bible_WeekUITests.swift b/Bible-WeekUITests/Bible_WeekUITests.swift index fec0021..1ff356e 100644 --- a/Bible-WeekUITests/Bible_WeekUITests.swift +++ b/Bible-WeekUITests/Bible_WeekUITests.swift @@ -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") + } }