From 3f1cafb3b6a56feec30273b85d61d6d3ab9dd853 Mon Sep 17 00:00:00 2001 From: boris Date: Wed, 16 Sep 2026 10:44:17 +0300 Subject: [PATCH] Prompt for an OpenAI API key after onboarding on local builds When Settings opens because no key is saved, expand the API-key section and show a clear message instead of a silent settings sheet. --- apps/ios/App/ConversationCoordinator.swift | 18 +++++++++++++++++- apps/ios/App/LibraryViews.swift | 10 ++++++++++ apps/ios/App/RootView.swift | 8 +++++++- 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/apps/ios/App/ConversationCoordinator.swift b/apps/ios/App/ConversationCoordinator.swift index bf2cf9e4..49080100 100644 --- a/apps/ios/App/ConversationCoordinator.swift +++ b/apps/ios/App/ConversationCoordinator.swift @@ -23,6 +23,8 @@ import MuralCore var typedReplyError: String? var notice: String? var showSettings = false + /// When true, Settings opens with the API-key section expanded and explains why. + var promptAPIKeySetup = false var showAIConsent = false private var startAfterConsent = false private let api: APIClient @@ -117,7 +119,10 @@ import MuralCore #if DEBUG if ProcessInfo.processInfo.arguments.contains("--preview") { showSettings = true; return } #endif - guard CredentialStore.hasKey else { showSettings = true; return } + guard CredentialStore.hasKey else { + requireAPIKey() + return + } cancelReset(); meanings.reset() error = nil; notice = nil; lastAssessmentKey = "" lastLanguageCheck = ""; pendingCommands = [:] @@ -153,6 +158,17 @@ import MuralCore startAfterConsent = false if hasAIConsent { start() } } + /// Opens Settings with a clear prompt to add a personal OpenAI key (local/BYOK builds). + func requireAPIKey(message: String = "Add your OpenAI API key in Settings to start practising on this phone.") { + notice = message + promptAPIKeySetup = true + showSettings = true + } + /// Call after onboarding when this install still has no key saved. + func promptAPIKeyAfterOnboardingIfNeeded() { + guard !CredentialStore.hasKey else { return } + requireAPIKey(message: "Welcome! Add your OpenAI API key to start conversations.") + } func selectLanguage(_ id: String) { guard !isRunning, id != language.id, LanguageRegistry.module(for: id) != nil else { return } cancelReset(); languageGeneration = UUID() diff --git a/apps/ios/App/LibraryViews.swift b/apps/ios/App/LibraryViews.swift index c6dddb92..15391abb 100644 --- a/apps/ios/App/LibraryViews.swift +++ b/apps/ios/App/LibraryViews.swift @@ -380,6 +380,16 @@ struct SettingsView: View { }.scrollContentBackground(.hidden).background(MuralColor.cream).tint(MuralColor.secondary) .navigationTitle("Make yourself comfortable").navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .confirmationAction) { Button("Done") { key = ""; dismiss() } } } + .onAppear { + hasKey = CredentialStore.hasKey + if coordinator.promptAPIKeySetup { + showingAPIKey = true + if message == nil { + message = coordinator.notice ?? "Add an OpenAI API key to start conversations on this phone." + } + coordinator.promptAPIKeySetup = false + } + } } .fileExporter(isPresented: $exporting, document: backup, contentType: .json, defaultFilename: "Mural-learning-backup") { result in if case .failure(let error) = result { message = error.localizedDescription } } .fileImporter(isPresented: $importing, allowedContentTypes: [.json]) { result in diff --git a/apps/ios/App/RootView.swift b/apps/ios/App/RootView.swift index dcaec553..fc617394 100644 --- a/apps/ios/App/RootView.swift +++ b/apps/ios/App/RootView.swift @@ -33,7 +33,13 @@ struct RootView: View { .sheet(isPresented: $coordinator.showAIConsent, onDismiss: { coordinator.resumeAfterAIConsent() }) { AIConsentView(agree: { coordinator.acceptAIConsent() }, decline: { coordinator.declineAIConsent() }) } - .fullScreenCover(isPresented: $onboarding) { OnboardingView(coordinator: coordinator) { coordinator.store.updatePreferences { $0.hasOnboarded = true }; onboarding = false } } + .fullScreenCover(isPresented: $onboarding) { + OnboardingView(coordinator: coordinator) { + coordinator.store.updatePreferences { $0.hasOnboarded = true } + onboarding = false + coordinator.promptAPIKeyAfterOnboardingIfNeeded() + } + } .alert("A little interruption", isPresented: Binding(get: { coordinator.error != nil || coordinator.store.error != nil }, set: { if !$0 { coordinator.error = nil; coordinator.store.error = nil } })) { Button("OK", role: .cancel) { coordinator.error = nil; coordinator.store.error = nil } } message: { Text(coordinator.error ?? coordinator.store.error ?? "") }