feat: 更新自定义币种管理,支持多个币种及验证功能

This commit is contained in:
ZhangLei
2025-11-03 23:08:48 +08:00
parent 91127faebe
commit fd0170ac37
7 changed files with 450 additions and 116 deletions

View File

@@ -273,7 +273,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 13.5;
MARKETING_VERSION = 1.1.1;
MARKETING_VERSION = 1.2.0;
PRODUCT_BUNDLE_IDENTIFIER = "com.mark.bitcoin-monitoring";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
@@ -306,7 +306,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 13.5;
MARKETING_VERSION = 1.1.1;
MARKETING_VERSION = 1.2.0;
PRODUCT_BUNDLE_IDENTIFIER = "com.mark.bitcoin-monitoring";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;

View File

@@ -59,7 +59,7 @@ class MenuBarManager: NSObject, ObservableObject {
.store(in: &cancellables)
//
appSettings.$customCryptoSymbol
appSettings.$customCryptoSymbols
.sink { [weak self] _ in
guard let self = self else { return }
self.priceManager.updateCryptoSymbolSettings()
@@ -245,8 +245,8 @@ class MenuBarManager: NSObject, ObservableObject {
}
// -
var customSymbolMenuItem: NSMenuItem?
if let customSymbol = appSettings.customCryptoSymbol {
var customSymbolMenuItems: [NSMenuItem] = []
for customSymbol in appSettings.customCryptoSymbols {
let isCurrent = customSymbol.isCurrentSymbol(currentApiSymbol)
let placeholderTitle = isCurrent ? "\(customSymbol.displayName) (自定义): 加载中..." : " \(customSymbol.displayName) (自定义): 加载中..."
let item = NSMenuItem(title: placeholderTitle, action: #selector(self.selectOrCopySymbol(_:)), keyEquivalent: "")
@@ -258,7 +258,7 @@ class MenuBarManager: NSObject, ObservableObject {
item.isEnabled = true
item.representedObject = ["customSymbol": customSymbol, "price": 0.0, "isCustom": true]
menu.addItem(item)
customSymbolMenuItem = item
customSymbolMenuItems.append(item)
}
//
@@ -294,22 +294,24 @@ class MenuBarManager: NSObject, ObservableObject {
}
//
if let customSymbol = self.appSettings.customCryptoSymbol,
let menuItem = customSymbolMenuItem {
let isCurrent = customSymbol.isCurrentSymbol(currentSymbolAfter)
for (index, customSymbol) in self.appSettings.customCryptoSymbols.enumerated() {
if index < customSymbolMenuItems.count {
let menuItem = customSymbolMenuItems[index]
let isCurrent = customSymbol.isCurrentSymbol(currentSymbolAfter)
if let price = await self.priceManager.fetchCustomSymbolPrice(forApiSymbol: customSymbol.apiSymbol) {
let title = isCurrent ? "\(customSymbol.displayName) (自定义): $\(self.formatPriceWithCommas(price))" : " \(customSymbol.displayName) (自定义): $\(self.formatPriceWithCommas(price))"
menuItem.title = title
menuItem.isEnabled = true
menuItem.target = self
menuItem.representedObject = ["customSymbol": customSymbol, "price": price, "isCustom": true]
} else {
let title = isCurrent ? "\(customSymbol.displayName) (自定义): 错误" : " \(customSymbol.displayName) (自定义): 错误"
menuItem.title = title
menuItem.isEnabled = false
menuItem.target = self
menuItem.representedObject = ["customSymbol": customSymbol, "price": 0.0, "isCustom": true]
if let price = await self.priceManager.fetchCustomSymbolPrice(forApiSymbol: customSymbol.apiSymbol) {
let title = isCurrent ? "\(customSymbol.displayName) (自定义): $\(self.formatPriceWithCommas(price))" : " \(customSymbol.displayName) (自定义): $\(self.formatPriceWithCommas(price))"
menuItem.title = title
menuItem.isEnabled = true
menuItem.target = self
menuItem.representedObject = ["customSymbol": customSymbol, "price": price, "isCustom": true]
} else {
let title = isCurrent ? "\(customSymbol.displayName) (自定义): 错误" : " \(customSymbol.displayName) (自定义): 错误"
menuItem.title = title
menuItem.isEnabled = false
menuItem.target = self
menuItem.representedObject = ["customSymbol": customSymbol, "price": 0.0, "isCustom": true]
}
}
}
}
@@ -500,9 +502,11 @@ class MenuBarManager: NSObject, ObservableObject {
} else {
//
if isCustom, let customSymbol = data["customSymbol"] as? CustomCryptoSymbol {
//
appSettings.saveCustomCryptoSymbol(customSymbol)
print("✅ 已切换到自定义币种: \(customSymbol.displayName)")
// -
if let index = appSettings.customCryptoSymbols.firstIndex(of: customSymbol) {
appSettings.selectCustomCryptoSymbol(at: index)
print("✅ 已切换到自定义币种: \(customSymbol.displayName)")
}
//
self.priceManager.updateCryptoSymbolSettings()

View File

@@ -25,8 +25,10 @@ class AppSettings: ObservableObject {
// MARK: -
///
@Published var customCryptoSymbol: CustomCryptoSymbol?
/// 5
@Published var customCryptoSymbols: [CustomCryptoSymbol] = []
/// 使
@Published var selectedCustomSymbolIndex: Int?
/// 使
@Published var useCustomSymbol: Bool = false
@@ -52,7 +54,8 @@ class AppSettings: ObservableObject {
// MARK: -
private let customSymbolKey = "CustomCryptoSymbol"
private let customSymbolsKey = "CustomCryptoSymbols"
private let selectedCustomSymbolIndexKey = "SelectedCustomSymbolIndex"
private let useCustomSymbolKey = "UseCustomSymbol"
// MARK: -
@@ -141,17 +144,26 @@ class AppSettings: ObservableObject {
//
launchAtLogin = defaults.bool(forKey: launchAtLoginKey)
// -
if let customSymbolData = defaults.data(forKey: customSymbolKey),
let customSymbol = try? JSONDecoder().decode(CustomCryptoSymbol.self, from: customSymbolData) {
customCryptoSymbol = customSymbol
//
if let customSymbolsData = defaults.data(forKey: customSymbolsKey),
let customSymbols = try? JSONDecoder().decode([CustomCryptoSymbol].self, from: customSymbolsData) {
customCryptoSymbols = customSymbols
//
let savedIndex = defaults.integer(forKey: selectedCustomSymbolIndexKey)
if savedIndex >= 0 && savedIndex < customSymbols.count {
selectedCustomSymbolIndex = savedIndex
}
// 使
useCustomSymbol = defaults.bool(forKey: useCustomSymbolKey)
#if DEBUG
print("🔧 [AppSettings] ✅ 已加载自定义币种: \(customSymbol.displayName),使用状态: \(useCustomSymbol)")
print("🔧 [AppSettings] ✅ 已加载 \(customSymbols.count) 个自定义币种,使用状态: \(useCustomSymbol)")
if let index = selectedCustomSymbolIndex {
print("🔧 [AppSettings] 当前选中自定义币种: \(customSymbols[index].displayName)")
}
#endif
} else {
customCryptoSymbol = nil
customCryptoSymbols = []
selectedCustomSymbolIndex = nil
useCustomSymbol = false
#if DEBUG
print("🔧 [AppSettings] 未找到自定义币种数据")
@@ -172,7 +184,7 @@ class AppSettings: ObservableObject {
#if DEBUG
let proxyInfo = proxyEnabled ? "\(proxyHost):\(proxyPort)" : "未启用"
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)")
#endif
}
@@ -193,9 +205,11 @@ class AppSettings: ObservableObject {
//
useCustomSymbol = false
customCryptoSymbol = nil
customCryptoSymbols = []
selectedCustomSymbolIndex = nil
defaults.set(false, forKey: useCustomSymbolKey)
defaults.removeObject(forKey: customSymbolKey)
defaults.removeObject(forKey: customSymbolsKey)
defaults.removeObject(forKey: selectedCustomSymbolIndexKey)
//
proxyEnabled = false
@@ -236,11 +250,13 @@ class AppSettings: ObservableObject {
// 使使
if useCustomSymbol {
useCustomSymbol = false
selectedCustomSymbolIndex = nil
defaults.set(false, forKey: useCustomSymbolKey)
defaults.removeObject(forKey: selectedCustomSymbolIndexKey)
#if DEBUG
if let customSymbol = customCryptoSymbol {
print("🔧 [AppSettings] ✅ 已切换到默认币种: \(symbol.displayName)自定义币种 \(customSymbol.displayName) 保留")
if !customCryptoSymbols.isEmpty {
print("🔧 [AppSettings] ✅ 已切换到默认币种: \(symbol.displayName)\(customCryptoSymbols.count) 个自定义币种保留")
}
#endif
}
@@ -407,43 +423,133 @@ class AppSettings: ObservableObject {
// MARK: -
///
/// - Parameter customSymbol:
func saveCustomCryptoSymbol(_ customSymbol: CustomCryptoSymbol) {
customCryptoSymbol = customSymbol
useCustomSymbol = true
do {
let data = try JSONEncoder().encode(customSymbol)
defaults.set(data, forKey: customSymbolKey)
defaults.set(true, forKey: useCustomSymbolKey)
///
/// - Parameter customSymbol:
/// - Returns:
@discardableResult
func addCustomCryptoSymbol(_ customSymbol: CustomCryptoSymbol) -> Bool {
//
guard customCryptoSymbols.count < 5 else {
#if DEBUG
print("🔧 [AppSettings] ✅ 已保存自定义币种: \(customSymbol.displayName)")
#endif
} catch {
#if DEBUG
print("🔧 [AppSettings] ❌ 保存自定义币种失败: \(error.localizedDescription)")
print("🔧 [AppSettings] ⚠️ 已达到最大自定义币种数量限制 (5个)")
#endif
return false
}
}
///
func removeCustomCryptoSymbol() {
customCryptoSymbol = nil
useCustomSymbol = false
defaults.removeObject(forKey: customSymbolKey)
defaults.set(false, forKey: useCustomSymbolKey)
//
guard !customCryptoSymbols.contains(customSymbol) else {
#if DEBUG
print("🔧 [AppSettings] ⚠️ 自定义币种已存在: \(customSymbol.displayName)")
#endif
return false
}
customCryptoSymbols.append(customSymbol)
//
if customCryptoSymbols.count == 1 {
selectedCustomSymbolIndex = 0
useCustomSymbol = true
defaults.set(true, forKey: useCustomSymbolKey)
}
// UserDefaults
saveCustomCryptoSymbols()
#if DEBUG
print("🔧 [AppSettings] ✅ 已移除自定义币种")
print("🔧 [AppSettings] ✅ 已添加自定义币种: \(customSymbol.displayName),当前总数: \(customCryptoSymbols.count)")
#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
/// - Returns: API
func getCurrentActiveApiSymbol() -> String {
if useCustomSymbol, let customSymbol = customCryptoSymbol {
if useCustomSymbol, let customSymbol = getCurrentSelectedCustomSymbol() {
return customSymbol.apiSymbol
} else {
return selectedSymbol.apiSymbol
@@ -453,7 +559,7 @@ class AppSettings: ObservableObject {
///
/// - Returns:
func getCurrentActiveDisplayName() -> String {
if useCustomSymbol, let customSymbol = customCryptoSymbol {
if useCustomSymbol, let customSymbol = getCurrentSelectedCustomSymbol() {
return customSymbol.displayName
} else {
return selectedSymbol.displayName
@@ -463,7 +569,7 @@ class AppSettings: ObservableObject {
///
/// - Returns:
func getCurrentActiveSystemImageName() -> String {
if useCustomSymbol, let customSymbol = customCryptoSymbol {
if useCustomSymbol, let customSymbol = getCurrentSelectedCustomSymbol() {
return customSymbol.systemImageName
} else {
return selectedSymbol.systemImageName
@@ -473,7 +579,7 @@ class AppSettings: ObservableObject {
///
/// - Returns:
func getCurrentActivePairDisplayName() -> String {
if useCustomSymbol, let customSymbol = customCryptoSymbol {
if useCustomSymbol, let customSymbol = getCurrentSelectedCustomSymbol() {
return customSymbol.pairDisplayName
} else {
return selectedSymbol.pairDisplayName
@@ -483,7 +589,7 @@ class AppSettings: ObservableObject {
/// 使
/// - Returns: 使
func isUsingCustomSymbol() -> Bool {
return useCustomSymbol && customCryptoSymbol != nil
return useCustomSymbol && !customCryptoSymbols.isEmpty && selectedCustomSymbolIndex != nil
}
}

View File

@@ -17,7 +17,8 @@ class PriceManager: ObservableObject {
@Published var selectedSymbol: CryptoSymbol
//
@Published var customCryptoSymbol: CustomCryptoSymbol?
@Published var customCryptoSymbols: [CustomCryptoSymbol] = []
@Published var selectedCustomSymbolIndex: Int?
@Published var useCustomSymbol: Bool = false
private let priceService: PriceService
@@ -35,7 +36,8 @@ class PriceManager: ObservableObject {
self.priceService = PriceService(appSettings: appSettings)
//
self.customCryptoSymbol = appSettings.customCryptoSymbol
self.customCryptoSymbols = appSettings.customCryptoSymbols
self.selectedCustomSymbolIndex = appSettings.selectedCustomSymbolIndex
self.useCustomSymbol = appSettings.useCustomSymbol
startPriceUpdates()
@@ -322,7 +324,8 @@ class PriceManager: ObservableObject {
/// AppSettings
func updateCryptoSymbolSettings() {
customCryptoSymbol = appSettings.customCryptoSymbol
customCryptoSymbols = appSettings.customCryptoSymbols
selectedCustomSymbolIndex = appSettings.selectedCustomSymbolIndex
useCustomSymbol = appSettings.useCustomSymbol
//
@@ -344,7 +347,8 @@ class PriceManager: ObservableObject {
func getAllAvailableSymbols() -> [String] {
var symbols = CryptoSymbol.allApiSymbols
if let customSymbol = appSettings.customCryptoSymbol {
//
for customSymbol in appSettings.customCryptoSymbols {
symbols.append(customSymbol.apiSymbol)
}
@@ -361,9 +365,10 @@ class PriceManager: ObservableObject {
}
//
if let customSymbol = appSettings.customCryptoSymbol,
customSymbol.apiSymbol == apiSymbol {
return customSymbol.displayName
for customSymbol in appSettings.customCryptoSymbols {
if customSymbol.apiSymbol == apiSymbol {
return customSymbol.displayName
}
}
// APIUSDT

View File

@@ -240,6 +240,54 @@ class PriceService: NSObject, ObservableObject, URLSessionTaskDelegate {
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: -
/**

View File

@@ -2,7 +2,7 @@
// CustomCryptoSymbol.swift
// Bitcoin Monitoring
//
// Created by Claude on 2025/11/03.
// Created by Mark on 2025/11/03.
//
import Foundation

View File

@@ -72,6 +72,15 @@ struct PreferencesWindowView: View {
@State private var isCustomSymbolValid: Bool = false
@State private var customSymbolErrorMessage: String?
@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
@@ -81,6 +90,7 @@ struct PreferencesWindowView: View {
init(appSettings: AppSettings, onClose: @escaping () -> Void) {
self.appSettings = appSettings
self.priceService = PriceService(appSettings: appSettings)
self.onClose = onClose
//
@@ -114,6 +124,11 @@ struct PreferencesWindowView: View {
} message: {
deleteCustomSymbolMessage
}
.alert("币种验证失败", isPresented: $showingValidationFailureAlert) {
Button("确定", role: .cancel) { }
} message: {
Text(validationFailureMessage)
}
}
//
@@ -396,56 +411,121 @@ struct PreferencesWindowView: View {
private var customCryptoSettingsView: some View {
SettingsGroupView(title: "自定义币种", icon: "plus.circle") {
VStack(alignment: .leading, spacing: 16) {
if appSettings.isUsingCustomSymbol() {
currentCustomSymbolView
} else {
addCustomSymbolView
//
if !appSettings.customCryptoSymbols.isEmpty {
customSymbolsListView
}
//
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 {
VStack(alignment: .leading, spacing: 8) {
HStack {
Image(systemName: appSettings.getCurrentActiveSystemImageName())
.foregroundColor(.orange)
.font(.system(size: 16))
//
private func customSymbolRowView(at index: Int) -> some View {
let customSymbol = appSettings.customCryptoSymbols[index]
let isSelected = appSettings.isUsingCustomSymbol() && appSettings.selectedCustomSymbolIndex == index
VStack(alignment: .leading, spacing: 2) {
Text("当前自定义币种")
.font(.subheadline)
.fontWeight(.medium)
return HStack {
//
Image(systemName: isSelected ? "checkmark.circle.fill" : "circle")
.font(.system(size: 14))
.foregroundColor(isSelected ? .blue : .secondary)
Text(appSettings.getCurrentActivePairDisplayName())
.font(.caption)
.foregroundColor(.secondary)
}
//
Image(systemName: customSymbol.systemImageName)
.foregroundColor(.orange)
.font(.system(size: 16))
Spacer()
//
VStack(alignment: .leading, spacing: 2) {
Text(customSymbol.displayName)
.font(.subheadline)
.fontWeight(isSelected ? .medium : .regular)
.foregroundColor(.primary)
Button("删除") {
showingCustomSymbolDeleteConfirmation = true
}
.buttonStyle(.bordered)
.controlSize(.small)
.foregroundColor(.red)
Text(customSymbol.pairDisplayName)
.font(.caption)
.foregroundColor(.secondary)
}
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 {
VStack(alignment: .leading, spacing: 12) {
Text("添加自定义币种")
Text(appSettings.customCryptoSymbols.isEmpty ? "添加自定义币种" : "添加更多自定义币种")
.font(.subheadline)
.foregroundColor(.primary)
Text("输入3-5个大写字母的币种符号ADA、DOGE、SHIB")
Text("输入3-5个大写字母的币种符号ENA、TRX、TRUMP")
.font(.caption)
.foregroundColor(.secondary)
//
if appSettings.customCryptoSymbols.count >= 5 {
Text("已达到最大限制5个币种")
.font(.caption)
.foregroundColor(.orange)
}
customSymbolInputView
}
}
@@ -458,7 +538,7 @@ struct PreferencesWindowView: View {
.foregroundColor(.secondary)
HStack(spacing: 12) {
TextField("例如: ADA", text: Binding(
TextField("例如: TRX", text: Binding(
get: { customSymbolInput },
set: { newValue in
let filteredValue = newValue.filter { $0.isLetter }.uppercased()
@@ -471,13 +551,31 @@ struct PreferencesWindowView: View {
))
.textFieldStyle(RoundedBorderTextFieldStyle())
.frame(maxWidth: .infinity)
.onSubmit {
//
Task {
await addCustomSymbolWithValidation()
}
}
Button("添加") {
addCustomSymbol()
Button {
Task {
await addCustomSymbolWithValidation()
}
} label: {
if isValidatingCustomSymbol {
HStack(spacing: 4) {
ProgressView()
.controlSize(.small)
Text("验证中...")
}
} else {
Text("添加")
}
}
.buttonStyle(.borderedProminent)
.controlSize(.small)
.disabled(!isCustomSymbolValid || isSaving)
.disabled(!isCustomSymbolValid || isSaving || isValidatingCustomSymbol || appSettings.customCryptoSymbols.count >= 5)
}
if !isCustomSymbolValid && !customSymbolInput.isEmpty {
@@ -536,7 +634,9 @@ struct PreferencesWindowView: View {
//
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)\" 吗?删除后将无法恢复。")
} else {
return Text("确定要删除自定义币种吗?删除后将无法恢复。")
@@ -667,7 +767,62 @@ struct PreferencesWindowView: View {
// 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() {
guard isCustomSymbolValid, !customSymbolInput.isEmpty else {
@@ -676,17 +831,27 @@ struct PreferencesWindowView: View {
do {
let customSymbol = try CustomCryptoSymbol(symbol: customSymbolInput)
appSettings.saveCustomCryptoSymbol(customSymbol)
//
customSymbolInput = ""
isCustomSymbolValid = false
customSymbolErrorMessage = nil
// 使
let success = appSettings.addCustomCryptoSymbol(customSymbol)
print("✅ [Preferences] 已添加自定义币种: \(customSymbol.displayName)")
if success {
//
customSymbolInput = ""
isCustomSymbolValid = false
customSymbolErrorMessage = nil
print("✅ [Preferences] 已添加自定义币种: \(customSymbol.displayName)")
} else {
//
customSymbolErrorMessage = "无法添加该币种(可能已达到最大限制或币种重复)"
isCustomSymbolValid = false
}
} catch {
// onChange
print("❌ [Preferences] 添加自定义币种失败: \(error.localizedDescription)")
customSymbolErrorMessage = "添加失败:\(error.localizedDescription)"
isCustomSymbolValid = false
}
}
@@ -694,7 +859,13 @@ struct PreferencesWindowView: View {
*
*/
private func deleteCustomSymbol() {
appSettings.removeCustomCryptoSymbol()
guard let index = pendingDeleteIndex else {
print("❌ [Preferences] 删除失败:无效的索引")
return
}
appSettings.removeCustomCryptoSymbol(at: index)
pendingDeleteIndex = nil
print("✅ [Preferences] 已删除自定义币种")
}
}