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:
89
Bible-Week/BibleAPI.swift
Normal file
89
Bible-Week/BibleAPI.swift
Normal file
@@ -0,0 +1,89 @@
|
||||
//
|
||||
// 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)")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user