mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
WIP: toggle for realistic voices
This commit is contained in:
parent
59271882a5
commit
a4419e58bc
5 changed files with 161 additions and 14 deletions
|
|
@ -2,15 +2,18 @@
|
|||
import Models
|
||||
import Services
|
||||
import SwiftUI
|
||||
import Utils
|
||||
import Views
|
||||
|
||||
struct TextToSpeechVoiceSelectionView: View {
|
||||
@EnvironmentObject var audioController: AudioController
|
||||
@EnvironmentObject var dataService: DataService
|
||||
|
||||
@StateObject var viewModel = TextToSpeechVoiceSelectionViewModel()
|
||||
|
||||
let language: VoiceLanguage
|
||||
let showLanguageChanger: Bool
|
||||
|
||||
@State var playbackSample: String? = nil
|
||||
|
||||
init(forLanguage: VoiceLanguage, showLanguageChanger: Bool) {
|
||||
self.language = forLanguage
|
||||
self.showLanguageChanger = showLanguageChanger
|
||||
|
|
@ -20,15 +23,30 @@
|
|||
Group {
|
||||
Form {
|
||||
if language.key == "en" {
|
||||
Toggle("Use Ultra Realistic Voices", isOn: $audioController.useUltraRealisticVoices)
|
||||
.accentColor(Color.green)
|
||||
}
|
||||
if viewModel.waitingForRealisticVoices {
|
||||
HStack {
|
||||
Text("Signing up for beta")
|
||||
Spacer()
|
||||
ProgressView()
|
||||
}
|
||||
} else {
|
||||
Toggle("Use Ultra Realistic Voices", isOn: $viewModel.realisticVoicesToggle)
|
||||
.accentColor(Color.green)
|
||||
}
|
||||
|
||||
if language.key == "en", audioController.useUltraRealisticVoices {
|
||||
Section {
|
||||
Text("Ultra realistic voices take longer to generate and do not offer a follow along user interface.")
|
||||
if !viewModel.waitingForRealisticVoices, !audioController.ultraRealisticFeatureKey.isEmpty {
|
||||
Text("You are in the ultra realistic voices beta. During the beta you can listen to 10,000 words of audio per day.")
|
||||
.multilineTextAlignment(.leading)
|
||||
} else if audioController.ultraRealisticFeatureRequested {
|
||||
Text("Your request to join the ultra realistic voices demo has been received. You will be informed by email when a spot is available.")
|
||||
.multilineTextAlignment(.leading)
|
||||
} else {
|
||||
Text("Ultra realistic voices are currently in limited beta. Enabling the feature will add you to the beta queue.")
|
||||
.multilineTextAlignment(.leading)
|
||||
}
|
||||
}
|
||||
|
||||
if audioController.useUltraRealisticVoices {
|
||||
ultraRealisticVoices
|
||||
} else {
|
||||
if showLanguageChanger {
|
||||
|
|
@ -43,6 +61,24 @@
|
|||
}
|
||||
}
|
||||
.navigationTitle("Choose a Voice")
|
||||
.onAppear {
|
||||
viewModel.realisticVoicesToggle = (audioController.useUltraRealisticVoices && !audioController.ultraRealisticFeatureKey.isEmpty)
|
||||
}.onChange(of: viewModel.realisticVoicesToggle) { value in
|
||||
if value, audioController.ultraRealisticFeatureKey.isEmpty {
|
||||
// User wants to sign up
|
||||
viewModel.waitingForRealisticVoices = true
|
||||
Task {
|
||||
await viewModel.requestUltraRealisticFeatureAccess(
|
||||
dataService: self.dataService,
|
||||
audioController: audioController
|
||||
)
|
||||
}
|
||||
} else if value, !audioController.ultraRealisticFeatureKey.isEmpty {
|
||||
audioController.useUltraRealisticVoices = true
|
||||
} else if !value {
|
||||
audioController.useUltraRealisticVoices = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var standardVoices: some View {
|
||||
|
|
@ -69,27 +105,27 @@
|
|||
HStack {
|
||||
Button(action: {
|
||||
if audioController.isPlayingSample(voice: voice.key) {
|
||||
playbackSample = nil
|
||||
viewModel.playbackSample = nil
|
||||
audioController.stopVoiceSample()
|
||||
} else {
|
||||
playbackSample = voice.key
|
||||
viewModel.playbackSample = voice.key
|
||||
audioController.playVoiceSample(voice: voice.key)
|
||||
Timer.scheduledTimer(withTimeInterval: 2.0, repeats: true) { timer in
|
||||
let playing = audioController.isPlayingSample(voice: voice.key)
|
||||
if playing {
|
||||
playbackSample = voice.key
|
||||
viewModel.playbackSample = voice.key
|
||||
} else if !playing {
|
||||
// If the playback sample is something else, its taken ownership
|
||||
// of the value so we just ignore it and shut down our timer.
|
||||
if playbackSample == voice.key {
|
||||
playbackSample = nil
|
||||
if viewModel.playbackSample == voice.key {
|
||||
viewModel.playbackSample = nil
|
||||
}
|
||||
timer.invalidate()
|
||||
}
|
||||
}
|
||||
}
|
||||
}, label: {
|
||||
if playbackSample == voice.key {
|
||||
if viewModel.playbackSample == voice.key {
|
||||
Image(systemName: "stop.circle")
|
||||
.font(.appTitleTwo)
|
||||
.padding(.trailing, 16)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
//
|
||||
// TextToSpeechVoiceSelectionViewModel.swift
|
||||
//
|
||||
//
|
||||
// Created by Jackson Harper on 11/10/22.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
import Foundation
|
||||
import Models
|
||||
import Services
|
||||
import SwiftUI
|
||||
import Views
|
||||
|
||||
@MainActor final class TextToSpeechVoiceSelectionViewModel: ObservableObject {
|
||||
@Published var playbackSample: String?
|
||||
@Published var realisticVoicesToggle: Bool = false
|
||||
@Published var waitingForRealisticVoices: Bool = false
|
||||
|
||||
func requestUltraRealisticFeatureAccess(
|
||||
dataService: DataService,
|
||||
audioController: AudioController
|
||||
) async {
|
||||
do {
|
||||
let feature = try await dataService.optInFeature(name: "ultra-realistic-voice")
|
||||
DispatchQueue.main.async {
|
||||
if let feature = feature {
|
||||
audioController.useUltraRealisticVoices = true
|
||||
audioController.ultraRealisticFeatureRequested = true
|
||||
audioController.ultraRealisticFeatureKey = feature.granted ? feature.token : ""
|
||||
self.realisticVoicesToggle = true
|
||||
} else {
|
||||
audioController.useUltraRealisticVoices = false
|
||||
audioController.ultraRealisticFeatureKey = ""
|
||||
audioController.ultraRealisticFeatureRequested = false
|
||||
self.realisticVoicesToggle = false
|
||||
}
|
||||
self.waitingForRealisticVoices = false
|
||||
}
|
||||
} catch {
|
||||
print("ERROR OPTING INTO FEATURE", error)
|
||||
audioController.useUltraRealisticVoices = false
|
||||
realisticVoicesToggle = false
|
||||
waitingForRealisticVoices = false
|
||||
audioController.ultraRealisticFeatureRequested = false
|
||||
Snackbar.show(message: "Error signing up for beta. Please try again.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -275,6 +275,9 @@
|
|||
|
||||
@AppStorage(UserDefaultKey.textToSpeechUseUltraRealisticVoices.rawValue) public var useUltraRealisticVoices = false
|
||||
|
||||
@AppStorage(UserDefaultKey.textToSpeechUltraRealisticFeatureKey.rawValue) public var ultraRealisticFeatureKey: String = ""
|
||||
@AppStorage(UserDefaultKey.textToSpeechUltraRealisticFeatureRequested.rawValue) public var ultraRealisticFeatureRequested: Bool = false
|
||||
|
||||
public var currentVoiceLanguage: VoiceLanguage {
|
||||
Voices.Languages.first(where: { $0.key == currentLanguage }) ?? Voices.English
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,57 @@
|
|||
//
|
||||
// File.swift
|
||||
//
|
||||
//
|
||||
// Created by Jackson Harper on 11/10/22.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Models
|
||||
import SwiftGraphQL
|
||||
|
||||
public struct Feature {
|
||||
public let name: String
|
||||
public let token: String
|
||||
public let granted: Bool
|
||||
}
|
||||
|
||||
public extension DataService {
|
||||
func optInFeature(name: String) async throws -> Feature? {
|
||||
enum MutationResult {
|
||||
case success(feature: Feature)
|
||||
case error(errorCode: Enums.OptInFeatureErrorCode)
|
||||
}
|
||||
|
||||
let featureSelection = Selection.Feature { Feature(name: try $0.name(), token: try $0.token(), granted: try $0.grantedAt() != nil) }
|
||||
let selection = Selection<MutationResult, Unions.OptInFeatureResult> {
|
||||
try $0.on(
|
||||
optInFeatureError: .init { .error(errorCode: try $0.errorCodes().first ?? .badRequest) },
|
||||
optInFeatureSuccess: .init { .success(feature: try $0.feature(selection: featureSelection)) }
|
||||
)
|
||||
}
|
||||
|
||||
let mutation = Selection.Mutation {
|
||||
try $0.optInFeature(input: InputObjects.OptInFeatureInput(name: name),
|
||||
selection: selection)
|
||||
}
|
||||
|
||||
let path = appEnvironment.graphqlPath
|
||||
let headers = networker.defaultHeaders
|
||||
|
||||
return try await withCheckedThrowingContinuation { continuation in
|
||||
send(mutation, to: path, headers: headers) { queryResult in
|
||||
guard let payload = try? queryResult.get() else {
|
||||
continuation.resume(throwing: BasicError.message(messageText: "network error"))
|
||||
return
|
||||
}
|
||||
|
||||
switch payload.data {
|
||||
case let .success(feature: feature):
|
||||
continuation.resume(returning: feature)
|
||||
case let .error(errorCode: errorCode):
|
||||
continuation.resume(throwing: BasicError.message(messageText: errorCode.rawValue))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -18,6 +18,8 @@ public enum UserDefaultKey: String {
|
|||
case textToSpeechDefaultLanguage
|
||||
case textToSpeechPreloadEnabled
|
||||
case textToSpeechUseUltraRealisticVoices
|
||||
case textToSpeechUltraRealisticFeatureKey
|
||||
case textToSpeechUltraRealisticFeatureRequested
|
||||
case recentSearchTerms
|
||||
case audioPlayerExpanded
|
||||
case themeName
|
||||
|
|
|
|||
Loading…
Reference in a new issue