RTSDA-iOS/Services/ChurchDataService.swift
RTSDA 00679f927c docs: Update README for v2.0 release and fix git remote URL
- Comprehensive README update documenting v2.0 architectural changes
- Updated git remote to ssh://rockvilleav@git.rockvilletollandsda.church:10443/RTSDA/RTSDA-iOS.git
- Documented unified ChurchService and 60% code reduction
- Added new features: Home Feed, responsive reading, enhanced UI
- Corrected license information (GPL v3 with church content copyright)
- Updated build instructions and technical stack details
2025-08-16 18:41:51 -04:00

404 lines
13 KiB
Swift

import Foundation
import Observation
// MARK: - Media Type Enum
enum MediaType: String, CaseIterable {
case sermons = "sermons"
case livestreams = "livestreams"
var displayName: String {
switch self {
case .sermons:
return "Sermons"
case .livestreams:
return "Live Archives"
}
}
var icon: String {
switch self {
case .sermons:
return "play.rectangle.fill"
case .livestreams:
return "dot.radiowaves.left.and.right"
}
}
}
@Observable
class ChurchDataService {
// MARK: - Properties
// State
var isLoading = false
var error: Error?
// Content
var events: [ChurchEvent] = []
var sermons: [Sermon] = []
var livestreamArchives: [Sermon] = []
var bulletins: [ChurchBulletin] = []
var dailyVerse: BibleVerse?
// Stream status
var isStreamLive = false
// Media Type Selection
var currentMediaType: MediaType = .sermons
// Feed
var feedItems: [FeedItem] = []
// MARK: - Public Methods
/// Load all content for the home feed
func loadHomeFeed() async {
await withTaskGroup(of: Void.self) { group in
group.addTask { await self.loadEvents() }
group.addTask { await self.loadRecentSermons() }
group.addTask { await self.loadRecentBulletins() }
group.addTask { await self.loadDailyVerse() }
group.addTask { await self.loadStreamStatus() }
}
await updateFeed()
}
/// Load upcoming events (for home feed - limited)
func loadEvents() async {
do {
isLoading = true
error = nil
let eventsJson = fetchEventsJson()
// Use Rust function for JSON parsing - NO business logic in Swift!
let eventsArrayJson = parseEventsFromJson(eventsJson: eventsJson)
let data = eventsArrayJson.data(using: .utf8) ?? Data()
let allEvents = try JSONDecoder().decode([ChurchEvent].self, from: data)
await MainActor.run {
// For home feed, limit to 5 events
self.events = Array(allEvents.prefix(5))
self.isLoading = false
}
} catch {
print("❌ Failed to load events: \(error)")
await MainActor.run {
self.error = error
self.isLoading = false
}
}
}
/// Load all events (for events list view)
func loadAllEvents() async {
do {
isLoading = true
error = nil
let eventsJson = fetchEventsJson()
// Use Rust function for JSON parsing - NO business logic in Swift!
let eventsArrayJson = parseEventsFromJson(eventsJson: eventsJson)
let data = eventsArrayJson.data(using: .utf8) ?? Data()
let allEvents = try JSONDecoder().decode([ChurchEvent].self, from: data)
await MainActor.run {
self.events = allEvents
self.isLoading = false
}
} catch {
print("❌ Failed to load all events: \(error)")
await MainActor.run {
self.error = error
self.isLoading = false
}
}
}
/// Load recent sermons
func loadRecentSermons() async {
do {
let sermonsJson = fetchSermonsJson()
// Use Rust function for JSON parsing - NO business logic in Swift!
let validatedJson = parseSermonsFromJson(sermonsJson: sermonsJson)
let data = validatedJson.data(using: .utf8) ?? Data()
let sermonList = try JSONDecoder().decode([Sermon].self, from: data)
await MainActor.run {
// Get the 5 most recent sermons
self.sermons = Array(sermonList.prefix(5))
}
} catch {
await MainActor.run {
self.error = error
}
}
}
/// Load all sermons (for Watch tab)
func loadAllSermons() async {
do {
isLoading = true
error = nil
let sermonsJson = fetchSermonsJson()
// Use Rust function for JSON parsing - NO business logic in Swift!
let validatedJson = parseSermonsFromJson(sermonsJson: sermonsJson)
let data = validatedJson.data(using: .utf8) ?? Data()
let sermonList = try JSONDecoder().decode([Sermon].self, from: data)
await MainActor.run {
self.sermons = sermonList
self.isLoading = false
}
} catch {
print("❌ Failed to load sermons: \(error)")
await MainActor.run {
self.error = error
self.isLoading = false
}
}
}
/// Load all livestream archives (for Watch tab)
func loadAllLivestreamArchives() async {
do {
isLoading = true
error = nil
let archivesJson = fetchLivestreamArchiveJson()
// Use Rust function for JSON parsing - NO business logic in Swift!
let validatedJson = parseSermonsFromJson(sermonsJson: archivesJson)
let data = validatedJson.data(using: .utf8) ?? Data()
let archiveList = try JSONDecoder().decode([Sermon].self, from: data)
await MainActor.run {
self.livestreamArchives = archiveList
self.isLoading = false
}
} catch {
print("❌ Failed to load livestream archives: \(error)")
await MainActor.run {
self.error = error
self.isLoading = false
}
}
}
/// Load content based on current media type
func loadCurrentMediaType() async {
switch currentMediaType {
case .sermons:
await loadAllSermons()
case .livestreams:
await loadAllLivestreamArchives()
}
}
/// Switch media type and load appropriate content
func switchMediaType(to newType: MediaType) async {
await MainActor.run {
self.currentMediaType = newType
}
await loadCurrentMediaType()
}
/// Get current content based on media type
var currentContent: [Sermon] {
switch currentMediaType {
case .sermons:
return sermons
case .livestreams:
return livestreamArchives
}
}
/// Load recent bulletins
func loadRecentBulletins() async {
do {
let bulletinsJson = fetchBulletinsJson()
// Use Rust function for JSON parsing - NO business logic in Swift!
let validatedJson = parseBulletinsFromJson(bulletinsJson: bulletinsJson)
let data = validatedJson.data(using: .utf8) ?? Data()
let bulletinList = try JSONDecoder().decode([ChurchBulletin].self, from: data)
await MainActor.run {
// Get the 3 most recent bulletins
self.bulletins = Array(bulletinList.prefix(3))
}
} catch {
await MainActor.run {
self.error = error
}
}
}
/// Load all bulletins (for Discover tab)
func loadAllBulletins() async {
do {
isLoading = true
error = nil
let bulletinsJson = fetchBulletinsJson()
// Use Rust function for JSON parsing - NO business logic in Swift!
let validatedJson = parseBulletinsFromJson(bulletinsJson: bulletinsJson)
let data = validatedJson.data(using: .utf8) ?? Data()
let bulletinList = try JSONDecoder().decode([ChurchBulletin].self, from: data)
await MainActor.run {
self.bulletins = bulletinList
self.isLoading = false
}
} catch {
await MainActor.run {
self.error = error
self.isLoading = false
}
}
}
/// Load daily Bible verse
func loadDailyVerse() async {
do {
let verseJson = fetchRandomBibleVerseJson()
// Use Rust function for JSON parsing - NO business logic in Swift!
let validatedJson = parseBibleVerseFromJson(verseJson: verseJson)
let data = validatedJson.data(using: .utf8) ?? Data()
let verse = try JSONDecoder().decode(BibleVerse.self, from: data)
await MainActor.run {
self.dailyVerse = verse
}
} catch {
print("❌ Failed to load daily verse: \(error)")
print("📖 Raw JSON was: \(fetchRandomBibleVerseJson())")
}
}
/// Search Bible verses
func searchVerses(_ query: String) async -> [BibleVerse] {
do {
let versesJson = fetchBibleVerseJson(query: query)
// Use Rust function for JSON parsing - NO business logic in Swift!
let validatedJson = parseBibleVerseFromJson(verseJson: versesJson)
let data = validatedJson.data(using: .utf8) ?? Data()
return try JSONDecoder().decode([BibleVerse].self, from: data)
} catch {
print("Failed to search verses: \(error)")
return []
}
}
/// Load stream status to check if live
func loadStreamStatus() async {
// Use Rust function directly - NO JSON parsing in Swift!
// Note: Using simple extraction until getStreamLiveStatus() binding is available
let statusJson = fetchStreamStatusJson()
let isLive = statusJson.contains("\"is_live\":true")
await MainActor.run {
self.isStreamLive = isLive
}
}
/// Submit contact form
func submitContact(name: String, email: String, subject: String, message: String, phone: String = "") async -> Bool {
let resultJson = submitContactV2Json(
name: name,
email: email,
subject: subject,
message: message,
phone: phone
)
// Use Rust function for JSON parsing - NO business logic in Swift!
let validatedJson = parseContactResultFromJson(resultJson: resultJson)
let data = validatedJson.data(using: .utf8) ?? Data()
do {
let result = try JSONDecoder().decode(ContactSubmissionResult.self, from: data)
return result.success
} catch {
return false
}
}
// MARK: - Private Methods
/// Create unified feed from all content
private func updateFeed() async {
await MainActor.run {
guard let eventsJson = try? JSONEncoder().encode(events),
let sermonsJson = try? JSONEncoder().encode(sermons),
let bulletinsJson = try? JSONEncoder().encode(bulletins),
let verseJson = try? JSONEncoder().encode(dailyVerse),
let eventsJsonString = String(data: eventsJson, encoding: .utf8),
let sermonsJsonString = String(data: sermonsJson, encoding: .utf8),
let bulletinsJsonString = String(data: bulletinsJson, encoding: .utf8),
let verseJsonString = String(data: verseJson, encoding: .utf8) else {
// Simple fallback - empty feed
self.feedItems = []
return
}
let feedJson = generateHomeFeedJson(
eventsJson: eventsJsonString,
sermonsJson: sermonsJsonString,
bulletinsJson: bulletinsJsonString,
verseJson: verseJsonString
)
guard let data = feedJson.data(using: .utf8),
let rustFeedItems = try? JSONDecoder().decode([RustFeedItem].self, from: data) else {
// Simple fallback - empty feed
self.feedItems = []
return
}
// Convert to Swift FeedItem models
self.feedItems = rustFeedItems.compactMap { rustItem -> FeedItem? in
let timestamp = parseDate(rustItem.timestamp) ?? Date()
switch rustItem.feedType {
case .event(let event):
return FeedItem(type: .event(event), timestamp: timestamp)
case .sermon(let sermon):
return FeedItem(type: .sermon(sermon), timestamp: timestamp)
case .bulletin(let bulletin):
return FeedItem(type: .bulletin(bulletin), timestamp: timestamp)
case .verse(let verse):
return FeedItem(type: .verse(verse), timestamp: timestamp)
}
}
}
}
private func parseDate(_ dateString: String) -> Date? {
let formatter = ISO8601DateFormatter()
return formatter.date(from: dateString)
}
}
// MARK: - Singleton Access
extension ChurchDataService {
static let shared = ChurchDataService()
}