Files
bible/Bible-Week/BibleAPI.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

90 lines
2.6 KiB
Swift

//
// BibleAPI.swift
// Bible-Week
//
// Cliente da API da Bíblia (NBV - Nova Bíblia Viva).
//
import Foundation
// MARK: - Modelos
/// Um livro da Bíblia (ex.: Gênesis, João).
struct Book: Decodable, Identifiable, Hashable {
let code: String
let name: String
let testament: String
let bookOrder: Int
let chapterCount: Int
var id: String { code }
enum CodingKeys: String, CodingKey {
case code, name, testament
case bookOrder = "book_order"
case chapterCount = "chapter_count"
}
}
/// Resposta de `GET /v1/books`.
struct BooksResponse: Decodable {
let books: [Book]
}
/// Um versículo com seu número e texto.
struct Verse: Decodable, Identifiable, Hashable {
let verse: Int
let text: String
var id: Int { verse }
}
/// Resposta de um capítulo completo.
struct Chapter: Decodable, Hashable {
let translation: String
let book: String
let bookName: String
let chapter: Int
let verses: [Verse]
}
// MARK: - Serviço
/// Cliente mínimo, somente leitura, para a API da Bíblia.
enum BibleAPI {
static let baseURL = URL(string: "https://bible.falehandix.com.br")!
// NOTA: para produção, mover para um Secrets.xcconfig (fora do controle de
// versão) ou para o Keychain. Mantido aqui apenas para a prova de conceito.
static let apiKey = "3717732ae41aa989ec7e0b30568784d78d23a8e026f5df889d82ae4b54f2986b"
/// Faz um GET na API e decodifica a resposta JSON.
static func get<T: Decodable>(_ path: String, query: [URLQueryItem] = []) async throws -> T {
var components = URLComponents(
url: baseURL.appendingPathComponent(path),
resolvingAgainstBaseURL: false
)!
if !query.isEmpty { components.queryItems = query }
var request = URLRequest(url: components.url!)
request.setValue(apiKey, forHTTPHeaderField: "X-API-Key")
let (data, response) = try await URLSession.shared.data(for: request)
guard let http = response as? HTTPURLResponse, 200..<300 ~= http.statusCode else {
throw URLError(.badServerResponse)
}
return try JSONDecoder().decode(T.self, from: data)
}
/// Busca todos os 66 livros em ordem canônica.
static func books() async throws -> [Book] {
let response: BooksResponse = try await get("/v1/books")
return response.books
}
/// Busca o texto completo de um capítulo.
static func chapter(book code: String, chapter: Int) async throws -> Chapter {
try await get("/v1/books/\(code)/chapters/\(chapter)")
}
}