WIP: toggle for realistic voices

This commit is contained in:
Jackson Harper 2022-11-10 19:32:13 +08:00
parent 59271882a5
commit a4419e58bc
5 changed files with 161 additions and 14 deletions

View file

@ -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)

View file

@ -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.")
}
}
}

View file

@ -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
}

View file

@ -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))
}
}
}
}
}

View file

@ -18,6 +18,8 @@ public enum UserDefaultKey: String {
case textToSpeechDefaultLanguage
case textToSpeechPreloadEnabled
case textToSpeechUseUltraRealisticVoices
case textToSpeechUltraRealisticFeatureKey
case textToSpeechUltraRealisticFeatureRequested
case recentSearchTerms
case audioPlayerExpanded
case themeName