All checks were successful
CI / Scraper / Lint (push) Successful in 10s
CI / Scraper / Test (push) Successful in 14s
Release / Scraper / Test (push) Successful in 18s
CI / Scraper / Lint (pull_request) Successful in 18s
Release / UI / Build (push) Successful in 23s
CI / Scraper / Test (pull_request) Successful in 15s
CI / UI / Build (pull_request) Successful in 32s
Release / Scraper / Docker (push) Successful in 55s
CI / Scraper / Docker Push (pull_request) Has been skipped
CI / UI / Docker Push (pull_request) Has been skipped
CI / Scraper / Docker Push (push) Successful in 1m5s
Release / UI / Docker (push) Successful in 1m12s
iOS CI / Build (push) Successful in 4m18s
iOS CI / Build (pull_request) Successful in 4m25s
iOS CI / Test (push) Successful in 8m11s
iOS CI / Test (pull_request) Successful in 8m21s
60 lines
1.5 KiB
Swift
60 lines
1.5 KiB
Swift
import Foundation
|
|
|
|
// MARK: - BookVoicePreferences
|
|
// Manages per-book voice overrides with global fallback.
|
|
// Persisted in UserDefaults as a slug → voice dictionary.
|
|
|
|
@MainActor
|
|
final class BookVoicePreferences: ObservableObject {
|
|
static let shared = BookVoicePreferences()
|
|
|
|
@Published private(set) var bookVoices: [String: String] = [:]
|
|
|
|
private let key = "v2.bookVoicePreferences"
|
|
|
|
private init() {
|
|
if let data = UserDefaults.standard.data(forKey: key),
|
|
let decoded = try? JSONDecoder().decode([String: String].self, from: data) {
|
|
bookVoices = decoded
|
|
}
|
|
}
|
|
|
|
// MARK: - Public API
|
|
|
|
func voice(for slug: String) -> String? {
|
|
bookVoices[slug]
|
|
}
|
|
|
|
/// Voice priority: book override → globalVoice → "af_bella"
|
|
func voiceWithFallback(for slug: String, globalVoice: String) -> String {
|
|
bookVoices[slug] ?? globalVoice
|
|
}
|
|
|
|
func setVoice(_ voice: String, for slug: String) {
|
|
bookVoices[slug] = voice
|
|
save()
|
|
}
|
|
|
|
func removeVoice(for slug: String) {
|
|
bookVoices.removeValue(forKey: slug)
|
|
save()
|
|
}
|
|
|
|
func hasOverride(for slug: String) -> Bool {
|
|
bookVoices[slug] != nil
|
|
}
|
|
|
|
func clearAll() {
|
|
bookVoices.removeAll()
|
|
save()
|
|
}
|
|
|
|
// MARK: - Persistence
|
|
|
|
private func save() {
|
|
if let encoded = try? JSONEncoder().encode(bookVoices) {
|
|
UserDefaults.standard.set(encoded, forKey: key)
|
|
}
|
|
}
|
|
}
|