Files
Bitcoin-Monitoring/test1/AppSettings.swift

354 lines
12 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// AppSettings.swift
// Bitcoin Monitoring
//
// Created by Mark on 2025/10/29.
//
import Foundation
import Combine
import ServiceManagement
///
///
@MainActor
class AppSettings: ObservableObject {
// MARK: - Published Properties
///
@Published var refreshInterval: RefreshInterval = .thirtySeconds
///
@Published var selectedSymbol: CryptoSymbol = .btc
///
@Published var launchAtLogin: Bool = false
// MARK: -
///
@Published var proxyEnabled: Bool = false
///
@Published var proxyHost: String = ""
///
@Published var proxyPort: Int = 8080
// MARK: - Private Properties
private let defaults = UserDefaults.standard
private let refreshIntervalKey = "BTCRefreshInterval"
private let selectedSymbolKey = "SelectedCryptoSymbol"
private let launchAtLoginKey = "LaunchAtLogin"
// MARK: -
private let proxyEnabledKey = "ProxyEnabled"
private let proxyHostKey = "ProxyHost"
private let proxyPortKey = "ProxyPort"
// MARK: - Initialization
init() {
loadSettings()
}
// MARK: - Configuration Methods
/// UserDefaults
/// 使30 + BTC
func loadSettings() {
#if DEBUG
print("🔧 [AppSettings] 开始加载配置...")
#endif
let hasRefreshIntervalKey = defaults.object(forKey: refreshIntervalKey) != nil
let savedIntervalValue = defaults.double(forKey: refreshIntervalKey)
#if DEBUG
print("🔧 [AppSettings] 刷新间隔键是否存在: \(hasRefreshIntervalKey)")
print("🔧 [AppSettings] 从 UserDefaults 读取刷新间隔: \(savedIntervalValue)")
#endif
if hasRefreshIntervalKey,
let savedInterval = RefreshInterval.allCases.first(where: { $0.rawValue == savedIntervalValue }) {
refreshInterval = savedInterval
#if DEBUG
print("🔧 [AppSettings] ✅ 使用保存的刷新间隔: \(savedInterval.displayText)")
#endif
} else {
refreshInterval = .thirtySeconds
#if DEBUG
print("🔧 [AppSettings] ❌ 未找到有效刷新间隔,使用默认值: \(refreshInterval.displayText)")
#endif
saveRefreshInterval(.thirtySeconds)
}
let hasSymbolKey = defaults.object(forKey: selectedSymbolKey) != nil
let savedSymbolRaw = defaults.string(forKey: selectedSymbolKey)
#if DEBUG
print("🔧 [AppSettings] 币种键是否存在: \(hasSymbolKey)")
if let symbol = savedSymbolRaw {
print("🔧 [AppSettings] 从 UserDefaults 读取币种: \(symbol)")
} else {
print("🔧 [AppSettings] 从 UserDefaults 读取币种: nil")
}
#endif
//
if hasSymbolKey,
let savedSymbolRaw = savedSymbolRaw,
!savedSymbolRaw.isEmpty, //
let savedSymbol = CryptoSymbol(rawValue: savedSymbolRaw) {
//
if CryptoSymbol.allCases.contains(savedSymbol) {
selectedSymbol = savedSymbol
#if DEBUG
print("🔧 [AppSettings] ✅ 使用保存的币种: \(savedSymbol.displayName)")
#endif
} else {
//
selectedSymbol = .btc
#if DEBUG
print("🔧 [AppSettings] ⚠️ 保存的币种不在支持列表中,重置为默认值: \(selectedSymbol.displayName)")
#endif
saveSelectedSymbol(.btc)
}
} else {
selectedSymbol = .btc
#if DEBUG
print("🔧 [AppSettings] ❌ 未找到有效币种配置,使用默认值: \(selectedSymbol.displayName)")
#endif
saveSelectedSymbol(.btc)
}
//
launchAtLogin = defaults.bool(forKey: launchAtLoginKey)
//
proxyEnabled = defaults.bool(forKey: proxyEnabledKey)
proxyHost = defaults.string(forKey: proxyHostKey) ?? ""
proxyPort = defaults.integer(forKey: proxyPortKey)
if proxyPort == 0 { proxyPort = 8080 } //
//
checkAndSyncLaunchAtLoginStatus()
#if DEBUG
print("🔧 [AppSettings] 配置加载完成 - 刷新间隔: \(refreshInterval.displayText), 币种: \(selectedSymbol.displayName), 开机自启动: \(launchAtLogin), 代理: \(proxyEnabled ? "\(proxyHost):\(proxyPort)" : "未启用")")
#endif
}
///
///
func resetToDefaults() {
#if DEBUG
print("🔧 [AppSettings] 重置所有设置为默认值")
#endif
refreshInterval = .thirtySeconds
selectedSymbol = .btc
//
saveRefreshInterval(.thirtySeconds)
saveSelectedSymbol(.btc)
//
proxyEnabled = false
proxyHost = ""
proxyPort = 8080
defaults.set(false, forKey: proxyEnabledKey)
defaults.set("", forKey: proxyHostKey)
defaults.set(8080, forKey: proxyPortKey)
#if DEBUG
print("🔧 [AppSettings] 重置完成 - 刷新间隔: \(refreshInterval.displayText), 币种: \(selectedSymbol.displayName), 代理: 已重置")
#endif
//
launchAtLogin = false
defaults.set(false, forKey: launchAtLoginKey)
//
toggleLoginItem(enabled: false)
}
///
/// - Parameter interval:
func saveRefreshInterval(_ interval: RefreshInterval) {
refreshInterval = interval
defaults.set(interval.rawValue, forKey: refreshIntervalKey)
}
///
/// - Parameter symbol:
func saveSelectedSymbol(_ symbol: CryptoSymbol) {
selectedSymbol = symbol
#if DEBUG
print("🔧 [AppSettings] 保存币种配置: \(symbol.displayName) (\(symbol.rawValue))")
#endif
defaults.set(symbol.rawValue, forKey: selectedSymbolKey)
}
// MARK: -
///
/// - Parameters:
/// - enabled:
/// - host:
/// - port:
func saveProxySettings(enabled: Bool, host: String, port: Int) {
proxyEnabled = enabled
proxyHost = host.trimmingCharacters(in: .whitespacesAndNewlines)
proxyPort = port
// UserDefaults
defaults.set(enabled, forKey: proxyEnabledKey)
defaults.set(proxyHost, forKey: proxyHostKey)
defaults.set(port, forKey: proxyPortKey)
#if DEBUG
if enabled {
print("🔧 [AppSettings] 保存代理设置: \(proxyHost):\(proxyPort)")
} else {
print("🔧 [AppSettings] 保存代理设置: 已禁用")
}
#endif
}
///
/// - Returns:
func validateProxySettings() -> (isValid: Bool, errorMessage: String?) {
guard proxyEnabled else {
return (true, nil) //
}
let trimmedHost = proxyHost.trimmingCharacters(in: .whitespacesAndNewlines)
//
if trimmedHost.isEmpty {
return (false, "代理服务器地址不能为空")
}
// IP
if !isValidHost(trimmedHost) {
return (false, "代理服务器地址格式不正确")
}
//
if proxyPort < 1 || proxyPort > 65535 {
return (false, "代理端口必须在 1-65535 范围内")
}
return (true, nil)
}
///
/// - Parameter host:
/// - Returns:
private func isValidHost(_ host: String) -> Bool {
// IP
if host.matches(pattern: #"^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"#) {
return true
}
//
if host.matches(pattern: #"^[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)*$"#) {
return true
}
return false
}
// MARK: -
///
/// - Parameter enabled:
func toggleLoginItem(enabled: Bool) {
// macOS SMAppService (macOS 13+)
if #available(macOS 13.0, *) {
do {
if enabled {
try SMAppService.mainApp.register()
#if DEBUG
print("🔧 [AppSettings] ✅ 开机自启动已启用")
#endif
} else {
try SMAppService.mainApp.unregister()
#if DEBUG
print("🔧 [AppSettings] ❌ 开机自启动已禁用")
#endif
}
// UserDefaults
launchAtLogin = enabled
defaults.set(enabled, forKey: launchAtLoginKey)
} catch {
#if DEBUG
print("🔧 [AppSettings] ⚠️ 设置开机自启动失败: \(error.localizedDescription)")
#endif
//
let actualStatus = SMAppService.mainApp.status
launchAtLogin = (actualStatus == .enabled)
defaults.set(launchAtLogin, forKey: launchAtLoginKey)
}
} else {
// macOS 13
#if DEBUG
print("🔧 [AppSettings] ⚠️ 当前 macOS 版本不支持 SMAppService无法设置开机自启动")
#endif
}
}
///
///
private func checkAndSyncLaunchAtLoginStatus() {
guard #available(macOS 13.0, *) else {
#if DEBUG
print("🔧 [AppSettings] ⚠️ 当前 macOS 版本不支持 SMAppService")
#endif
return
}
let actualStatus = SMAppService.mainApp.status
let isEnabled = (actualStatus == .enabled)
//
if isEnabled != launchAtLogin {
launchAtLogin = isEnabled
defaults.set(isEnabled, forKey: launchAtLoginKey)
#if DEBUG
print("🔧 [AppSettings] 🔄 已同步开机自启动状态: \(isEnabled)")
#endif
}
}
///
/// - Returns:
func isLaunchAtLoginEnabled() -> Bool {
guard #available(macOS 13.0, *) else {
return false
}
let actualStatus = SMAppService.mainApp.status
return actualStatus == .enabled
}
}
// MARK: - String Extension for Regex Matching
extension String {
///
/// - Parameter pattern:
/// - Returns:
func matches(pattern: String) -> Bool {
guard let regex = try? NSRegularExpression(pattern: pattern, options: .caseInsensitive) else {
return false
}
let range = NSRange(location: 0, length: self.utf16.count)
return regex.firstMatch(in: self, options: [], range: range) != nil
}
}