- Rename application from MenuWhisper to Tell me with new domain com.fmartingr.tellme - Implement comprehensive preferences window with 6 tabs (General, Models, Text Insertion, Interface, Advanced, Permissions) - Add full English/Spanish localization for all UI elements - Create functional onboarding flow with model download capability - Implement preview dialog for transcription editing - Add settings export/import functionality - Fix HUD content display issues and add comprehensive permission checking - Enhance build scripts and app bundle creation for proper localization support
215 lines
No EOL
7.5 KiB
Swift
215 lines
No EOL
7.5 KiB
Swift
import Foundation
|
|
import CoreUtils
|
|
|
|
public enum HotkeyMode: String, CaseIterable, Codable {
|
|
case pushToTalk = "push_to_talk"
|
|
case toggle = "toggle"
|
|
|
|
public var displayName: String {
|
|
switch self {
|
|
case .pushToTalk:
|
|
return NSLocalizedString("preferences.general.mode.push_to_talk", comment: "Push to talk mode")
|
|
case .toggle:
|
|
return NSLocalizedString("preferences.general.mode.toggle", comment: "Toggle mode")
|
|
}
|
|
}
|
|
}
|
|
|
|
public struct HotkeyConfig: Codable {
|
|
public let keyCode: UInt32
|
|
public let modifiers: UInt32
|
|
|
|
public init(keyCode: UInt32, modifiers: UInt32) {
|
|
self.keyCode = keyCode
|
|
self.modifiers = modifiers
|
|
}
|
|
|
|
// Default to ⌘⇧V
|
|
public static let `default` = HotkeyConfig(keyCode: 9, modifiers: 768) // V key with Cmd+Shift
|
|
}
|
|
|
|
public enum InsertionMethod: String, CaseIterable, Codable {
|
|
case paste = "paste"
|
|
case typing = "typing"
|
|
|
|
public var displayName: String {
|
|
switch self {
|
|
case .paste:
|
|
return NSLocalizedString("preferences.insertion.method.paste", comment: "Paste method")
|
|
case .typing:
|
|
return NSLocalizedString("preferences.insertion.method.type", comment: "Type method")
|
|
}
|
|
}
|
|
}
|
|
|
|
public class Settings: ObservableObject {
|
|
private let logger = Logger(category: "Settings")
|
|
private let userDefaults = UserDefaults.standard
|
|
|
|
// General Settings
|
|
@Published public var hotkey: HotkeyConfig {
|
|
didSet { saveHotkey() }
|
|
}
|
|
|
|
@Published public var hotkeyMode: HotkeyMode {
|
|
didSet { saveHotkeyMode() }
|
|
}
|
|
|
|
@Published public var playSounds: Bool {
|
|
didSet { userDefaults.set(playSounds, forKey: "playSounds") }
|
|
}
|
|
|
|
@Published public var dictationTimeLimit: TimeInterval {
|
|
didSet { userDefaults.set(dictationTimeLimit, forKey: "dictationTimeLimit") }
|
|
}
|
|
|
|
// HUD Settings
|
|
@Published public var hudOpacity: Double {
|
|
didSet { userDefaults.set(hudOpacity, forKey: "hudOpacity") }
|
|
}
|
|
|
|
@Published public var hudSize: Double {
|
|
didSet { userDefaults.set(hudSize, forKey: "hudSize") }
|
|
}
|
|
|
|
// Model Settings
|
|
@Published public var activeModelName: String? {
|
|
didSet { userDefaults.set(activeModelName, forKey: "activeModelName") }
|
|
}
|
|
|
|
@Published public var forcedLanguage: String? {
|
|
didSet { userDefaults.set(forcedLanguage, forKey: "forcedLanguage") }
|
|
}
|
|
|
|
// Insertion Settings
|
|
@Published public var insertionMethod: InsertionMethod {
|
|
didSet { userDefaults.set(insertionMethod.rawValue, forKey: "insertionMethod") }
|
|
}
|
|
|
|
@Published public var showPreview: Bool {
|
|
didSet { userDefaults.set(showPreview, forKey: "showPreview") }
|
|
}
|
|
|
|
// Advanced Settings
|
|
@Published public var enableLogging: Bool {
|
|
didSet { userDefaults.set(enableLogging, forKey: "enableLogging") }
|
|
}
|
|
|
|
@Published public var processingThreads: Int {
|
|
didSet { userDefaults.set(processingThreads, forKey: "processingThreads") }
|
|
}
|
|
|
|
public init() {
|
|
// Load settings from UserDefaults
|
|
self.hotkey = Settings.loadHotkey()
|
|
self.hotkeyMode = HotkeyMode(rawValue: userDefaults.string(forKey: "hotkeyMode") ?? "") ?? .pushToTalk
|
|
self.playSounds = userDefaults.object(forKey: "playSounds") as? Bool ?? false
|
|
self.dictationTimeLimit = userDefaults.object(forKey: "dictationTimeLimit") as? TimeInterval ?? 600 // 10 minutes
|
|
|
|
// HUD Settings
|
|
self.hudOpacity = userDefaults.object(forKey: "hudOpacity") as? Double ?? 0.9
|
|
self.hudSize = userDefaults.object(forKey: "hudSize") as? Double ?? 1.0
|
|
|
|
// Model Settings
|
|
self.activeModelName = userDefaults.string(forKey: "activeModelName")
|
|
self.forcedLanguage = userDefaults.string(forKey: "forcedLanguage")
|
|
|
|
// Insertion Settings
|
|
let insertionMethodString = userDefaults.string(forKey: "insertionMethod") ?? "paste"
|
|
self.insertionMethod = InsertionMethod(rawValue: insertionMethodString) ?? .paste
|
|
self.showPreview = userDefaults.object(forKey: "showPreview") as? Bool ?? false
|
|
|
|
// Advanced Settings
|
|
self.enableLogging = userDefaults.object(forKey: "enableLogging") as? Bool ?? false
|
|
self.processingThreads = userDefaults.object(forKey: "processingThreads") as? Int ?? 4
|
|
|
|
logger.info("Settings initialized")
|
|
}
|
|
|
|
public func exportSettings() throws -> Data {
|
|
let settingsDict: [String: Any] = [
|
|
"hotkeyKeyCode": hotkey.keyCode,
|
|
"hotkeyModifiers": hotkey.modifiers,
|
|
"hotkeyMode": hotkeyMode.rawValue,
|
|
"playSounds": playSounds,
|
|
"dictationTimeLimit": dictationTimeLimit,
|
|
"hudOpacity": hudOpacity,
|
|
"hudSize": hudSize,
|
|
"activeModelName": activeModelName as Any,
|
|
"forcedLanguage": forcedLanguage as Any,
|
|
"insertionMethod": insertionMethod.rawValue,
|
|
"showPreview": showPreview,
|
|
"enableLogging": enableLogging,
|
|
"processingThreads": processingThreads
|
|
]
|
|
|
|
return try JSONSerialization.data(withJSONObject: settingsDict, options: .prettyPrinted)
|
|
}
|
|
|
|
public func importSettings(from data: Data) throws {
|
|
let settingsDict = try JSONSerialization.jsonObject(with: data) as? [String: Any] ?? [:]
|
|
|
|
if let keyCode = settingsDict["hotkeyKeyCode"] as? UInt32,
|
|
let modifiers = settingsDict["hotkeyModifiers"] as? UInt32 {
|
|
hotkey = HotkeyConfig(keyCode: keyCode, modifiers: modifiers)
|
|
}
|
|
|
|
if let modeString = settingsDict["hotkeyMode"] as? String,
|
|
let mode = HotkeyMode(rawValue: modeString) {
|
|
hotkeyMode = mode
|
|
}
|
|
|
|
if let sounds = settingsDict["playSounds"] as? Bool {
|
|
playSounds = sounds
|
|
}
|
|
|
|
if let timeLimit = settingsDict["dictationTimeLimit"] as? TimeInterval {
|
|
dictationTimeLimit = timeLimit
|
|
}
|
|
|
|
if let opacity = settingsDict["hudOpacity"] as? Double {
|
|
hudOpacity = opacity
|
|
}
|
|
|
|
if let size = settingsDict["hudSize"] as? Double {
|
|
hudSize = size
|
|
}
|
|
|
|
activeModelName = settingsDict["activeModelName"] as? String
|
|
forcedLanguage = settingsDict["forcedLanguage"] as? String
|
|
|
|
if let methodString = settingsDict["insertionMethod"] as? String,
|
|
let method = InsertionMethod(rawValue: methodString) {
|
|
insertionMethod = method
|
|
}
|
|
|
|
if let preview = settingsDict["showPreview"] as? Bool {
|
|
showPreview = preview
|
|
}
|
|
|
|
if let logging = settingsDict["enableLogging"] as? Bool {
|
|
enableLogging = logging
|
|
}
|
|
|
|
if let threads = settingsDict["processingThreads"] as? Int {
|
|
processingThreads = threads
|
|
}
|
|
|
|
logger.info("Settings imported successfully")
|
|
}
|
|
|
|
private static func loadHotkey() -> HotkeyConfig {
|
|
let keyCode = UserDefaults.standard.object(forKey: "hotkeyKeyCode") as? UInt32 ?? HotkeyConfig.default.keyCode
|
|
let modifiers = UserDefaults.standard.object(forKey: "hotkeyModifiers") as? UInt32 ?? HotkeyConfig.default.modifiers
|
|
return HotkeyConfig(keyCode: keyCode, modifiers: modifiers)
|
|
}
|
|
|
|
private func saveHotkey() {
|
|
userDefaults.set(hotkey.keyCode, forKey: "hotkeyKeyCode")
|
|
userDefaults.set(hotkey.modifiers, forKey: "hotkeyModifiers")
|
|
}
|
|
|
|
private func saveHotkeyMode() {
|
|
userDefaults.set(hotkeyMode.rawValue, forKey: "hotkeyMode")
|
|
}
|
|
} |