mirror of
https://github.com/jiayouzl/Bitcoin-Monitoring.git
synced 2025-11-26 03:44:57 +08:00
feat: 更新自定义币种管理,支持多个币种及验证功能
This commit is contained in:
@@ -273,7 +273,7 @@
|
|||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 13.5;
|
MACOSX_DEPLOYMENT_TARGET = 13.5;
|
||||||
MARKETING_VERSION = 1.1.1;
|
MARKETING_VERSION = 1.2.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.mark.bitcoin-monitoring";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.mark.bitcoin-monitoring";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
@@ -306,7 +306,7 @@
|
|||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 13.5;
|
MACOSX_DEPLOYMENT_TARGET = 13.5;
|
||||||
MARKETING_VERSION = 1.1.1;
|
MARKETING_VERSION = 1.2.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.mark.bitcoin-monitoring";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.mark.bitcoin-monitoring";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ class MenuBarManager: NSObject, ObservableObject {
|
|||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
// 监听自定义币种配置变化
|
// 监听自定义币种配置变化
|
||||||
appSettings.$customCryptoSymbol
|
appSettings.$customCryptoSymbols
|
||||||
.sink { [weak self] _ in
|
.sink { [weak self] _ in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
self.priceManager.updateCryptoSymbolSettings()
|
self.priceManager.updateCryptoSymbolSettings()
|
||||||
@@ -245,8 +245,8 @@ class MenuBarManager: NSObject, ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 添加自定义币种菜单项(如果存在)- 显示在最后
|
// 添加自定义币种菜单项(如果存在)- 显示在最后
|
||||||
var customSymbolMenuItem: NSMenuItem?
|
var customSymbolMenuItems: [NSMenuItem] = []
|
||||||
if let customSymbol = appSettings.customCryptoSymbol {
|
for customSymbol in appSettings.customCryptoSymbols {
|
||||||
let isCurrent = customSymbol.isCurrentSymbol(currentApiSymbol)
|
let isCurrent = customSymbol.isCurrentSymbol(currentApiSymbol)
|
||||||
let placeholderTitle = isCurrent ? "✓ \(customSymbol.displayName) (自定义): 加载中..." : " \(customSymbol.displayName) (自定义): 加载中..."
|
let placeholderTitle = isCurrent ? "✓ \(customSymbol.displayName) (自定义): 加载中..." : " \(customSymbol.displayName) (自定义): 加载中..."
|
||||||
let item = NSMenuItem(title: placeholderTitle, action: #selector(self.selectOrCopySymbol(_:)), keyEquivalent: "")
|
let item = NSMenuItem(title: placeholderTitle, action: #selector(self.selectOrCopySymbol(_:)), keyEquivalent: "")
|
||||||
@@ -258,7 +258,7 @@ class MenuBarManager: NSObject, ObservableObject {
|
|||||||
item.isEnabled = true
|
item.isEnabled = true
|
||||||
item.representedObject = ["customSymbol": customSymbol, "price": 0.0, "isCustom": true]
|
item.representedObject = ["customSymbol": customSymbol, "price": 0.0, "isCustom": true]
|
||||||
menu.addItem(item)
|
menu.addItem(item)
|
||||||
customSymbolMenuItem = item
|
customSymbolMenuItems.append(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 异步并发获取所有币种价格并更新对应的菜单项
|
// 异步并发获取所有币种价格并更新对应的菜单项
|
||||||
@@ -294,22 +294,24 @@ class MenuBarManager: NSObject, ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 更新自定义币种菜单项
|
// 更新自定义币种菜单项
|
||||||
if let customSymbol = self.appSettings.customCryptoSymbol,
|
for (index, customSymbol) in self.appSettings.customCryptoSymbols.enumerated() {
|
||||||
let menuItem = customSymbolMenuItem {
|
if index < customSymbolMenuItems.count {
|
||||||
let isCurrent = customSymbol.isCurrentSymbol(currentSymbolAfter)
|
let menuItem = customSymbolMenuItems[index]
|
||||||
|
let isCurrent = customSymbol.isCurrentSymbol(currentSymbolAfter)
|
||||||
|
|
||||||
if let price = await self.priceManager.fetchCustomSymbolPrice(forApiSymbol: customSymbol.apiSymbol) {
|
if let price = await self.priceManager.fetchCustomSymbolPrice(forApiSymbol: customSymbol.apiSymbol) {
|
||||||
let title = isCurrent ? "✓ \(customSymbol.displayName) (自定义): $\(self.formatPriceWithCommas(price))" : " \(customSymbol.displayName) (自定义): $\(self.formatPriceWithCommas(price))"
|
let title = isCurrent ? "✓ \(customSymbol.displayName) (自定义): $\(self.formatPriceWithCommas(price))" : " \(customSymbol.displayName) (自定义): $\(self.formatPriceWithCommas(price))"
|
||||||
menuItem.title = title
|
menuItem.title = title
|
||||||
menuItem.isEnabled = true
|
menuItem.isEnabled = true
|
||||||
menuItem.target = self
|
menuItem.target = self
|
||||||
menuItem.representedObject = ["customSymbol": customSymbol, "price": price, "isCustom": true]
|
menuItem.representedObject = ["customSymbol": customSymbol, "price": price, "isCustom": true]
|
||||||
} else {
|
} else {
|
||||||
let title = isCurrent ? "✓ \(customSymbol.displayName) (自定义): 错误" : " \(customSymbol.displayName) (自定义): 错误"
|
let title = isCurrent ? "✓ \(customSymbol.displayName) (自定义): 错误" : " \(customSymbol.displayName) (自定义): 错误"
|
||||||
menuItem.title = title
|
menuItem.title = title
|
||||||
menuItem.isEnabled = false
|
menuItem.isEnabled = false
|
||||||
menuItem.target = self
|
menuItem.target = self
|
||||||
menuItem.representedObject = ["customSymbol": customSymbol, "price": 0.0, "isCustom": true]
|
menuItem.representedObject = ["customSymbol": customSymbol, "price": 0.0, "isCustom": true]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -500,9 +502,11 @@ class MenuBarManager: NSObject, ObservableObject {
|
|||||||
} else {
|
} else {
|
||||||
// 选择该币种
|
// 选择该币种
|
||||||
if isCustom, let customSymbol = data["customSymbol"] as? CustomCryptoSymbol {
|
if isCustom, let customSymbol = data["customSymbol"] as? CustomCryptoSymbol {
|
||||||
// 选择自定义币种
|
// 选择自定义币种 - 找到对应的索引并选择
|
||||||
appSettings.saveCustomCryptoSymbol(customSymbol)
|
if let index = appSettings.customCryptoSymbols.firstIndex(of: customSymbol) {
|
||||||
print("✅ 已切换到自定义币种: \(customSymbol.displayName)")
|
appSettings.selectCustomCryptoSymbol(at: index)
|
||||||
|
print("✅ 已切换到自定义币种: \(customSymbol.displayName)")
|
||||||
|
}
|
||||||
|
|
||||||
// 立即更新价格管理器
|
// 立即更新价格管理器
|
||||||
self.priceManager.updateCryptoSymbolSettings()
|
self.priceManager.updateCryptoSymbolSettings()
|
||||||
|
|||||||
@@ -25,8 +25,10 @@ class AppSettings: ObservableObject {
|
|||||||
|
|
||||||
// MARK: - 自定义币种相关属性
|
// MARK: - 自定义币种相关属性
|
||||||
|
|
||||||
/// 当前选中的自定义币种(如果有)
|
/// 自定义币种列表(最多5个)
|
||||||
@Published var customCryptoSymbol: CustomCryptoSymbol?
|
@Published var customCryptoSymbols: [CustomCryptoSymbol] = []
|
||||||
|
/// 当前选中的自定义币种索引(如果使用自定义币种)
|
||||||
|
@Published var selectedCustomSymbolIndex: Int?
|
||||||
/// 是否使用自定义币种
|
/// 是否使用自定义币种
|
||||||
@Published var useCustomSymbol: Bool = false
|
@Published var useCustomSymbol: Bool = false
|
||||||
|
|
||||||
@@ -52,7 +54,8 @@ class AppSettings: ObservableObject {
|
|||||||
|
|
||||||
// MARK: - 自定义币种配置键值
|
// MARK: - 自定义币种配置键值
|
||||||
|
|
||||||
private let customSymbolKey = "CustomCryptoSymbol"
|
private let customSymbolsKey = "CustomCryptoSymbols"
|
||||||
|
private let selectedCustomSymbolIndexKey = "SelectedCustomSymbolIndex"
|
||||||
private let useCustomSymbolKey = "UseCustomSymbol"
|
private let useCustomSymbolKey = "UseCustomSymbol"
|
||||||
|
|
||||||
// MARK: - 代理配置键值
|
// MARK: - 代理配置键值
|
||||||
@@ -141,17 +144,26 @@ class AppSettings: ObservableObject {
|
|||||||
// 加载开机自启动设置
|
// 加载开机自启动设置
|
||||||
launchAtLogin = defaults.bool(forKey: launchAtLoginKey)
|
launchAtLogin = defaults.bool(forKey: launchAtLoginKey)
|
||||||
|
|
||||||
// 加载自定义币种设置 - 总是先尝试读取数据
|
// 加载自定义币种设置
|
||||||
if let customSymbolData = defaults.data(forKey: customSymbolKey),
|
if let customSymbolsData = defaults.data(forKey: customSymbolsKey),
|
||||||
let customSymbol = try? JSONDecoder().decode(CustomCryptoSymbol.self, from: customSymbolData) {
|
let customSymbols = try? JSONDecoder().decode([CustomCryptoSymbol].self, from: customSymbolsData) {
|
||||||
customCryptoSymbol = customSymbol
|
customCryptoSymbols = customSymbols
|
||||||
|
// 加载选中的自定义币种索引
|
||||||
|
let savedIndex = defaults.integer(forKey: selectedCustomSymbolIndexKey)
|
||||||
|
if savedIndex >= 0 && savedIndex < customSymbols.count {
|
||||||
|
selectedCustomSymbolIndex = savedIndex
|
||||||
|
}
|
||||||
// 根据保存的状态决定是否使用自定义币种
|
// 根据保存的状态决定是否使用自定义币种
|
||||||
useCustomSymbol = defaults.bool(forKey: useCustomSymbolKey)
|
useCustomSymbol = defaults.bool(forKey: useCustomSymbolKey)
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
print("🔧 [AppSettings] ✅ 已加载自定义币种: \(customSymbol.displayName),使用状态: \(useCustomSymbol)")
|
print("🔧 [AppSettings] ✅ 已加载 \(customSymbols.count) 个自定义币种,使用状态: \(useCustomSymbol)")
|
||||||
|
if let index = selectedCustomSymbolIndex {
|
||||||
|
print("🔧 [AppSettings] 当前选中自定义币种: \(customSymbols[index].displayName)")
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
} else {
|
} else {
|
||||||
customCryptoSymbol = nil
|
customCryptoSymbols = []
|
||||||
|
selectedCustomSymbolIndex = nil
|
||||||
useCustomSymbol = false
|
useCustomSymbol = false
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
print("🔧 [AppSettings] ℹ️ 未找到自定义币种数据")
|
print("🔧 [AppSettings] ℹ️ 未找到自定义币种数据")
|
||||||
@@ -172,7 +184,7 @@ class AppSettings: ObservableObject {
|
|||||||
#if DEBUG
|
#if DEBUG
|
||||||
let proxyInfo = proxyEnabled ? "\(proxyHost):\(proxyPort)" : "未启用"
|
let proxyInfo = proxyEnabled ? "\(proxyHost):\(proxyPort)" : "未启用"
|
||||||
let authInfo = proxyEnabled && !proxyUsername.isEmpty ? " (认证: \(proxyUsername))" : ""
|
let authInfo = proxyEnabled && !proxyUsername.isEmpty ? " (认证: \(proxyUsername))" : ""
|
||||||
let customInfo = useCustomSymbol && customCryptoSymbol != nil ? " (自定义: \(customCryptoSymbol!.displayName))" : ""
|
let customInfo = useCustomSymbol && !customCryptoSymbols.isEmpty ? " (自定义: \(customCryptoSymbols.count)个)" : ""
|
||||||
print("🔧 [AppSettings] 配置加载完成 - 刷新间隔: \(refreshInterval.displayText), 币种: \(getCurrentActiveDisplayName())\(customInfo), 开机自启动: \(launchAtLogin), 代理: \(proxyInfo)\(authInfo)")
|
print("🔧 [AppSettings] 配置加载完成 - 刷新间隔: \(refreshInterval.displayText), 币种: \(getCurrentActiveDisplayName())\(customInfo), 开机自启动: \(launchAtLogin), 代理: \(proxyInfo)\(authInfo)")
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@@ -193,9 +205,11 @@ class AppSettings: ObservableObject {
|
|||||||
|
|
||||||
// 重置自定义币种设置
|
// 重置自定义币种设置
|
||||||
useCustomSymbol = false
|
useCustomSymbol = false
|
||||||
customCryptoSymbol = nil
|
customCryptoSymbols = []
|
||||||
|
selectedCustomSymbolIndex = nil
|
||||||
defaults.set(false, forKey: useCustomSymbolKey)
|
defaults.set(false, forKey: useCustomSymbolKey)
|
||||||
defaults.removeObject(forKey: customSymbolKey)
|
defaults.removeObject(forKey: customSymbolsKey)
|
||||||
|
defaults.removeObject(forKey: selectedCustomSymbolIndexKey)
|
||||||
|
|
||||||
// 重置代理设置
|
// 重置代理设置
|
||||||
proxyEnabled = false
|
proxyEnabled = false
|
||||||
@@ -236,11 +250,13 @@ class AppSettings: ObservableObject {
|
|||||||
// 如果当前正在使用自定义币种,只是切换使用状态,不删除数据
|
// 如果当前正在使用自定义币种,只是切换使用状态,不删除数据
|
||||||
if useCustomSymbol {
|
if useCustomSymbol {
|
||||||
useCustomSymbol = false
|
useCustomSymbol = false
|
||||||
|
selectedCustomSymbolIndex = nil
|
||||||
defaults.set(false, forKey: useCustomSymbolKey)
|
defaults.set(false, forKey: useCustomSymbolKey)
|
||||||
|
defaults.removeObject(forKey: selectedCustomSymbolIndexKey)
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
if let customSymbol = customCryptoSymbol {
|
if !customCryptoSymbols.isEmpty {
|
||||||
print("🔧 [AppSettings] ✅ 已切换到默认币种: \(symbol.displayName),自定义币种 \(customSymbol.displayName) 保留")
|
print("🔧 [AppSettings] ✅ 已切换到默认币种: \(symbol.displayName),\(customCryptoSymbols.count) 个自定义币种保留")
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@@ -407,43 +423,133 @@ class AppSettings: ObservableObject {
|
|||||||
|
|
||||||
// MARK: - 自定义币种相关方法
|
// MARK: - 自定义币种相关方法
|
||||||
|
|
||||||
/// 保存自定义币种设置
|
/// 添加自定义币种
|
||||||
/// - Parameter customSymbol: 要保存的自定义币种
|
/// - Parameter customSymbol: 要添加的自定义币种
|
||||||
func saveCustomCryptoSymbol(_ customSymbol: CustomCryptoSymbol) {
|
/// - Returns: 是否添加成功
|
||||||
customCryptoSymbol = customSymbol
|
@discardableResult
|
||||||
useCustomSymbol = true
|
func addCustomCryptoSymbol(_ customSymbol: CustomCryptoSymbol) -> Bool {
|
||||||
|
// 检查是否已达到最大数量限制
|
||||||
do {
|
guard customCryptoSymbols.count < 5 else {
|
||||||
let data = try JSONEncoder().encode(customSymbol)
|
|
||||||
defaults.set(data, forKey: customSymbolKey)
|
|
||||||
defaults.set(true, forKey: useCustomSymbolKey)
|
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
print("🔧 [AppSettings] ✅ 已保存自定义币种: \(customSymbol.displayName)")
|
print("🔧 [AppSettings] ⚠️ 已达到最大自定义币种数量限制 (5个)")
|
||||||
#endif
|
|
||||||
} catch {
|
|
||||||
#if DEBUG
|
|
||||||
print("🔧 [AppSettings] ❌ 保存自定义币种失败: \(error.localizedDescription)")
|
|
||||||
#endif
|
#endif
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// 移除自定义币种
|
// 检查是否已存在相同的币种
|
||||||
func removeCustomCryptoSymbol() {
|
guard !customCryptoSymbols.contains(customSymbol) else {
|
||||||
customCryptoSymbol = nil
|
#if DEBUG
|
||||||
useCustomSymbol = false
|
print("🔧 [AppSettings] ⚠️ 自定义币种已存在: \(customSymbol.displayName)")
|
||||||
defaults.removeObject(forKey: customSymbolKey)
|
#endif
|
||||||
defaults.set(false, forKey: useCustomSymbolKey)
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
customCryptoSymbols.append(customSymbol)
|
||||||
|
|
||||||
|
// 如果这是第一个自定义币种,自动选中并启用自定义币种模式
|
||||||
|
if customCryptoSymbols.count == 1 {
|
||||||
|
selectedCustomSymbolIndex = 0
|
||||||
|
useCustomSymbol = true
|
||||||
|
defaults.set(true, forKey: useCustomSymbolKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存到 UserDefaults
|
||||||
|
saveCustomCryptoSymbols()
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
print("🔧 [AppSettings] ✅ 已移除自定义币种")
|
print("🔧 [AppSettings] ✅ 已添加自定义币种: \(customSymbol.displayName),当前总数: \(customCryptoSymbols.count)")
|
||||||
#endif
|
#endif
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 移除指定索引的自定义币种
|
||||||
|
/// - Parameter index: 要移除的币种索引
|
||||||
|
func removeCustomCryptoSymbol(at index: Int) {
|
||||||
|
guard index >= 0 && index < customCryptoSymbols.count else {
|
||||||
|
#if DEBUG
|
||||||
|
print("🔧 [AppSettings] ⚠️ 无效的自定义币种索引: \(index)")
|
||||||
|
#endif
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let removedSymbol = customCryptoSymbols[index]
|
||||||
|
customCryptoSymbols.remove(at: index)
|
||||||
|
|
||||||
|
// 如果移除的是当前选中的币种,需要调整选中状态
|
||||||
|
if selectedCustomSymbolIndex == index {
|
||||||
|
// 如果还有其他自定义币种,选中第一个;否则切换到系统默认币种
|
||||||
|
if !customCryptoSymbols.isEmpty {
|
||||||
|
selectedCustomSymbolIndex = 0
|
||||||
|
} else {
|
||||||
|
// 没有自定义币种了,切换到系统默认币种
|
||||||
|
selectedCustomSymbolIndex = nil
|
||||||
|
useCustomSymbol = false
|
||||||
|
defaults.set(false, forKey: useCustomSymbolKey)
|
||||||
|
}
|
||||||
|
} else if let selectedIndex = selectedCustomSymbolIndex, selectedIndex > index {
|
||||||
|
// 如果选中的币种在移除的币种之后,需要调整索引
|
||||||
|
selectedCustomSymbolIndex = selectedIndex - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存到 UserDefaults
|
||||||
|
if let selectedIndex = selectedCustomSymbolIndex {
|
||||||
|
defaults.set(selectedIndex, forKey: selectedCustomSymbolIndexKey)
|
||||||
|
} else {
|
||||||
|
defaults.removeObject(forKey: selectedCustomSymbolIndexKey)
|
||||||
|
}
|
||||||
|
saveCustomCryptoSymbols()
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
print("🔧 [AppSettings] ✅ 已移除自定义币种: \(removedSymbol.displayName),剩余: \(customCryptoSymbols.count)")
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 选择指定的自定义币种
|
||||||
|
/// - Parameter index: 要选中的币种索引
|
||||||
|
func selectCustomCryptoSymbol(at index: Int) {
|
||||||
|
guard index >= 0 && index < customCryptoSymbols.count else {
|
||||||
|
#if DEBUG
|
||||||
|
print("🔧 [AppSettings] ⚠️ 无效的自定义币种索引: \(index)")
|
||||||
|
#endif
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedCustomSymbolIndex = index
|
||||||
|
useCustomSymbol = true
|
||||||
|
defaults.set(index, forKey: selectedCustomSymbolIndexKey)
|
||||||
|
defaults.set(true, forKey: useCustomSymbolKey)
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
print("🔧 [AppSettings] ✅ 已选中自定义币种: \(customCryptoSymbols[index].displayName)")
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 获取当前选中的自定义币种
|
||||||
|
/// - Returns: 当前选中的自定义币种,如果没有则返回nil
|
||||||
|
func getCurrentSelectedCustomSymbol() -> CustomCryptoSymbol? {
|
||||||
|
guard let index = selectedCustomSymbolIndex,
|
||||||
|
index >= 0 && index < customCryptoSymbols.count else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return customCryptoSymbols[index]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 保存自定义币种列表到 UserDefaults
|
||||||
|
private func saveCustomCryptoSymbols() {
|
||||||
|
do {
|
||||||
|
let data = try JSONEncoder().encode(customCryptoSymbols)
|
||||||
|
defaults.set(data, forKey: customSymbolsKey)
|
||||||
|
} catch {
|
||||||
|
#if DEBUG
|
||||||
|
print("🔧 [AppSettings] ❌ 保存自定义币种列表失败: \(error.localizedDescription)")
|
||||||
|
#endif
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 获取当前活跃的币种API符号
|
/// 获取当前活跃的币种API符号
|
||||||
/// - Returns: 当前活跃币种的API符号
|
/// - Returns: 当前活跃币种的API符号
|
||||||
func getCurrentActiveApiSymbol() -> String {
|
func getCurrentActiveApiSymbol() -> String {
|
||||||
if useCustomSymbol, let customSymbol = customCryptoSymbol {
|
if useCustomSymbol, let customSymbol = getCurrentSelectedCustomSymbol() {
|
||||||
return customSymbol.apiSymbol
|
return customSymbol.apiSymbol
|
||||||
} else {
|
} else {
|
||||||
return selectedSymbol.apiSymbol
|
return selectedSymbol.apiSymbol
|
||||||
@@ -453,7 +559,7 @@ class AppSettings: ObservableObject {
|
|||||||
/// 获取当前活跃的币种显示名称
|
/// 获取当前活跃的币种显示名称
|
||||||
/// - Returns: 当前活跃币种的显示名称
|
/// - Returns: 当前活跃币种的显示名称
|
||||||
func getCurrentActiveDisplayName() -> String {
|
func getCurrentActiveDisplayName() -> String {
|
||||||
if useCustomSymbol, let customSymbol = customCryptoSymbol {
|
if useCustomSymbol, let customSymbol = getCurrentSelectedCustomSymbol() {
|
||||||
return customSymbol.displayName
|
return customSymbol.displayName
|
||||||
} else {
|
} else {
|
||||||
return selectedSymbol.displayName
|
return selectedSymbol.displayName
|
||||||
@@ -463,7 +569,7 @@ class AppSettings: ObservableObject {
|
|||||||
/// 获取当前活跃的币种图标
|
/// 获取当前活跃的币种图标
|
||||||
/// - Returns: 当前活跃币种的图标名称
|
/// - Returns: 当前活跃币种的图标名称
|
||||||
func getCurrentActiveSystemImageName() -> String {
|
func getCurrentActiveSystemImageName() -> String {
|
||||||
if useCustomSymbol, let customSymbol = customCryptoSymbol {
|
if useCustomSymbol, let customSymbol = getCurrentSelectedCustomSymbol() {
|
||||||
return customSymbol.systemImageName
|
return customSymbol.systemImageName
|
||||||
} else {
|
} else {
|
||||||
return selectedSymbol.systemImageName
|
return selectedSymbol.systemImageName
|
||||||
@@ -473,7 +579,7 @@ class AppSettings: ObservableObject {
|
|||||||
/// 获取当前活跃的币种交易对显示名称
|
/// 获取当前活跃的币种交易对显示名称
|
||||||
/// - Returns: 当前活跃币种的交易对显示名称
|
/// - Returns: 当前活跃币种的交易对显示名称
|
||||||
func getCurrentActivePairDisplayName() -> String {
|
func getCurrentActivePairDisplayName() -> String {
|
||||||
if useCustomSymbol, let customSymbol = customCryptoSymbol {
|
if useCustomSymbol, let customSymbol = getCurrentSelectedCustomSymbol() {
|
||||||
return customSymbol.pairDisplayName
|
return customSymbol.pairDisplayName
|
||||||
} else {
|
} else {
|
||||||
return selectedSymbol.pairDisplayName
|
return selectedSymbol.pairDisplayName
|
||||||
@@ -483,7 +589,7 @@ class AppSettings: ObservableObject {
|
|||||||
/// 判断是否正在使用自定义币种
|
/// 判断是否正在使用自定义币种
|
||||||
/// - Returns: 是否正在使用自定义币种
|
/// - Returns: 是否正在使用自定义币种
|
||||||
func isUsingCustomSymbol() -> Bool {
|
func isUsingCustomSymbol() -> Bool {
|
||||||
return useCustomSymbol && customCryptoSymbol != nil
|
return useCustomSymbol && !customCryptoSymbols.isEmpty && selectedCustomSymbolIndex != nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ class PriceManager: ObservableObject {
|
|||||||
@Published var selectedSymbol: CryptoSymbol
|
@Published var selectedSymbol: CryptoSymbol
|
||||||
|
|
||||||
// 自定义币种相关属性
|
// 自定义币种相关属性
|
||||||
@Published var customCryptoSymbol: CustomCryptoSymbol?
|
@Published var customCryptoSymbols: [CustomCryptoSymbol] = []
|
||||||
|
@Published var selectedCustomSymbolIndex: Int?
|
||||||
@Published var useCustomSymbol: Bool = false
|
@Published var useCustomSymbol: Bool = false
|
||||||
|
|
||||||
private let priceService: PriceService
|
private let priceService: PriceService
|
||||||
@@ -35,7 +36,8 @@ class PriceManager: ObservableObject {
|
|||||||
self.priceService = PriceService(appSettings: appSettings)
|
self.priceService = PriceService(appSettings: appSettings)
|
||||||
|
|
||||||
// 初始化自定义币种状态
|
// 初始化自定义币种状态
|
||||||
self.customCryptoSymbol = appSettings.customCryptoSymbol
|
self.customCryptoSymbols = appSettings.customCryptoSymbols
|
||||||
|
self.selectedCustomSymbolIndex = appSettings.selectedCustomSymbolIndex
|
||||||
self.useCustomSymbol = appSettings.useCustomSymbol
|
self.useCustomSymbol = appSettings.useCustomSymbol
|
||||||
|
|
||||||
startPriceUpdates()
|
startPriceUpdates()
|
||||||
@@ -322,7 +324,8 @@ class PriceManager: ObservableObject {
|
|||||||
|
|
||||||
/// 更新币种设置(当AppSettings中的自定义币种发生变化时调用)
|
/// 更新币种设置(当AppSettings中的自定义币种发生变化时调用)
|
||||||
func updateCryptoSymbolSettings() {
|
func updateCryptoSymbolSettings() {
|
||||||
customCryptoSymbol = appSettings.customCryptoSymbol
|
customCryptoSymbols = appSettings.customCryptoSymbols
|
||||||
|
selectedCustomSymbolIndex = appSettings.selectedCustomSymbolIndex
|
||||||
useCustomSymbol = appSettings.useCustomSymbol
|
useCustomSymbol = appSettings.useCustomSymbol
|
||||||
|
|
||||||
// 重置价格状态,强制重新获取
|
// 重置价格状态,强制重新获取
|
||||||
@@ -344,7 +347,8 @@ class PriceManager: ObservableObject {
|
|||||||
func getAllAvailableSymbols() -> [String] {
|
func getAllAvailableSymbols() -> [String] {
|
||||||
var symbols = CryptoSymbol.allApiSymbols
|
var symbols = CryptoSymbol.allApiSymbols
|
||||||
|
|
||||||
if let customSymbol = appSettings.customCryptoSymbol {
|
// 添加所有自定义币种
|
||||||
|
for customSymbol in appSettings.customCryptoSymbols {
|
||||||
symbols.append(customSymbol.apiSymbol)
|
symbols.append(customSymbol.apiSymbol)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -361,9 +365,10 @@ class PriceManager: ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否是自定义币种
|
// 检查是否是自定义币种
|
||||||
if let customSymbol = appSettings.customCryptoSymbol,
|
for customSymbol in appSettings.customCryptoSymbols {
|
||||||
customSymbol.apiSymbol == apiSymbol {
|
if customSymbol.apiSymbol == apiSymbol {
|
||||||
return customSymbol.displayName
|
return customSymbol.displayName
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果都找不到,返回API符号的基础部分(去掉USDT)
|
// 如果都找不到,返回API符号的基础部分(去掉USDT)
|
||||||
|
|||||||
@@ -240,6 +240,54 @@ class PriceService: NSObject, ObservableObject, URLSessionTaskDelegate {
|
|||||||
return price
|
return price
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 验证自定义币种是否在币安API中存在
|
||||||
|
/// - Parameter symbol: 币种符号(如 "ADA")
|
||||||
|
/// - Returns: 是否存在该币种
|
||||||
|
func validateCustomSymbol(_ symbol: String) async -> Bool {
|
||||||
|
let apiSymbol = "\(symbol)USDT"
|
||||||
|
let urlString = "\(baseURL)?symbol=\(apiSymbol)"
|
||||||
|
|
||||||
|
guard let url = URL(string: urlString) else {
|
||||||
|
#if DEBUG
|
||||||
|
print("❌ [PriceService] 验证失败:无效的URL - \(urlString)")
|
||||||
|
#endif
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
print("🔍 [PriceService] 验证币种存在性: \(apiSymbol)")
|
||||||
|
#endif
|
||||||
|
|
||||||
|
do {
|
||||||
|
// 发送网络请求验证币种是否存在
|
||||||
|
let (_, response) = try await session.data(from: url)
|
||||||
|
|
||||||
|
guard let httpResponse = response as? HTTPURLResponse else {
|
||||||
|
#if DEBUG
|
||||||
|
print("❌ [PriceService] 验证失败:无效响应 - \(symbol)")
|
||||||
|
#endif
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
let isValid = httpResponse.statusCode == 200
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
if isValid {
|
||||||
|
print("✅ [PriceService] 币种验证成功: \(symbol) 存在")
|
||||||
|
} else {
|
||||||
|
print("❌ [PriceService] 币种验证失败: \(symbol) 不存在 (HTTP \(httpResponse.statusCode))")
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return isValid
|
||||||
|
} catch {
|
||||||
|
#if DEBUG
|
||||||
|
print("❌ [PriceService] 币种验证网络错误: \(symbol) - \(error.localizedDescription)")
|
||||||
|
#endif
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - 代理配置相关方法
|
// MARK: - 代理配置相关方法
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// CustomCryptoSymbol.swift
|
// CustomCryptoSymbol.swift
|
||||||
// Bitcoin Monitoring
|
// Bitcoin Monitoring
|
||||||
//
|
//
|
||||||
// Created by Claude on 2025/11/03.
|
// Created by Mark on 2025/11/03.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|||||||
@@ -72,6 +72,15 @@ struct PreferencesWindowView: View {
|
|||||||
@State private var isCustomSymbolValid: Bool = false
|
@State private var isCustomSymbolValid: Bool = false
|
||||||
@State private var customSymbolErrorMessage: String?
|
@State private var customSymbolErrorMessage: String?
|
||||||
@State private var showingCustomSymbolDeleteConfirmation: Bool = false
|
@State private var showingCustomSymbolDeleteConfirmation: Bool = false
|
||||||
|
@State private var pendingDeleteIndex: Int? = nil
|
||||||
|
|
||||||
|
// 验证相关状态
|
||||||
|
@State private var isValidatingCustomSymbol: Bool = false
|
||||||
|
@State private var showingValidationFailureAlert: Bool = false
|
||||||
|
@State private var validationFailureMessage: String = ""
|
||||||
|
|
||||||
|
// PriceService 引用
|
||||||
|
private let priceService: PriceService
|
||||||
|
|
||||||
// 导航状态 - 当前选中的标签页
|
// 导航状态 - 当前选中的标签页
|
||||||
@State private var selectedTab: SettingsTab = .general
|
@State private var selectedTab: SettingsTab = .general
|
||||||
@@ -81,6 +90,7 @@ struct PreferencesWindowView: View {
|
|||||||
|
|
||||||
init(appSettings: AppSettings, onClose: @escaping () -> Void) {
|
init(appSettings: AppSettings, onClose: @escaping () -> Void) {
|
||||||
self.appSettings = appSettings
|
self.appSettings = appSettings
|
||||||
|
self.priceService = PriceService(appSettings: appSettings)
|
||||||
self.onClose = onClose
|
self.onClose = onClose
|
||||||
|
|
||||||
// 初始化临时状态
|
// 初始化临时状态
|
||||||
@@ -114,6 +124,11 @@ struct PreferencesWindowView: View {
|
|||||||
} message: {
|
} message: {
|
||||||
deleteCustomSymbolMessage
|
deleteCustomSymbolMessage
|
||||||
}
|
}
|
||||||
|
.alert("币种验证失败", isPresented: $showingValidationFailureAlert) {
|
||||||
|
Button("确定", role: .cancel) { }
|
||||||
|
} message: {
|
||||||
|
Text(validationFailureMessage)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 主要内容视图
|
// 主要内容视图
|
||||||
@@ -396,56 +411,121 @@ struct PreferencesWindowView: View {
|
|||||||
private var customCryptoSettingsView: some View {
|
private var customCryptoSettingsView: some View {
|
||||||
SettingsGroupView(title: "自定义币种", icon: "plus.circle") {
|
SettingsGroupView(title: "自定义币种", icon: "plus.circle") {
|
||||||
VStack(alignment: .leading, spacing: 16) {
|
VStack(alignment: .leading, spacing: 16) {
|
||||||
if appSettings.isUsingCustomSymbol() {
|
// 显示已添加的自定义币种列表
|
||||||
currentCustomSymbolView
|
if !appSettings.customCryptoSymbols.isEmpty {
|
||||||
} else {
|
customSymbolsListView
|
||||||
addCustomSymbolView
|
}
|
||||||
|
|
||||||
|
// 添加新币种的输入区域
|
||||||
|
addCustomSymbolView
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 自定义币种列表视图
|
||||||
|
private var customSymbolsListView: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 12) {
|
||||||
|
Text("已添加的自定义币种 (\(appSettings.customCryptoSymbols.count)/5)")
|
||||||
|
.font(.subheadline)
|
||||||
|
.fontWeight(.medium)
|
||||||
|
.foregroundColor(.primary)
|
||||||
|
|
||||||
|
VStack(spacing: 8) {
|
||||||
|
ForEach(0..<appSettings.customCryptoSymbols.count, id: \.self) { index in
|
||||||
|
customSymbolRowView(at: index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 当前自定义币种视图
|
// 自定义币种行视图
|
||||||
private var currentCustomSymbolView: some View {
|
private func customSymbolRowView(at index: Int) -> some View {
|
||||||
VStack(alignment: .leading, spacing: 8) {
|
let customSymbol = appSettings.customCryptoSymbols[index]
|
||||||
HStack {
|
let isSelected = appSettings.isUsingCustomSymbol() && appSettings.selectedCustomSymbolIndex == index
|
||||||
Image(systemName: appSettings.getCurrentActiveSystemImageName())
|
|
||||||
.foregroundColor(.orange)
|
|
||||||
.font(.system(size: 16))
|
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 2) {
|
return HStack {
|
||||||
Text("当前自定义币种")
|
// 选中状态指示器
|
||||||
.font(.subheadline)
|
Image(systemName: isSelected ? "checkmark.circle.fill" : "circle")
|
||||||
.fontWeight(.medium)
|
.font(.system(size: 14))
|
||||||
|
.foregroundColor(isSelected ? .blue : .secondary)
|
||||||
|
|
||||||
Text(appSettings.getCurrentActivePairDisplayName())
|
// 币种图标
|
||||||
.font(.caption)
|
Image(systemName: customSymbol.systemImageName)
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.orange)
|
||||||
}
|
.font(.system(size: 16))
|
||||||
|
|
||||||
Spacer()
|
// 币种信息
|
||||||
|
VStack(alignment: .leading, spacing: 2) {
|
||||||
|
Text(customSymbol.displayName)
|
||||||
|
.font(.subheadline)
|
||||||
|
.fontWeight(isSelected ? .medium : .regular)
|
||||||
|
.foregroundColor(.primary)
|
||||||
|
|
||||||
Button("删除") {
|
Text(customSymbol.pairDisplayName)
|
||||||
showingCustomSymbolDeleteConfirmation = true
|
.font(.caption)
|
||||||
}
|
.foregroundColor(.secondary)
|
||||||
.buttonStyle(.bordered)
|
|
||||||
.controlSize(.small)
|
|
||||||
.foregroundColor(.red)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
// 删除按钮
|
||||||
|
Button(action: {
|
||||||
|
showingCustomSymbolDeleteConfirmation = true
|
||||||
|
pendingDeleteIndex = index
|
||||||
|
}) {
|
||||||
|
Image(systemName: "trash")
|
||||||
|
.font(.system(size: 12))
|
||||||
|
.foregroundColor(.red)
|
||||||
|
}
|
||||||
|
.buttonStyle(PlainButtonStyle())
|
||||||
|
.frame(width: 24, height: 24)
|
||||||
|
.background(
|
||||||
|
Circle()
|
||||||
|
.fill(Color.red.opacity(0.1))
|
||||||
|
)
|
||||||
|
.onHover { isHovered in
|
||||||
|
if isHovered {
|
||||||
|
NSCursor.pointingHand.set()
|
||||||
|
} else {
|
||||||
|
NSCursor.arrow.set()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 12)
|
||||||
|
.padding(.vertical, 8)
|
||||||
|
.background(
|
||||||
|
RoundedRectangle(cornerRadius: 6)
|
||||||
|
.fill(isSelected ? Color.blue.opacity(0.1) : Color.clear)
|
||||||
|
)
|
||||||
|
.overlay(
|
||||||
|
RoundedRectangle(cornerRadius: 6)
|
||||||
|
.stroke(isSelected ? Color.blue : Color(NSColor.separatorColor), lineWidth: 1)
|
||||||
|
)
|
||||||
|
.contentShape(RoundedRectangle(cornerRadius: 6))
|
||||||
|
.onTapGesture {
|
||||||
|
// 点击选中币种
|
||||||
|
appSettings.selectCustomCryptoSymbol(at: index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加自定义币种视图
|
// 添加自定义币种视图
|
||||||
private var addCustomSymbolView: some View {
|
private var addCustomSymbolView: some View {
|
||||||
VStack(alignment: .leading, spacing: 12) {
|
VStack(alignment: .leading, spacing: 12) {
|
||||||
Text("添加自定义币种")
|
Text(appSettings.customCryptoSymbols.isEmpty ? "添加自定义币种" : "添加更多自定义币种")
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
.foregroundColor(.primary)
|
.foregroundColor(.primary)
|
||||||
|
|
||||||
Text("输入3-5个大写字母的币种符号(如 ADA、DOGE、SHIB)")
|
Text("输入3-5个大写字母的币种符号(如 ENA、TRX、TRUMP)")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
|
|
||||||
|
// 显示数量限制提示
|
||||||
|
if appSettings.customCryptoSymbols.count >= 5 {
|
||||||
|
Text("已达到最大限制(5个币种)")
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundColor(.orange)
|
||||||
|
}
|
||||||
|
|
||||||
customSymbolInputView
|
customSymbolInputView
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -458,7 +538,7 @@ struct PreferencesWindowView: View {
|
|||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
|
|
||||||
HStack(spacing: 12) {
|
HStack(spacing: 12) {
|
||||||
TextField("例如: ADA", text: Binding(
|
TextField("例如: TRX", text: Binding(
|
||||||
get: { customSymbolInput },
|
get: { customSymbolInput },
|
||||||
set: { newValue in
|
set: { newValue in
|
||||||
let filteredValue = newValue.filter { $0.isLetter }.uppercased()
|
let filteredValue = newValue.filter { $0.isLetter }.uppercased()
|
||||||
@@ -471,13 +551,31 @@ struct PreferencesWindowView: View {
|
|||||||
))
|
))
|
||||||
.textFieldStyle(RoundedBorderTextFieldStyle())
|
.textFieldStyle(RoundedBorderTextFieldStyle())
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
|
.onSubmit {
|
||||||
|
// 按回车键触发添加自定义币种
|
||||||
|
Task {
|
||||||
|
await addCustomSymbolWithValidation()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Button("添加") {
|
Button {
|
||||||
addCustomSymbol()
|
Task {
|
||||||
|
await addCustomSymbolWithValidation()
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
if isValidatingCustomSymbol {
|
||||||
|
HStack(spacing: 4) {
|
||||||
|
ProgressView()
|
||||||
|
.controlSize(.small)
|
||||||
|
Text("验证中...")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Text("添加")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.buttonStyle(.borderedProminent)
|
.buttonStyle(.borderedProminent)
|
||||||
.controlSize(.small)
|
.controlSize(.small)
|
||||||
.disabled(!isCustomSymbolValid || isSaving)
|
.disabled(!isCustomSymbolValid || isSaving || isValidatingCustomSymbol || appSettings.customCryptoSymbols.count >= 5)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isCustomSymbolValid && !customSymbolInput.isEmpty {
|
if !isCustomSymbolValid && !customSymbolInput.isEmpty {
|
||||||
@@ -536,7 +634,9 @@ struct PreferencesWindowView: View {
|
|||||||
|
|
||||||
// 删除自定义币种确认消息
|
// 删除自定义币种确认消息
|
||||||
private var deleteCustomSymbolMessage: Text {
|
private var deleteCustomSymbolMessage: Text {
|
||||||
if let customSymbol = appSettings.customCryptoSymbol {
|
if let index = pendingDeleteIndex,
|
||||||
|
index >= 0 && index < appSettings.customCryptoSymbols.count {
|
||||||
|
let customSymbol = appSettings.customCryptoSymbols[index]
|
||||||
return Text("确定要删除自定义币种 \"\(customSymbol.displayName)\" 吗?删除后将无法恢复。")
|
return Text("确定要删除自定义币种 \"\(customSymbol.displayName)\" 吗?删除后将无法恢复。")
|
||||||
} else {
|
} else {
|
||||||
return Text("确定要删除自定义币种吗?删除后将无法恢复。")
|
return Text("确定要删除自定义币种吗?删除后将无法恢复。")
|
||||||
@@ -667,7 +767,62 @@ struct PreferencesWindowView: View {
|
|||||||
// MARK: - 自定义币种相关方法
|
// MARK: - 自定义币种相关方法
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 添加自定义币种
|
* 添加自定义币种(带币安API验证)
|
||||||
|
*/
|
||||||
|
private func addCustomSymbolWithValidation() async {
|
||||||
|
guard isCustomSymbolValid, !customSymbolInput.isEmpty else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
let customSymbol = try CustomCryptoSymbol(symbol: customSymbolInput)
|
||||||
|
|
||||||
|
// 开始验证
|
||||||
|
isValidatingCustomSymbol = true
|
||||||
|
|
||||||
|
// 验证币种是否在币安API中存在
|
||||||
|
let isValid = await priceService.validateCustomSymbol(customSymbol.symbol)
|
||||||
|
|
||||||
|
await MainActor.run {
|
||||||
|
isValidatingCustomSymbol = false
|
||||||
|
|
||||||
|
if isValid {
|
||||||
|
// 验证通过,添加币种
|
||||||
|
let success = appSettings.addCustomCryptoSymbol(customSymbol)
|
||||||
|
|
||||||
|
if success {
|
||||||
|
// 清空输入状态
|
||||||
|
customSymbolInput = ""
|
||||||
|
isCustomSymbolValid = false
|
||||||
|
customSymbolErrorMessage = nil
|
||||||
|
|
||||||
|
print("✅ [Preferences] 已添加自定义币种: \(customSymbol.displayName)")
|
||||||
|
} else {
|
||||||
|
// 添加失败(可能是因为数量限制或重复)
|
||||||
|
customSymbolErrorMessage = "无法添加该币种(可能已达到最大限制或币种重复)"
|
||||||
|
isCustomSymbolValid = false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 验证失败,显示错误提示
|
||||||
|
validationFailureMessage = "币种 \"\(customSymbol.symbol)\" 在币安交易所中不存在,请检查币种代码是否正确"
|
||||||
|
showingValidationFailureAlert = true
|
||||||
|
isCustomSymbolValid = false
|
||||||
|
customSymbolErrorMessage = "币种不存在或无法获取价格"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
await MainActor.run {
|
||||||
|
isValidatingCustomSymbol = false
|
||||||
|
// 格式验证失败(这种情况理论上不会发生,因为我们在onChange中已经验证了)
|
||||||
|
print("❌ [Preferences] 添加自定义币种失败: \(error.localizedDescription)")
|
||||||
|
customSymbolErrorMessage = "添加失败:\(error.localizedDescription)"
|
||||||
|
isCustomSymbolValid = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加自定义币种(原方法,保留作为备用)
|
||||||
*/
|
*/
|
||||||
private func addCustomSymbol() {
|
private func addCustomSymbol() {
|
||||||
guard isCustomSymbolValid, !customSymbolInput.isEmpty else {
|
guard isCustomSymbolValid, !customSymbolInput.isEmpty else {
|
||||||
@@ -676,17 +831,27 @@ struct PreferencesWindowView: View {
|
|||||||
|
|
||||||
do {
|
do {
|
||||||
let customSymbol = try CustomCryptoSymbol(symbol: customSymbolInput)
|
let customSymbol = try CustomCryptoSymbol(symbol: customSymbolInput)
|
||||||
appSettings.saveCustomCryptoSymbol(customSymbol)
|
|
||||||
|
|
||||||
// 清空输入状态
|
// 使用新的添加方法
|
||||||
customSymbolInput = ""
|
let success = appSettings.addCustomCryptoSymbol(customSymbol)
|
||||||
isCustomSymbolValid = false
|
|
||||||
customSymbolErrorMessage = nil
|
|
||||||
|
|
||||||
print("✅ [Preferences] 已添加自定义币种: \(customSymbol.displayName)")
|
if success {
|
||||||
|
// 清空输入状态
|
||||||
|
customSymbolInput = ""
|
||||||
|
isCustomSymbolValid = false
|
||||||
|
customSymbolErrorMessage = nil
|
||||||
|
|
||||||
|
print("✅ [Preferences] 已添加自定义币种: \(customSymbol.displayName)")
|
||||||
|
} else {
|
||||||
|
// 添加失败(可能是因为数量限制或重复)
|
||||||
|
customSymbolErrorMessage = "无法添加该币种(可能已达到最大限制或币种重复)"
|
||||||
|
isCustomSymbolValid = false
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// 这种情况理论上不会发生,因为我们在onChange中已经验证了
|
// 这种情况理论上不会发生,因为我们在onChange中已经验证了
|
||||||
print("❌ [Preferences] 添加自定义币种失败: \(error.localizedDescription)")
|
print("❌ [Preferences] 添加自定义币种失败: \(error.localizedDescription)")
|
||||||
|
customSymbolErrorMessage = "添加失败:\(error.localizedDescription)"
|
||||||
|
isCustomSymbolValid = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -694,7 +859,13 @@ struct PreferencesWindowView: View {
|
|||||||
* 删除自定义币种
|
* 删除自定义币种
|
||||||
*/
|
*/
|
||||||
private func deleteCustomSymbol() {
|
private func deleteCustomSymbol() {
|
||||||
appSettings.removeCustomCryptoSymbol()
|
guard let index = pendingDeleteIndex else {
|
||||||
|
print("❌ [Preferences] 删除失败:无效的索引")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
appSettings.removeCustomCryptoSymbol(at: index)
|
||||||
|
pendingDeleteIndex = nil
|
||||||
print("✅ [Preferences] 已删除自定义币种")
|
print("✅ [Preferences] 已删除自定义币种")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user