fix: streamline keychain usage (#7)
This commit is contained in:
@@ -19,14 +19,6 @@ struct ContentView: View {
|
||||
MeetingListView(settingsViewModel: settingsViewModel)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
// Force load settings to check onboarding status
|
||||
settingsViewModel.loadSettings()
|
||||
}
|
||||
.onReceive(NotificationCenter.default.publisher(for: Notification.Name("OnboardingReset"))) { _ in
|
||||
// Reload settings when onboarding is reset
|
||||
settingsViewModel.loadSettings()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -294,7 +294,7 @@ class AudioManager: NSObject, ObservableObject {
|
||||
}
|
||||
|
||||
private func connectToOpenAIRealtime(source: AudioSource) {
|
||||
guard let key = KeychainHelper.shared.get(forKey: "openAIKey"), !key.isEmpty else {
|
||||
guard let key = KeychainHelper.shared.getAPIKey(), !key.isEmpty else {
|
||||
print("❌ No OpenAI key found")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -12,6 +12,19 @@ class KeychainHelper {
|
||||
|
||||
private init() {}
|
||||
|
||||
/// Gets the API key directly from keychain
|
||||
/// - Returns: The API key string if found, nil otherwise
|
||||
func getAPIKey() -> String? {
|
||||
return get(forKey: "openAIKey")
|
||||
}
|
||||
|
||||
/// Saves the API key to keychain
|
||||
/// - Parameter apiKey: The API key to save
|
||||
/// - Returns: True if the save was successful, false otherwise
|
||||
func saveAPIKey(_ apiKey: String) -> Bool {
|
||||
return save(apiKey, forKey: "openAIKey")
|
||||
}
|
||||
|
||||
/// Saves a string value to the keychain
|
||||
/// - Parameters:
|
||||
/// - value: The string value to save
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
// UserDefaultsManager.swift
|
||||
// Manages non-sensitive app settings using UserDefaults
|
||||
|
||||
import Foundation
|
||||
|
||||
/// Manages non-sensitive app settings using UserDefaults
|
||||
class UserDefaultsManager {
|
||||
static let shared = UserDefaultsManager()
|
||||
|
||||
private let userDefaults = UserDefaults.standard
|
||||
|
||||
private init() {}
|
||||
|
||||
// MARK: - Keys
|
||||
private enum Keys {
|
||||
static let userBlurb = "userBlurb"
|
||||
static let systemPrompt = "systemPrompt"
|
||||
static let hasCompletedOnboarding = "hasCompletedOnboarding"
|
||||
static let hasAcceptedTerms = "hasAcceptedTerms"
|
||||
static let selectedTemplateId = "selectedTemplateId"
|
||||
}
|
||||
|
||||
// MARK: - User Blurb
|
||||
var userBlurb: String {
|
||||
get { userDefaults.string(forKey: Keys.userBlurb) ?? "" }
|
||||
set { userDefaults.set(newValue, forKey: Keys.userBlurb) }
|
||||
}
|
||||
|
||||
// MARK: - System Prompt
|
||||
var systemPrompt: String {
|
||||
get {
|
||||
let stored = userDefaults.string(forKey: Keys.systemPrompt)
|
||||
return stored?.isEmpty == false ? stored! : Settings.defaultSystemPrompt()
|
||||
}
|
||||
set { userDefaults.set(newValue, forKey: Keys.systemPrompt) }
|
||||
}
|
||||
|
||||
// MARK: - Onboarding Status
|
||||
var hasCompletedOnboarding: Bool {
|
||||
get { userDefaults.bool(forKey: Keys.hasCompletedOnboarding) }
|
||||
set { userDefaults.set(newValue, forKey: Keys.hasCompletedOnboarding) }
|
||||
}
|
||||
|
||||
// MARK: - Terms Acceptance
|
||||
var hasAcceptedTerms: Bool {
|
||||
get { userDefaults.bool(forKey: Keys.hasAcceptedTerms) }
|
||||
set { userDefaults.set(newValue, forKey: Keys.hasAcceptedTerms) }
|
||||
}
|
||||
|
||||
// MARK: - Selected Template ID
|
||||
var selectedTemplateId: UUID? {
|
||||
get {
|
||||
guard let uuidString = userDefaults.string(forKey: Keys.selectedTemplateId) else { return nil }
|
||||
return UUID(uuidString: uuidString)
|
||||
}
|
||||
set {
|
||||
if let uuid = newValue {
|
||||
userDefaults.set(uuid.uuidString, forKey: Keys.selectedTemplateId)
|
||||
} else {
|
||||
userDefaults.removeObject(forKey: Keys.selectedTemplateId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,34 @@
|
||||
import Foundation
|
||||
|
||||
struct Settings: Codable {
|
||||
var openAIKey: String
|
||||
var userBlurb: String
|
||||
var systemPrompt: String
|
||||
var selectedTemplateId: UUID?
|
||||
var hasCompletedOnboarding: Bool
|
||||
var hasAcceptedTerms: Bool
|
||||
// Only store API key in memory - will be loaded from keychain when needed
|
||||
var openAIKey: String = ""
|
||||
|
||||
// Computed properties that access UserDefaults
|
||||
var userBlurb: String {
|
||||
get { UserDefaultsManager.shared.userBlurb }
|
||||
set { UserDefaultsManager.shared.userBlurb = newValue }
|
||||
}
|
||||
|
||||
var systemPrompt: String {
|
||||
get { UserDefaultsManager.shared.systemPrompt }
|
||||
set { UserDefaultsManager.shared.systemPrompt = newValue }
|
||||
}
|
||||
|
||||
var selectedTemplateId: UUID? {
|
||||
get { UserDefaultsManager.shared.selectedTemplateId }
|
||||
set { UserDefaultsManager.shared.selectedTemplateId = newValue }
|
||||
}
|
||||
|
||||
var hasCompletedOnboarding: Bool {
|
||||
get { UserDefaultsManager.shared.hasCompletedOnboarding }
|
||||
set { UserDefaultsManager.shared.hasCompletedOnboarding = newValue }
|
||||
}
|
||||
|
||||
var hasAcceptedTerms: Bool {
|
||||
get { UserDefaultsManager.shared.hasAcceptedTerms }
|
||||
set { UserDefaultsManager.shared.hasAcceptedTerms = newValue }
|
||||
}
|
||||
|
||||
// System prompt default loading
|
||||
static func defaultSystemPrompt() -> String {
|
||||
@@ -35,17 +57,12 @@ struct Settings: Codable {
|
||||
return result
|
||||
}
|
||||
|
||||
init(openAIKey: String = "",
|
||||
userBlurb: String = "",
|
||||
systemPrompt: String = "",
|
||||
selectedTemplateId: UUID? = nil,
|
||||
hasCompletedOnboarding: Bool = false,
|
||||
hasAcceptedTerms: Bool = false) {
|
||||
init(openAIKey: String = "") {
|
||||
self.openAIKey = openAIKey
|
||||
self.userBlurb = userBlurb
|
||||
self.systemPrompt = systemPrompt.isEmpty ? Settings.defaultSystemPrompt() : systemPrompt
|
||||
self.selectedTemplateId = selectedTemplateId
|
||||
self.hasCompletedOnboarding = hasCompletedOnboarding
|
||||
self.hasAcceptedTerms = hasAcceptedTerms
|
||||
}
|
||||
|
||||
// MARK: - Codable conformance for API key only
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case openAIKey
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@ class NotesGenerator {
|
||||
return AsyncStream<String>(String.self) { continuation in
|
||||
Task {
|
||||
do {
|
||||
guard let apiKey = KeychainHelper.shared.get(forKey: "openAIKey"), !apiKey.isEmpty else {
|
||||
guard let apiKey = KeychainHelper.shared.getAPIKey(), !apiKey.isEmpty else {
|
||||
continuation.finish()
|
||||
return
|
||||
}
|
||||
@@ -91,7 +91,7 @@ class NotesGenerator {
|
||||
/// Validates if OpenAI API key is configured
|
||||
/// - Returns: True if API key exists, false otherwise
|
||||
func isConfigured() -> Bool {
|
||||
guard let key = KeychainHelper.shared.get(forKey: "openAIKey"),
|
||||
guard let key = KeychainHelper.shared.getAPIKey(),
|
||||
!key.isEmpty else {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -180,8 +180,8 @@ class MeetingViewModel: ObservableObject {
|
||||
meeting.generatedNotes = ""
|
||||
|
||||
// Load settings for generation
|
||||
let userBlurb = KeychainHelper.shared.get(forKey: "userBlurb") ?? ""
|
||||
let systemPrompt = KeychainHelper.shared.get(forKey: "systemPrompt") ?? Settings.defaultSystemPrompt()
|
||||
let userBlurb = UserDefaultsManager.shared.userBlurb
|
||||
let systemPrompt = UserDefaultsManager.shared.systemPrompt
|
||||
|
||||
// Use streaming generation
|
||||
let stream = NotesGenerator.shared.generateNotesStream(
|
||||
|
||||
@@ -8,23 +8,13 @@ class SettingsViewModel: ObservableObject {
|
||||
@Published var templates: [NoteTemplate] = []
|
||||
|
||||
init() {
|
||||
loadSettings()
|
||||
loadTemplates()
|
||||
}
|
||||
|
||||
func loadSettings() {
|
||||
settings.openAIKey = KeychainHelper.shared.get(forKey: "openAIKey") ?? ""
|
||||
settings.userBlurb = KeychainHelper.shared.get(forKey: "userBlurb") ?? ""
|
||||
settings.systemPrompt = KeychainHelper.shared.get(forKey: "systemPrompt") ?? Settings.defaultSystemPrompt()
|
||||
|
||||
// Load onboarding status
|
||||
settings.hasCompletedOnboarding = KeychainHelper.shared.get(forKey: "hasCompletedOnboarding") == "true"
|
||||
settings.hasAcceptedTerms = KeychainHelper.shared.get(forKey: "hasAcceptedTerms") == "true"
|
||||
|
||||
// Load selected template ID
|
||||
if let templateIdString = KeychainHelper.shared.get(forKey: "selectedTemplateId"),
|
||||
let templateId = UUID(uuidString: templateIdString) {
|
||||
settings.selectedTemplateId = templateId
|
||||
/// Loads the API key from keychain (only called when actually needed)
|
||||
func loadAPIKey() {
|
||||
if settings.openAIKey.isEmpty {
|
||||
settings.openAIKey = KeychainHelper.shared.getAPIKey() ?? ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,22 +52,12 @@ class SettingsViewModel: ObservableObject {
|
||||
return
|
||||
}
|
||||
|
||||
let openAISaved = KeychainHelper.shared.save(settings.openAIKey, forKey: "openAIKey")
|
||||
let blurbSaved = KeychainHelper.shared.save(settings.userBlurb, forKey: "userBlurb")
|
||||
let promptSaved = KeychainHelper.shared.save(settings.systemPrompt, forKey: "systemPrompt")
|
||||
|
||||
// Save onboarding status
|
||||
let onboardingSaved = KeychainHelper.shared.save(settings.hasCompletedOnboarding ? "true" : "false", forKey: "hasCompletedOnboarding")
|
||||
let termsSaved = KeychainHelper.shared.save(settings.hasAcceptedTerms ? "true" : "false", forKey: "hasAcceptedTerms")
|
||||
|
||||
// Save selected template ID
|
||||
var templateIdSaved = true
|
||||
if let templateId = settings.selectedTemplateId {
|
||||
templateIdSaved = KeychainHelper.shared.save(templateId.uuidString, forKey: "selectedTemplateId")
|
||||
}
|
||||
// Only save API key to keychain - other values are automatically saved to UserDefaults
|
||||
// via computed properties when they're modified
|
||||
let openAISaved = KeychainHelper.shared.saveAPIKey(settings.openAIKey)
|
||||
|
||||
if showMessage {
|
||||
if openAISaved && blurbSaved && promptSaved && templateIdSaved && onboardingSaved && termsSaved {
|
||||
if openAISaved {
|
||||
saveMessage = "Settings saved successfully!"
|
||||
} else {
|
||||
saveMessage = "Error saving settings"
|
||||
|
||||
@@ -152,6 +152,7 @@ struct OnboardingView: View {
|
||||
.onAppear {
|
||||
checkPermissions()
|
||||
// Load existing API key if available
|
||||
settingsViewModel.loadAPIKey()
|
||||
apiKey = settingsViewModel.settings.openAIKey
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,8 +155,8 @@ struct SettingsView: View {
|
||||
.navigationTitle("Settings")
|
||||
.frame(minWidth: 600, minHeight: 600)
|
||||
.onAppear {
|
||||
viewModel.loadSettings()
|
||||
viewModel.loadTemplates()
|
||||
viewModel.loadAPIKey()
|
||||
}
|
||||
.onDisappear {
|
||||
DispatchQueue.main.async {
|
||||
|
||||
Reference in New Issue
Block a user