feat: TypeWhisper Flow sessions, Phase 4 UX, and GitHub Pages privacy site

Migrate keyboard dictation to continuous Flow sessions with auto-start,
tap-to-toggle recording, 60s countdown, five-step onboarding, and App Group
IPC. Add docs/ GitHub Pages site with en/zh privacy policy for App Store compliance.
This commit is contained in:
Rocky
2026-06-19 18:05:50 +08:00
parent 6148d05093
commit 7f059dbd45
48 changed files with 3815 additions and 1065 deletions
+79
View File
@@ -0,0 +1,79 @@
// AppPermissions.swift
// OSGKeyboard · Main App
//
// Central permission status for onboarding and Flow session startup.
import AVFoundation
import Speech
import UIKit
enum AppPermissions {
enum MicStatus: Equatable {
case undetermined
case granted
case denied
}
enum SpeechStatus: Equatable {
case undetermined
case granted
case denied
case restricted
}
static var micStatus: MicStatus {
switch AVAudioApplication.shared.recordPermission {
case .granted: return .granted
case .denied: return .denied
case .undetermined: return .undetermined
@unknown default: return .denied
}
}
static var speechStatus: SpeechStatus {
switch SFSpeechRecognizer.authorizationStatus() {
case .authorized: return .granted
case .denied: return .denied
case .restricted: return .restricted
case .notDetermined: return .undetermined
@unknown default: return .denied
}
}
/// Both permissions required for Flow voice sessions.
static var flowRequirementsMet: Bool {
micStatus == .granted && speechStatus == .granted
}
/// Show guided permission pages when any Flow permission is not granted.
static var needsPermissionGuidance: Bool {
micStatus != .granted || speechStatus != .granted
}
static func requestMicrophone() async -> Bool {
switch micStatus {
case .granted: return true
case .denied: return false
case .undetermined: return await AVAudioApplication.requestRecordPermission()
}
}
static func requestSpeechRecognition() async -> Bool {
switch speechStatus {
case .granted: return true
case .denied, .restricted: return false
case .undetermined:
return await withCheckedContinuation { (cont: CheckedContinuation<Bool, Never>) in
SFSpeechRecognizer.requestAuthorization { status in
cont.resume(returning: status == .authorized)
}
}
}
}
static func openSystemSettings() {
guard let url = URL(string: UIApplication.openSettingsURLString) else { return }
UIApplication.shared.open(url)
}
}