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:
113
Bible-Week/TrackReaderView.swift
Normal file
113
Bible-Week/TrackReaderView.swift
Normal file
@@ -0,0 +1,113 @@
|
||||
//
|
||||
// TrackReaderView.swift
|
||||
// Bible-Week
|
||||
//
|
||||
// Leitura de uma trilha do dia: carrega os capítulos em sequência
|
||||
// e permite marcar a trilha como concluída.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct TrackReaderView: View {
|
||||
let day: Int
|
||||
let reading: TrackReading
|
||||
@Bindable var plan: ReadingPlanState
|
||||
|
||||
@State private var chapters: [Chapter] = []
|
||||
@State private var isLoading = false
|
||||
@State private var errorMessage: String?
|
||||
|
||||
private var isCompleted: Bool {
|
||||
plan.isCompleted(day: day, track: reading.track)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
if isLoading {
|
||||
ProgressView("Carregando leitura...")
|
||||
} else if let errorMessage {
|
||||
ContentUnavailableView {
|
||||
Label("Erro ao carregar", systemImage: "exclamationmark.triangle")
|
||||
} description: {
|
||||
Text(errorMessage)
|
||||
} actions: {
|
||||
Button("Tentar novamente") { Task { await load() } }
|
||||
}
|
||||
} else {
|
||||
content
|
||||
}
|
||||
}
|
||||
.navigationTitle(reading.label)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.safeAreaInset(edge: .bottom) {
|
||||
markButton
|
||||
}
|
||||
.task { await load() }
|
||||
}
|
||||
|
||||
private var content: some View {
|
||||
ScrollView {
|
||||
LazyVStack(alignment: .leading, spacing: 16) {
|
||||
ForEach(chapters, id: \.self) { chapter in
|
||||
Text("\(chapter.bookName) \(chapter.chapter)")
|
||||
.font(.title3.bold())
|
||||
.padding(.top, 8)
|
||||
ForEach(chapter.verses) { verse in
|
||||
Text("\(Text("\(verse.verse)").font(.caption2).foregroundColor(.accentColor).baselineOffset(4)) \(verse.text)")
|
||||
.font(.body)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
}
|
||||
|
||||
private var markButton: some View {
|
||||
Button {
|
||||
toggleCompletion()
|
||||
} label: {
|
||||
Label(
|
||||
isCompleted ? "Concluído" : "Marcar como concluído",
|
||||
systemImage: isCompleted ? "checkmark.circle.fill" : "circle"
|
||||
)
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
.tint(isCompleted ? .green : .accentColor)
|
||||
.controlSize(.large)
|
||||
.padding()
|
||||
.background(.bar)
|
||||
.accessibilityLabel(
|
||||
isCompleted
|
||||
? "\(reading.track.displayName) concluída. Toque para desmarcar."
|
||||
: "Marcar \(reading.track.displayName) como concluída"
|
||||
)
|
||||
}
|
||||
|
||||
private func toggleCompletion() {
|
||||
withAnimation {
|
||||
plan.setCompleted(!isCompleted, day: day, track: reading.track)
|
||||
}
|
||||
if plan.isCompleted {
|
||||
// Plano 180/180 concluído: remove o lembrete diário.
|
||||
ReadingReminderService.cancelDailyReminder()
|
||||
}
|
||||
}
|
||||
|
||||
private func load() async {
|
||||
isLoading = true
|
||||
errorMessage = nil
|
||||
defer { isLoading = false }
|
||||
do {
|
||||
var loaded: [Chapter] = []
|
||||
for ref in reading.chapters {
|
||||
loaded.append(try await BibleAPI.chapter(book: ref.bookCode, chapter: ref.chapter))
|
||||
}
|
||||
chapters = loaded
|
||||
} catch {
|
||||
errorMessage = "Verifique sua conexão e tente novamente."
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user