- 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>
90 lines
2.6 KiB
Swift
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)")
|
|
}
|
|
}
|