Files
bible/Bible-Week/ReadingPlanState.swift
Matheus A Silveira 80185726af Adiciona Plano Bíblia em 180 Dias com lembrete diário e ícone do app
- Leitor bíblico consumindo a API NBV (bible.falehandix.com.br): lista de
  livros por testamento e leitura de capítulos
- Plano de 180 dias com três trilhas diárias (História, Sabedoria e
  Profetas, Novo Testamento) geradas deterministicamente sobre o cânon
  embutido (1.189 capítulos, cobertura testada)
- Lembrete diário via notificação local recorrente (UNCalendarNotificationTrigger,
  identificador fixo, sem duplicatas), com pedido de autorização em contexto,
  configurações de horário/som, e deep link da notificação para a Leitura de Hoje
- Estado do plano em SwiftData: progresso, pausa/retomada, reinício e
  conclusão preservando preferências do lembrete
- 15 testes unitários (Testing) e 2 testes de UI (XCUIAutomation)
- Ícone do app a partir do logo biblia.png

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-04 18:54:48 -03:00

95 lines
2.5 KiB
Swift

//
// ReadingPlanState.swift
// Bible-Week
//
// Estado persistente do Plano Bíblia em 180 Dias: progresso de leitura
// e preferências do lembrete diário.
//
import Foundation
import SwiftData
@Model
final class ReadingPlanState {
var startedAt: Date
var isPaused: Bool
/// Chaves "dia-trilha" das leituras concluídas, ex.: "34-nt".
var completedKeys: [String]
// Preferências do lembrete diário. Sobrevivem a pausa e reinício do plano.
var reminderEnabled: Bool
var reminderHour: Int
var reminderMinute: Int
var reminderSoundEnabled: Bool
var hasSeenReminderPrompt: Bool
init(startedAt: Date = .now) {
self.startedAt = startedAt
self.isPaused = false
self.completedKeys = []
self.reminderEnabled = false
self.reminderHour = 20
self.reminderMinute = 0
self.reminderSoundEnabled = true
self.hasSeenReminderPrompt = false
}
// MARK: - Progresso
private static func key(day: Int, track: PlanTrack) -> String {
"\(day)-\(track.rawValue)"
}
func isCompleted(day: Int, track: PlanTrack) -> Bool {
completedKeys.contains(Self.key(day: day, track: track))
}
func setCompleted(_ completed: Bool, day: Int, track: PlanTrack) {
let key = Self.key(day: day, track: track)
if completed {
if !completedKeys.contains(key) { completedKeys.append(key) }
} else {
completedKeys.removeAll { $0 == key }
}
}
func isDayCompleted(_ day: Int) -> Bool {
PlanTrack.allCases.allSatisfy { isCompleted(day: day, track: $0) }
}
/// Primeiro dia com leitura pendente (1...180). Se tudo concluído, 180.
var currentDay: Int {
(1...PlanSchedule.totalDays).first { !isDayCompleted($0) } ?? PlanSchedule.totalDays
}
var completedDaysCount: Int {
(1...PlanSchedule.totalDays).count { isDayCompleted($0) }
}
var isCompleted: Bool {
completedDaysCount == PlanSchedule.totalDays
}
/// Fração concluída do plano (0...1).
var progress: Double {
Double(completedDaysCount) / Double(PlanSchedule.totalDays)
}
// MARK: - Lembrete
/// Horário do lembrete formatado, ex.: "20:00".
var reminderTimeText: String {
String(format: "%02d:%02d", reminderHour, reminderMinute)
}
// MARK: - Ciclo de vida do plano
/// Recomeça o plano do dia 1, preservando as preferências de lembrete.
func restart() {
completedKeys = []
startedAt = .now
isPaused = false
}
}