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>
This commit is contained in:
114
Bible-Week/ReadingPlanSchedule.swift
Normal file
114
Bible-Week/ReadingPlanSchedule.swift
Normal file
@@ -0,0 +1,114 @@
|
||||
//
|
||||
// ReadingPlanSchedule.swift
|
||||
// Bible-Week
|
||||
//
|
||||
// Plano "Bíblia em 180 Dias": três trilhas paralelas por dia
|
||||
// (História, Sabedoria e Profetas, Novo Testamento), distribuindo os
|
||||
// capítulos de cada trilha uniformemente pelos 180 dias.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// As três trilhas de leitura diária do plano.
|
||||
enum PlanTrack: String, CaseIterable, Codable, Sendable {
|
||||
case history = "history"
|
||||
case wisdom = "wisdom"
|
||||
case newTestament = "nt"
|
||||
|
||||
var displayName: String {
|
||||
switch self {
|
||||
case .history: "História"
|
||||
case .wisdom: "Sabedoria e Profetas"
|
||||
case .newTestament: "Novo Testamento"
|
||||
}
|
||||
}
|
||||
|
||||
/// Livros da trilha, em ordem canônica.
|
||||
var books: [Book] {
|
||||
switch self {
|
||||
case .history: BibleCanon.books(in: 1...17) // Gênesis...Ester
|
||||
case .wisdom: BibleCanon.books(in: 18...39) // Jó...Malaquias
|
||||
case .newTestament: BibleCanon.books(in: 40...66) // Mateus...Apocalipse
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Referência a um capítulo específico de um livro.
|
||||
struct ChapterRef: Hashable, Sendable {
|
||||
let bookCode: String
|
||||
let bookName: String
|
||||
let chapter: Int
|
||||
}
|
||||
|
||||
/// Os capítulos de uma trilha em um dia do plano.
|
||||
struct TrackReading: Hashable, Sendable, Identifiable {
|
||||
let track: PlanTrack
|
||||
let chapters: [ChapterRef]
|
||||
|
||||
var id: PlanTrack { track }
|
||||
|
||||
/// Rótulo legível, ex.: "Gênesis 34–36" ou "Obadias 1 e Jonas 1–2".
|
||||
var label: String {
|
||||
// Agrupa capítulos consecutivos do mesmo livro em intervalos.
|
||||
var runs: [(name: String, first: Int, last: Int)] = []
|
||||
for ref in chapters {
|
||||
if var last = runs.last, last.name == ref.bookName, last.last == ref.chapter - 1 {
|
||||
last.last = ref.chapter
|
||||
runs[runs.count - 1] = last
|
||||
} else {
|
||||
runs.append((ref.bookName, ref.chapter, ref.chapter))
|
||||
}
|
||||
}
|
||||
let parts = runs.map { run in
|
||||
run.first == run.last ? "\(run.name) \(run.first)" : "\(run.name) \(run.first)–\(run.last)"
|
||||
}
|
||||
return parts.joined(separator: " e ")
|
||||
}
|
||||
}
|
||||
|
||||
/// A leitura completa de um dia (as três trilhas).
|
||||
struct DayReading: Sendable {
|
||||
let day: Int
|
||||
let readings: [TrackReading]
|
||||
|
||||
func reading(for track: PlanTrack) -> TrackReading? {
|
||||
readings.first { $0.track == track }
|
||||
}
|
||||
}
|
||||
|
||||
/// Cronograma fixo e determinístico do plano de 180 dias.
|
||||
enum PlanSchedule {
|
||||
static let totalDays = 180
|
||||
|
||||
static let days: [DayReading] = generate()
|
||||
|
||||
/// Leitura do dia `n` (1-based, limitado a 1...180).
|
||||
static func day(_ n: Int) -> DayReading {
|
||||
days[max(1, min(n, totalDays)) - 1]
|
||||
}
|
||||
|
||||
private static func generate() -> [DayReading] {
|
||||
let trackChapters: [PlanTrack: [ChapterRef]] = Dictionary(
|
||||
uniqueKeysWithValues: PlanTrack.allCases.map { track in
|
||||
let refs = track.books.flatMap { book in
|
||||
(1...book.chapterCount).map {
|
||||
ChapterRef(bookCode: book.code, bookName: book.name, chapter: $0)
|
||||
}
|
||||
}
|
||||
return (track, refs)
|
||||
}
|
||||
)
|
||||
|
||||
return (0..<totalDays).map { index in
|
||||
let readings = PlanTrack.allCases.map { track -> TrackReading in
|
||||
let all = trackChapters[track]!
|
||||
// Partição uniforme: o dia i recebe as posições
|
||||
// [i*T/180, (i+1)*T/180), cobrindo tudo sem sobreposição.
|
||||
let start = index * all.count / totalDays
|
||||
let end = (index + 1) * all.count / totalDays
|
||||
return TrackReading(track: track, chapters: Array(all[start..<end]))
|
||||
}
|
||||
return DayReading(day: index + 1, readings: readings)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user