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:
2026-09-04 18:54:48 -03:00
parent 4d36df488f
commit 80185726af
19 changed files with 1583 additions and 110 deletions

View File

@@ -7,26 +7,29 @@
import SwiftUI
import SwiftData
import UserNotifications
@main
struct Bible_WeekApp: App {
var sharedModelContainer: ModelContainer = {
let schema = Schema([
Item.self,
])
let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)
@State private var router: AppRouter
// Referência forte: o UNUserNotificationCenter guarda o delegate como weak.
private let notificationCoordinator: NotificationCoordinator
do {
return try ModelContainer(for: schema, configurations: [modelConfiguration])
} catch {
fatalError("Could not create ModelContainer: \(error)")
}
}()
init() {
let router = AppRouter()
let coordinator = NotificationCoordinator(router: router)
// Registrado no launch para o toque na notificação funcionar
// também quando o app estava fechado.
UNUserNotificationCenter.current().delegate = coordinator
self.notificationCoordinator = coordinator
self._router = State(initialValue: router)
}
var body: some Scene {
WindowGroup {
ContentView()
.environment(router)
}
.modelContainer(sharedModelContainer)
.modelContainer(for: ReadingPlanState.self)
}
}