// // ReadingReminderService.swift // Bible-Week // // Lembrete diário do Plano de 180 Dias via notificações locais. // Uma única notificação recorrente (UNCalendarNotificationTrigger) com // identificador fixo — nunca 180 notificações individuais. // import Foundation import UserNotifications enum NotificationIdentifier { static let readingPlanDailyReminder = "reading-plan-daily-reminder" } enum NotificationUserInfo { static let destinationKey = "destination" static let destinationReadingPlan = "readingPlan" static let planIdKey = "planId" static let planId = "bible-180" static let screenKey = "screen" static let screenToday = "today" } enum ReadingReminderService { /// Mensagens que incentivam sem pressionar (nada de "streak perdido"). static let motivationalMessages = [ "Separe alguns minutos para a Palavra hoje.", "Continue sua jornada pela Bíblia.", "Um capítulo de cada vez. Vamos continuar?", "Sua leitura de hoje está esperando por você.", "Reserve este momento para sua leitura.", "Continue de onde você parou.", ] // MARK: - Autorização static func authorizationStatus() async -> UNAuthorizationStatus { await UNUserNotificationCenter.current().notificationSettings().authorizationStatus } /// Pede autorização ao iOS. Apenas alerta e som — badge não é usado. static func requestAuthorization() async throws -> Bool { try await UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound]) } // MARK: - Conteúdo /// Monta o conteúdo do lembrete. Com `currentDay`, personaliza o título /// com o dia da jornada; sem ele, usa o título padrão. static func makeContent(currentDay: Int?, soundEnabled: Bool) -> UNMutableNotificationContent { let content = UNMutableNotificationContent() if let currentDay { content.title = "📖 Dia \(currentDay) da sua jornada" } else { content.title = "📖 Sua leitura de hoje" } content.body = motivationalMessages.randomElement() ?? "Continue sua jornada pela Bíblia. Sua leitura de hoje está esperando por você." content.sound = soundEnabled ? .default : nil content.userInfo = [ NotificationUserInfo.destinationKey: NotificationUserInfo.destinationReadingPlan, NotificationUserInfo.planIdKey: NotificationUserInfo.planId, NotificationUserInfo.screenKey: NotificationUserInfo.screenToday, ] return content } // MARK: - Agendamento /// Agenda (ou substitui) o lembrete diário recorrente. Usar sempre o /// mesmo identificador garante que nunca haverá duplicatas. static func scheduleDailyReminder(hour: Int, minute: Int, soundEnabled: Bool, currentDay: Int?) async throws { let content = makeContent(currentDay: currentDay, soundEnabled: soundEnabled) var components = DateComponents() components.hour = hour components.minute = minute let trigger = UNCalendarNotificationTrigger(dateMatching: components, repeats: true) let request = UNNotificationRequest( identifier: NotificationIdentifier.readingPlanDailyReminder, content: content, trigger: trigger ) try await UNUserNotificationCenter.current().add(request) } static func cancelDailyReminder() { UNUserNotificationCenter.current().removePendingNotificationRequests( withIdentifiers: [NotificationIdentifier.readingPlanDailyReminder] ) } /// Sincroniza o agendamento com o estado do plano: agenda se o lembrete /// está habilitado, o plano ativo e a permissão concedida; caso contrário /// cancela. Também atualiza o número do dia no título ao reagendar. @MainActor static func refresh(for plan: ReadingPlanState) async { let shouldSchedule = plan.reminderEnabled && !plan.isPaused && !plan.isCompleted guard shouldSchedule, await authorizationStatus() == .authorized else { cancelDailyReminder() return } try? await scheduleDailyReminder( hour: plan.reminderHour, minute: plan.reminderMinute, soundEnabled: plan.reminderSoundEnabled, currentDay: plan.currentDay ) } }