// // ReadingPlanView.swift // Bible-Week // // Aba Plano: Bíblia em 180 Dias. Mostra a Leitura de Hoje com as três // trilhas, progresso e o lembrete diário. // import SwiftUI import SwiftData struct ReadingPlanView: View { @Environment(\.modelContext) private var modelContext @Environment(\.scenePhase) private var scenePhase @Query private var planStates: [ReadingPlanState] @State private var showReminderPrompt = false @State private var showSettings = false private var plan: ReadingPlanState? { planStates.first } var body: some View { NavigationStack { Group { if let plan { if plan.isCompleted { completedView(plan) } else if plan.isPaused { pausedView(plan) } else { activeView(plan) } } else { startView } } .navigationTitle("Bíblia em 180 Dias") .toolbar { if plan != nil { ToolbarItem(placement: .topBarTrailing) { Button { showSettings = true } label: { Label("Configurações do plano", systemImage: "gearshape") } } } } .sheet(isPresented: $showReminderPrompt) { if let plan { ReminderPromptSheet(plan: plan) } } .sheet(isPresented: $showSettings) { if let plan { PlanSettingsView(plan: plan) } } } // Ao voltar ao app, ressincroniza o agendamento (atualiza o número // do dia no título da notificação e respeita pausa/conclusão). .task(id: scenePhase) { if scenePhase == .active, let plan { await ReadingReminderService.refresh(for: plan) } } } // MARK: - Plano não iniciado private var startView: some View { VStack(spacing: 20) { Image(systemName: "book.pages") .font(.system(size: 56)) .foregroundStyle(.tint) Text("Leia a Bíblia inteira em 180 dias") .font(.title3.bold()) .multilineTextAlignment(.center) Text("Todos os dias, uma leitura de História, uma de Sabedoria e Profetas e uma do Novo Testamento.") .multilineTextAlignment(.center) .foregroundStyle(.secondary) Button("Começar plano") { startPlan() } .buttonStyle(.borderedProminent) .controlSize(.large) } .padding(32) } private func startPlan() { let state = ReadingPlanState() modelContext.insert(state) // Pedido de autorização acontece em contexto, não no launch: // só depois que o usuário decidiu começar a jornada. showReminderPrompt = true } // MARK: - Plano ativo private func activeView(_ plan: ReadingPlanState) -> some View { let today = PlanSchedule.day(plan.currentDay) return List { Section { VStack(alignment: .leading, spacing: 12) { Text("Dia \(plan.currentDay) de \(PlanSchedule.totalDays)") .font(.title2.bold()) ProgressView(value: plan.progress) { EmptyView() } currentValueLabel: { Text("\(Int((plan.progress * 100).rounded()))% concluído") .font(.caption) .foregroundStyle(.secondary) } reminderChip(plan) } .padding(.vertical, 4) } Section("Leitura de Hoje") { ForEach(today.readings) { reading in trackRow(reading, day: today.day, plan: plan) } } } } private func reminderChip(_ plan: ReadingPlanState) -> some View { Button { // Alteração rápida: o chip leva direto às configurações. showSettings = true } label: { HStack(spacing: 6) { Image(systemName: plan.reminderEnabled ? "bell.fill" : "bell.slash") Text(plan.reminderEnabled ? "Lembrete às \(plan.reminderTimeText)" : "Sem lembrete") } .font(.caption.weight(.medium)) .padding(.horizontal, 10) .padding(.vertical, 6) .background(Capsule().fill(Color(.secondarySystemBackground))) } .buttonStyle(.plain) .accessibilityLabel( plan.reminderEnabled ? "Lembrete diário ativado para \(plan.reminderHour) horas e \(plan.reminderMinute) minutos. Toque para alterar." : "Lembrete diário desativado. Toque para configurar." ) } private func trackRow(_ reading: TrackReading, day: Int, plan: ReadingPlanState) -> some View { let done = plan.isCompleted(day: day, track: reading.track) return NavigationLink { TrackReaderView(day: day, reading: reading, plan: plan) } label: { HStack(spacing: 12) { Button { toggleCompletion(reading, day: day, plan: plan) } label: { Image(systemName: done ? "checkmark.circle.fill" : "circle") .font(.title3) .foregroundStyle(done ? Color.accentColor : Color.secondary) } .buttonStyle(.borderless) .accessibilityLabel(done ? "\(reading.track.displayName) concluída. Toque para desmarcar." : "Marcar \(reading.track.displayName) como concluída") VStack(alignment: .leading, spacing: 2) { Text(reading.track.displayName) .font(.caption) .foregroundStyle(.secondary) Text(reading.label) .strikethrough(done) .foregroundStyle(done ? .secondary : .primary) } } } } private func toggleCompletion(_ reading: TrackReading, day: Int, plan: ReadingPlanState) { let done = plan.isCompleted(day: day, track: reading.track) withAnimation { plan.setCompleted(!done, day: day, track: reading.track) } if plan.isCompleted { // 180/180: não continuar lembrando depois que o usuário terminou. ReadingReminderService.cancelDailyReminder() } } // MARK: - Plano pausado private func pausedView(_ plan: ReadingPlanState) -> some View { ContentUnavailableView { Label("Plano pausado", systemImage: "pause.circle") } description: { Text("Seu progresso está guardado: \(plan.completedDaysCount) de \(PlanSchedule.totalDays) dias concluídos.") } actions: { Button("Continuar plano") { plan.isPaused = false Task { await ReadingReminderService.refresh(for: plan) } } .buttonStyle(.borderedProminent) } } // MARK: - Plano concluído private func completedView(_ plan: ReadingPlanState) -> some View { ContentUnavailableView { Label("Você concluiu a Bíblia!", systemImage: "checkmark.seal.fill") } description: { Text("180 de 180 dias. Parabéns pela jornada completa pela Palavra.") } actions: { Button("Reiniciar plano") { plan.restart() Task { await ReadingReminderService.refresh(for: plan) } } .buttonStyle(.borderedProminent) } } } // MARK: - Prompt contextual do lembrete /// Apresentado uma única vez, logo após o usuário iniciar o plano. /// A autorização do iOS só é pedida quando ele toca em "Ativar lembrete". struct ReminderPromptSheet: View { @Bindable var plan: ReadingPlanState @Environment(\.dismiss) private var dismiss @State private var time: Date = Calendar.current.date(bySettingHour: 20, minute: 0, second: 0, of: .now) ?? .now @State private var showDeniedInfo = false var body: some View { VStack(spacing: 20) { Image(systemName: "bell.badge") .font(.system(size: 44)) .foregroundStyle(.tint) .padding(.top, 24) Text("Quer receber um lembrete diário?") .font(.title3.bold()) .multilineTextAlignment(.center) Text("Reserve um momento todos os dias para continuar sua jornada pela Bíblia.") .multilineTextAlignment(.center) .foregroundStyle(.secondary) DatePicker("Horário", selection: $time, displayedComponents: .hourAndMinute) .datePickerStyle(.wheel) .labelsHidden() if showDeniedInfo { VStack(spacing: 8) { Text("Notificações desativadas") .font(.headline) Text("Para receber lembretes de leitura, habilite as notificações nos Ajustes do iPhone.") .font(.caption) .multilineTextAlignment(.center) .foregroundStyle(.secondary) Button("Abrir Ajustes") { openNotificationSettings() } } } else { Button("Ativar lembrete") { activateReminder() } .buttonStyle(.borderedProminent) .controlSize(.large) } Button("Agora não") { plan.hasSeenReminderPrompt = true dismiss() } .foregroundStyle(.secondary) } .padding(.horizontal, 24) .padding(.bottom, 16) .presentationDetents([.large]) .interactiveDismissDisabled(false) } private func activateReminder() { plan.hasSeenReminderPrompt = true let components = Calendar.current.dateComponents([.hour, .minute], from: time) plan.reminderHour = components.hour ?? 20 plan.reminderMinute = components.minute ?? 0 Task { let granted = (try? await ReadingReminderService.requestAuthorization()) ?? false if granted { plan.reminderEnabled = true await ReadingReminderService.refresh(for: plan) dismiss() } else { plan.reminderEnabled = false showDeniedInfo = true } } } } /// Abre os ajustes de notificação do app usando o mecanismo oficial do iOS. @MainActor func openNotificationSettings() { guard let url = URL(string: UIApplication.openNotificationSettingsURLString) else { return } UIApplication.shared.open(url) } #Preview { ReadingPlanView() .environment(AppRouter()) .modelContainer(for: ReadingPlanState.self, inMemory: true) }