Improve AI voices modal

This commit is contained in:
Jackson Harper
2023-11-17 21:17:43 +08:00
parent db54ecff3a
commit b8558ec2f7
6 changed files with 158 additions and 136 deletions

View File

@ -32,12 +32,14 @@ struct AnimatingCellHeight: AnimatableModifier {
@State var isListScrolled = false
@State var listTitle = ""
@State var isEditMode: EditMode = .inactive
@State var showOpenAIVoices = false
@EnvironmentObject var dataService: DataService
@EnvironmentObject var audioController: AudioController
@AppStorage(UserDefaultKey.homeFeedlayoutPreference.rawValue) var prefersListLayout = true
@AppStorage(UserDefaultKey.shouldPromptCommunityModal.rawValue) var shouldPromptCommunityModal = true
@AppStorage(UserDefaultKey.openAIPrimerDisplayed.rawValue) var openAIPrimerDisplayed = false
@ObservedObject var viewModel: HomeFeedViewModel
@State private var selection = Set<String>()
@ -99,6 +101,17 @@ struct AnimatingCellHeight: AnimatableModifier {
FilterSelectorView(viewModel: viewModel)
}
}
.sheet(isPresented: $showOpenAIVoices) {
OpenAIVoicesModal(audioController: audioController)
}
.onAppear {
showOpenAIVoices = true
if !openAIPrimerDisplayed, !Voices.isOpenAIVoice(self.audioController.currentVoice) {
showOpenAIVoices = true
openAIPrimerDisplayed = true
}
}
.toolbar {
toolbarItems
}

View File

@ -33,7 +33,6 @@ import Views
@Published var showLabelsSheet = false
@Published var showFiltersModal = false
@Published var showCommunityModal = false
@Published var featureItems = [LinkedItem]()
@Published var listConfig: LibraryListConfig

View File

@ -0,0 +1,143 @@
// swiftlint:disable line_length
//
// CommunityModal.swift
//
//
// Created by Jackson Harper on 12/7/22.
//
#if os(iOS)
import Foundation
import Models
import Services
import SwiftUI
import Views
struct OpenAIVoiceItem {
let name: String
let key: String
}
public struct OpenAIVoicesModal: View {
@Environment(\.dismiss) private var dismiss
let audioController: AudioController
let message: String = """
We've added six new voices powered by OpenAI and enabled them for all users. If you are already using our Ultra Realistic voices, don't worry, trying these voices will not remove you from the ultra realistic beta.
[Tell your friends about Omnivore](https://omnivore.app)
"""
@State var playbackSample: String?
let voices = [
OpenAIVoiceItem(name: "Alloy", key: "openai-alloy"),
OpenAIVoiceItem(name: "Echo", key: "openai-echo"),
OpenAIVoiceItem(name: "Fable", key: "openai-fable"),
OpenAIVoiceItem(name: "Onyx", key: "openai-onyx"),
OpenAIVoiceItem(name: "Nova", key: "openai-nova"),
OpenAIVoiceItem(name: "Shimmer", key: "openai-shimmer")
]
var closeButton: some View {
Button(action: {
dismiss()
}, label: {
ZStack {
Circle()
.foregroundColor(Color.circleButtonBackground)
.frame(width: 30, height: 30)
Image(systemName: "xmark")
.resizable(resizingMode: Image.ResizingMode.stretch)
.foregroundColor(Color.circleButtonForeground)
.aspectRatio(contentMode: .fit)
.font(Font.title.weight(.bold))
.frame(width: 12, height: 12)
}
})
}
public var body: some View {
HStack {
Text("New voices powered by OpenAI")
.font(Font.system(size: 20, weight: .bold))
Spacer()
closeButton
}
.padding(.top, 16)
.padding(.horizontal, 16)
List {
Section {
let parsedMessage = try? AttributedString(markdown: message,
options: .init(interpretedSyntax: .inlineOnly))
Text(parsedMessage ?? "")
.multilineTextAlignment(.leading)
.foregroundColor(Color.appGrayTextContrast)
.accentColor(.blue)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.top, 16)
}
Section {
ForEach(voices, id: \.self.name) { voice in
voiceRow(for: voice)
}
}
}
.environmentObject(audioController)
}
func voiceRow(for voice: OpenAIVoiceItem) -> some View {
Button(action: {
if audioController.isPlayingSample(voice: voice.key) {
playbackSample = nil
audioController.stopVoiceSample()
}
playbackSample = voice.key
audioController.currentVoice = 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
} 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
}
timer.invalidate()
}
}
}, label: {
HStack {
if playbackSample == voice.key {
Image(systemName: "stop.circle")
.font(.appTitleTwo)
.padding(.trailing, 16)
} else {
Image(systemName: "play.circle")
.font(.appTitleTwo)
.padding(.trailing, 16)
}
Text(voice.name)
Spacer()
if audioController.currentVoice == voice.key {
if audioController.isPlaying, audioController.isLoading {
ProgressView()
} else {
Image(systemName: "checkmark")
}
}
}.contentShape(Rectangle())
})
.buttonStyle(PlainButtonStyle())
.frame(maxWidth: .infinity)
}
}
#endif

View File

@ -14,9 +14,6 @@ import Views
public final class RootViewModel: ObservableObject {
let services = Services()
@Published public var showNewFeaturePrimer = false
@AppStorage(UserDefaultKey.shouldShowNewFeaturePrimer.rawValue) var shouldShowNewFeaturePrimer = false
public init() {
registerFonts()

View File

@ -26,10 +26,9 @@ public enum UserDefaultKey: String {
case recentSearchTerms
case audioPlayerExpanded
case themeName
case shouldShowNewFeaturePrimer
case openAIPrimerDisplayed
case notificationsEnabled
case deviceTokenID
case shouldPromptCommunityModal
case userWordsPerMinute
case hideFeatureSection
case justifyText

View File

@ -1,129 +0,0 @@
//
// CommunityModal.swift
//
//
// Created by Jackson Harper on 12/7/22.
//
#if os(iOS)
import Foundation
import StoreKit
import SwiftUI
// swiftlint:disable:next line_length
let tweetUrl = "https://twitter.com/intent/tweet?text=I%20recently%20started%20using%20@OmnivoreApp%20as%20a%20free,%20open-source%20read-it-later%20app.%20Check%20it%20out:%20https://omnivore.app"
public struct CommunityModal: View {
@Environment(\.dismiss) private var dismiss
let message: String = """
Thank you for being a member of the Omnivore Community.
Omnivore is a free and open-source project and relies on \
help from our community to grow. Below are a few simple \
things you can do to help us build a better Omnivore.
If you would like to financially assist Omnivore \
please [contribute on Open Collective](https://opencollective.com/omnivore).
"""
public init() {}
// var body: some View {
// ZStack {
// Image("Biz-card_2020")
// .resizable()
// .edgesIgnoringSafeArea(.all)
// closeButton
// }
// }
var closeButton: some View {
VStack {
HStack {
Spacer()
Button {
dismiss()
} label: {
Image(systemName: "xmark.circle")
.padding(10)
}
}
.padding(.top, 5)
Spacer()
}
}
public var header: some View {
VStack(spacing: 0) {
Text(LocalText.communityHeadline)
.font(.textToSpeechRead)
.foregroundColor(Color.appGrayTextContrast)
.frame(maxWidth: .infinity, alignment: .leading)
HStack {
TextChip(text: "Help Wanted", color: Color.appBackground)
.frame(alignment: .leading)
TextChip(text: "Community", color: Color.green)
.frame(alignment: .leading)
}
.padding(.top, 10)
.frame(maxWidth: .infinity, alignment: .leading)
}
}
let links = [
(title: LocalText.communityTweet, url: tweetUrl),
(title: LocalText.communityFollowTwitter, url: "https://twitter.com/omnivoreapp"),
(title: LocalText.communityJoinDiscord, url: "https://discord.gg/h2z5rppzz9"),
(title: LocalText.communityStarGithub, url: "https://github.com/omnivore-app/omnivore")
]
var buttonLinks: some View {
VStack(spacing: 15) {
Button(action: {
// swiftlint:disable:next line_length
if let scene = UIApplication.shared.connectedScenes.first(where: { $0.activationState == .foregroundActive }) as? UIWindowScene {
SKStoreReviewController.requestReview(in: scene)
}
}, label: { Text(LocalText.communityAppstoreReview) })
.frame(maxWidth: .infinity, alignment: .leading)
ForEach(links, id: \.url) { link in
if let url = URL(string: link.url) {
Link(link.title, destination: url)
.frame(maxWidth: .infinity, alignment: .leading)
}
}
}.frame(maxWidth: .infinity, alignment: .leading)
}
public var body: some View {
VStack(spacing: 0) {
header
let parsedMessage = try? AttributedString(markdown: message,
options: .init(interpretedSyntax: .inlineOnlyPreservingWhitespace))
Text(parsedMessage ?? "")
.multilineTextAlignment(.leading)
.foregroundColor(Color.appGrayTextContrast)
.accentColor(.blue)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.top, 16)
Spacer()
buttonLinks
Spacer()
Button(action: {
dismiss()
}, label: { Text(LocalText.dismissButton) })
.buttonStyle(PlainButtonStyle())
.padding(.bottom, 16)
.frame(alignment: .bottom)
}.padding()
}
}
#endif